summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorAmery Hung <ameryhung@gmail.com>2026-09-28 11:53:33 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-09-29 10:35:12 +0000
commit46e0744621081c25fe3ccf4e4c270ca2844e7dff (patch)
treedd127c360663ddbb1e167f16f673cadf2cf95393 /kernel
parent77d9384a69cdbaeaf97eca8c624c47436703e76d (diff)
downloadlinux-next-46e0744621081c25fe3ccf4e4c270ca2844e7dff.tar.gz
linux-next-46e0744621081c25fe3ccf4e4c270ca2844e7dff.zip
bpf: Check global subprog memory arguments in the common path
Route fixed-size global ARG_PTR_TO_MEM arguments through check_func_arg(). Preserve their read-write access check, nullable contract, and support for BTF-defined allocated memory. Recognize subprog calls explicitly in the common checker. Global subprog stack liveness can prove that bytes in an argument are unused by the callee. Retain the existing allowance for those poisoned stack bytes when call metadata is present, and update the nullability log expectation. The verifier also rejects packet pointers when the callee may change packet data. The concrete packet-backed register state is only available while checking the call site; the independently verified callee sees generic PTR_TO_MEM. Record this property as subprog_may_change_pkt in subprog-only call metadata and retain the packet-pointer rejection in the common fixed-memory path. Signed-off-by: Amery Hung <ameryhung@gmail.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20260928185334.1004200-11-ameryhung@gmail.com
Diffstat (limited to 'kernel')
-rw-r--r--kernel/bpf/verifier.c58
1 files changed, 27 insertions, 31 deletions
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 8aa1336453a4..ad18b6ab7e88 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -7703,6 +7703,11 @@ size_error:
return err;
}
+static bool is_subprog(const struct bpf_call_arg_meta *meta)
+{
+ return meta->btf && !meta->func_id;
+}
+
static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
argno_t argno, u32 mem_size, enum bpf_access_type access_type,
struct bpf_call_arg_meta *meta, bool *known_memory)
@@ -7721,10 +7726,11 @@ static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg
}
/*
- * Only a global subprog (meta == NULL) may read poisoned stack slots:
+ * Only a global subprog may read poisoned stack slots:
* its static stack liveness proved the callee body skips them.
*/
- size = (!meta && base_type(reg->type) == PTR_TO_STACK) ? -(int)mem_size : mem_size;
+ size = (is_subprog(meta) &&
+ base_type(reg->type) == PTR_TO_STACK) ? -(int)mem_size : mem_size;
if (access_type & BPF_READ)
err = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta,
@@ -9036,8 +9042,8 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
type &= ~PTR_MAYBE_NULL;
if (base_type(arg_type) == ARG_PTR_TO_MEM)
type &= ~DYNPTR_TYPE_FLAG_MASK;
- /* Allow allocated memory for kfunc ARG_PTR_TO_MEM but not helper. */
- if (is_kfunc(meta) && base_type(arg_type) == ARG_PTR_TO_MEM &&
+ /* Allow allocated memory for BTF-defined ARG_PTR_TO_MEM but not helpers. */
+ if (!is_helper(meta) && base_type(arg_type) == ARG_PTR_TO_MEM &&
type_is_ptr_alloc_obj(type))
type = PTR_TO_MEM;
@@ -9667,6 +9673,17 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
bpf_diag_reg_type_plain(env, reg->type));
return err;
}
+ /*
+ * PTR_TO_PACKET gets passed as PTR_TO_MEM, preventing us from adjusting
+ * bounds tracking information.
+ */
+ if (is_subprog(meta) && meta->subprog_may_change_pkt &&
+ (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg))) {
+ verbose(env,
+ "cannot pass packet pointer %s to %s(): function may change packet data\n",
+ reg_arg_name(env, argno), meta->func_name);
+ return -EINVAL;
+ }
if (arg_type & MEM_ALIGNED)
err = check_ptr_alignment(env, reg, 0, arg_size, true);
break;
@@ -10806,6 +10823,9 @@ static void gen_subprog_arg_proto(const struct bpf_subprog_info *sub, const stru
arg_type = ARG_IGNORE;
} else if (base_type(arg_type) == ARG_PTR_TO_ARENA) {
arg_type |= PTR_MAYBE_NULL;
+ } else if (base_type(arg_type) == ARG_PTR_TO_MEM) {
+ proto->arg_size[arg] = sub->args[slot].mem_size;
+ arg_type |= MEM_FIXED_SIZE | MEM_WRITE;
} else if (base_type(arg_type) == ARG_PTR_TO_BTF_ID) {
proto->arg_btf_id[arg] = &sub->args[slot].btf_id;
}
@@ -10821,7 +10841,6 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
{
struct bpf_subprog_info *sub = subprog_info(env, subprog);
struct bpf_func_state *caller = cur_func(env);
- struct bpf_verifier_log *log = &env->log;
const struct btf_param *args, *stack_args;
const struct btf_type *func, *func_proto;
struct bpf_call_arg_meta meta;
@@ -10831,6 +10850,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
memset(&meta, 0, sizeof(meta));
meta.btf = btf;
+ meta.subprog_may_change_pkt = sub->changes_pkt_data;
meta.func_name = bpf_subprog_name(env, subprog);
ret = btf_prepare_func_args(env, subprog);
@@ -10863,7 +10883,6 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
* verifier sees.
*/
for (arg = 0, slot = 0; arg < btf_type_vlen(func_proto); arg++, slot += nslots) {
- struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, slot);
enum bpf_arg_type arg_type = fn->arg_type[arg];
argno_t argno = argno_from_arg(slot + 1);
const struct btf_type *t;
@@ -10875,34 +10894,11 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
if (arg_type == ARG_SCALAR || arg_type == ARG_IGNORE ||
arg_type == ARG_PTR_TO_CTX || arg_type == ARG_PTR_TO_DYNPTR ||
base_type(arg_type) == ARG_PTR_TO_ARENA ||
- base_type(arg_type) == ARG_PTR_TO_BTF_ID) {
+ base_type(arg_type) == ARG_PTR_TO_BTF_ID ||
+ base_type(arg_type) == ARG_PTR_TO_MEM) {
ret = check_func_arg(env, arg, slot, 0, &meta, env->insn_idx);
if (ret)
return ret;
- } else if (base_type(arg_type) == ARG_PTR_TO_MEM) {
- ret = check_func_arg_reg_off(env, reg, argno, ARG_PTR_TO_MEM);
- if (ret < 0)
- return ret;
- if (check_mem_reg(env, reg, argno, sub->args[slot].mem_size,
- BPF_READ | BPF_WRITE, NULL,
- NULL))
- return -EINVAL;
- /*
- * PTR_TO_PACKET get passed as PTR_TO_MEM, preventing
- * us from adjusting bounds tracking info.
- */
- if ((reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) &&
- sub->changes_pkt_data) {
- bpf_log(log, "%s is a packet pointer, but func#%d may change packet data\n",
- reg_arg_name(env, argno), subprog);
- return -EINVAL;
- }
- if (!(arg_type & PTR_MAYBE_NULL) &&
- (type_may_be_null(reg->type) || bpf_register_is_null(reg))) {
- bpf_log(log, "%s is expected to be non-NULL\n",
- reg_arg_name(env, argno));
- return -EINVAL;
- }
} else {
verifier_bug(env, "unrecognized %s type %d",
reg_arg_name(env, argno), arg_type);