From: Amery Hung <ameryhung@gmail.com>
To: bpf@vger.kernel.org
Cc: alexei.starovoitov@gmail.com, andrii@kernel.org,
daniel@iogearbox.net, eddyz87@gmail.com, memxor@gmail.com,
ameryhung@gmail.com, kernel-team@meta.com
Subject: [PATCH bpf-next v2 10/11] bpf: Check global subprog memory arguments in the common path
Date: Mon, 28 Sep 2026 11:14:12 -0700 [thread overview]
Message-ID: <20260928181414.644158-11-ameryhung@gmail.com> (raw)
In-Reply-To: <20260928181414.644158-1-ameryhung@gmail.com>
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>
---
include/linux/bpf_verifier.h | 3 +
kernel/bpf/verifier.c | 58 +++++++++----------
.../bpf/progs/verifier_global_ptr_args.c | 3 +-
.../bpf/progs/verifier_global_subprogs.c | 2 +-
4 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 144da990ee8c..385a63edc095 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1649,6 +1649,9 @@ struct bpf_call_arg_meta {
struct ret_mem_desc ret_mem;
struct arg_raw_mem_desc arg_raw_mem;
+ /* Only set by subprog */
+ bool subprog_may_change_pkt;
+
/* Only set by kfunc */
bool r0_rdonly;
u32 kfunc_flags;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index a39b23864878..2142610ce503 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -7702,6 +7702,11 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
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)
@@ -7720,10 +7725,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,
@@ -9047,8 +9053,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;
@@ -9678,6 +9684,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;
@@ -10817,6 +10834,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;
}
@@ -10832,7 +10852,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;
@@ -10842,6 +10861,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);
@@ -10874,7 +10894,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;
@@ -10886,34 +10905,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);
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
index f639e2767e35..03507eeae3cb 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
@@ -389,7 +389,8 @@ __weak int subprog_pkt_ptr_changes_data(struct __sk_buff *skb __arg_ctx,
SEC("?tc")
__failure __log_level(2)
-__msg("R2 is a packet pointer, but func#{{[0-9]+}} may change packet data")
+__msg("cannot pass packet pointer R2")
+__msg("function may change packet data")
__msg("Caller passes invalid args into func#{{[0-9]+}} ('subprog_pkt_ptr_changes_data')")
int pkt_ptr_to_global_mem_arg_changes_data(struct __sk_buff *skb)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
index 27fbe54e8795..574b26b5a7df 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
@@ -195,7 +195,7 @@ int arg_tag_nonnull_ptr_good(void *ctx)
SEC("?raw_tp")
__failure __log_level(2)
-__msg("R1 is expected to be non-NULL")
+__msg("Possibly NULL pointer passed to trusted R1")
int arg_tag_nonnull_ptr_null_bad(void *ctx)
{
int y = 74;
--
2.52.0
next prev parent reply other threads:[~2026-09-28 18:14 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 18:14 [PATCH bpf-next v2 00/11] Unify subprog argument checks Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 01/11] bpf: Fix kfunc BTF parameter lookups after wide arguments Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 02/11] bpf: Identify subprog calls in argument metadata Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 03/11] bpf: Build argument prototypes for subprog calls Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 04/11] bpf: Check subprog scalar arguments in the common path Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 05/11] bpf: Check global subprog untrusted " Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 06/11] bpf: Check subprog context " Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 07/11] bpf: Check subprog arena " Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 08/11] bpf: Check subprog dynptr " Amery Hung
2026-09-28 18:28 ` sashiko-bot
2026-09-28 18:33 ` Amery Hung
2026-09-28 18:14 ` [PATCH bpf-next v2 09/11] bpf: Check global subprog BTF-ID " Amery Hung
2026-09-28 18:14 ` Amery Hung [this message]
2026-09-28 18:14 ` [PATCH bpf-next v2 11/11] bpf: Check all subprog " Amery Hung
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260928181414.644158-11-ameryhung@gmail.com \
--to=ameryhung@gmail.com \
--cc=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=kernel-team@meta.com \
--cc=memxor@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox