* [PATCH v2 0/2] bpf: reject stack-argument callback subprograms @ 2026-08-17 20:48 Jérémy Jean 2026-08-17 20:48 ` [PATCH v2 1/2] " Jérémy Jean 2026-08-17 20:48 ` [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test Jérémy Jean 0 siblings, 2 replies; 7+ messages in thread From: Jérémy Jean @ 2026-08-17 20:48 UTC (permalink / raw) To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Yonghong Song Cc: bpf, linux-kernel, linux-kselftest, Jérémy Jean Hello, Thanks for the feedbacks. Here is the v2 that splits the patch into two commits. I hope I made it correctly. /Jérémy --- Helper callbacks enter BPF subprograms through bpf_callback_t, whose runtime ABI supplies five arguments. BTF validation nevertheless permits static callback subprograms to declare more than five arguments when JIT stack arguments are supported. This lets verifier state for a callback use outgoing stack argument slots prepared at the helper call site. The helper does not pass those slots. On x86-64, callback loads of arguments seven and later therefore read the helper native frame instead of the synthetic values checked by the verifier. This series rejects callback subprograms with incoming stack arguments and adds verifier coverage for the rejection. Changes v1 -> v2: - split the verifier fix and selftest into separate patches; - drop the callback-specific verifier log message and stop matching it in the selftest; - use __clobber_common plus r6 in the selftest. Jérémy Jean (2): bpf: reject stack-argument callback subprograms selftests/bpf: add callback stack-argument rejection test v1: https://lore.kernel.org/bpf/14a7e7c2-36f7-4aa2-9b20-cc54700a9f1b@linux.dev/T/ kernel/bpf/verifier.c | 2 ++ .../selftests/bpf/progs/verifier_stack_arg.c | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+) -- 2.47.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/2] bpf: reject stack-argument callback subprograms 2026-08-17 20:48 [PATCH v2 0/2] bpf: reject stack-argument callback subprograms Jérémy Jean @ 2026-08-17 20:48 ` Jérémy Jean 2026-08-17 21:11 ` sashiko-bot 2026-08-17 21:37 ` bot+bpf-ci 2026-08-17 20:48 ` [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test Jérémy Jean 1 sibling, 2 replies; 7+ messages in thread From: Jérémy Jean @ 2026-08-17 20:48 UTC (permalink / raw) To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Yonghong Song Cc: bpf, linux-kernel, linux-kselftest, Jérémy Jean Helper callbacks enter BPF subprograms through bpf_callback_t, whose runtime ABI supplies five arguments. BTF validation nevertheless permits static callback subprograms to declare more than five arguments when JIT stack arguments are supported. This lets verifier state for a callback use outgoing stack argument slots prepared at the helper call site. The helper does not pass those slots. On x86-64, callback loads of arguments seven and later therefore read the helper native frame instead of the synthetic values checked by the verifier. KASAN reports a slab OOB write. Reject callback subprograms with incoming stack arguments when processing callback calls. Fixes: 0f6bd5e7a804 ("bpf: Support stack arguments for bpf functions") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr> --- kernel/bpf/verifier.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index fdc5fbb1f78c..5fcefc0eaba0 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9285,6 +9285,8 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins err = btf_check_subprog_call(env, subprog, caller->regs); if (err == -EFAULT) return err; + if (bpf_in_stack_arg_cnt(&env->subprog_info[subprog])) + return -EINVAL; /* set_callee_state is used for direct subprog calls, but we are * interested in validating only BPF helpers that can call subprogs as -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v2 1/2] bpf: reject stack-argument callback subprograms 2026-08-17 20:48 ` [PATCH v2 1/2] " Jérémy Jean @ 2026-08-17 21:11 ` sashiko-bot 2026-08-17 21:37 ` bot+bpf-ci 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-08-17 21:11 UTC (permalink / raw) To: Jérémy Jean; +Cc: bpf Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The patch silently rejects callback subprograms with stack arguments by returning `-EINVAL` without emitting a verifier log message. -- --- Patch [1]: [PATCH v2 1/2] bpf: reject stack-argument callback subprograms --- commit 0a06c150da6a5163953fb53d000c974d1e9a97dc Author: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr> bpf: reject stack-argument callback subprograms This commit rejects callback subprograms that declare incoming stack arguments when processing callback calls. It prevents out-of-bounds reads in callbacks that attempt to read arguments from the helper's native frame instead of the synthetic verifier values. > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index fdc5fbb1f78ca..5fcefc0eaba00 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -9285,6 +9285,8 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins > err = btf_check_subprog_call(env, subprog, caller->regs); > if (err == -EFAULT) > return err; > + if (bpf_in_stack_arg_cnt(&env->subprog_info[subprog])) > + return -EINVAL; [Severity: Medium] Does this silently reject the callback subprogram without providing a verifier log message? Returning -EINVAL without using verbose() leaves the user with an opaque error when a BPF program uses a callback subprogram with stack arguments. Would it be helpful to add a verbose() log here to explain the load failure to BPF developers? > > /* set_callee_state is used for direct subprog calls, but we are > * interested in validating only BPF helpers that can call subprogs as -- Sashiko AI review · https://sashiko.dev/#/patchset/20260817204812.1637171-1-Jeremy.Jean@oss.cyber.gouv.fr?part=1 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 1/2] bpf: reject stack-argument callback subprograms 2026-08-17 20:48 ` [PATCH v2 1/2] " Jérémy Jean 2026-08-17 21:11 ` sashiko-bot @ 2026-08-17 21:37 ` bot+bpf-ci 1 sibling, 0 replies; 7+ messages in thread From: bot+bpf-ci @ 2026-08-17 21:37 UTC (permalink / raw) To: Jeremy.Jean, ast, daniel, andrii, eddyz87, memxor, yonghong.song Cc: bpf, linux-kernel, linux-kselftest, Jeremy.Jean, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 2265 bytes --] > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index e421ea2b80c3..6f6e35bc7c97 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -9785,6 +9785,8 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins > err = btf_check_subprog_call(env, subprog, caller->regs); > if (err == -EFAULT) > return err; > + if (bpf_in_stack_arg_cnt(&env->subprog_info[subprog])) > + return -EINVAL; Should this rejection include a verbose() message explaining why the callback was rejected? Currently this returns a bare -EINVAL, which means bpf_loop(), bpf_for_each_map_elem(), bpf_timer_set_callback(), bpf_find_vma(), and bpf_user_ringbuf_drain() with a callback that has more than five arguments will fail with EINVAL but nothing in the verifier log shows why. check_helper_call() propagates the error silently for all five of those helpers. Only the three kfunc callback sites (in check_kfunc_call()) add "kfunc %s#%d failed callback verification". Every other stack-argument rejection in this feature logs a message before returning: check_stack_arg_read() reports "invalid read from stack arg off %d depth %d", check_stack_arg_write() reports "stack arg write offset %d exceeds max %d stack args", check_outgoing_stack_args() reports "callee expects %d args, stack arg%d is not initialized", and bpf_fixup_call_args() reports "func#%d writes %u stack arg slots, but calls only require %u". This is the path the patch is meant to reject. For the other outcome (the caller's outgoing slots are not initialized), btf_check_subprog_call() returns -EFAULT and the existing message is printed, and push_callback_call() returns before this new check. All other error paths in push_callback_call() also log: verifier_bug() for the two internal consistency checks, and verbose() inside setup_func_entry() or push_stack(). > > /* set_callee_state is used for direct subprog calls, but we are > * interested in validating only BPF helpers that can call subprogs as --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32068993990 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test 2026-08-17 20:48 [PATCH v2 0/2] bpf: reject stack-argument callback subprograms Jérémy Jean 2026-08-17 20:48 ` [PATCH v2 1/2] " Jérémy Jean @ 2026-08-17 20:48 ` Jérémy Jean 2026-08-17 21:12 ` sashiko-bot 2026-08-17 21:24 ` bot+bpf-ci 1 sibling, 2 replies; 7+ messages in thread From: Jérémy Jean @ 2026-08-17 20:48 UTC (permalink / raw) To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Yonghong Song Cc: bpf, linux-kernel, linux-kselftest, Jérémy Jean Add verifier coverage for callback subprograms that declare incoming stack arguments. The test passes a nine-argument callback to bpf_loop() and expects the verifier to reject it. Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr> --- .../selftests/bpf/progs/verifier_stack_arg.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_stack_arg.c b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c index 7e0ce5db28a0..50b276fc2c3f 100644 --- a/tools/testing/selftests/bpf/progs/verifier_stack_arg.c +++ b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c @@ -27,6 +27,13 @@ static int subprog_7args(int a, int b, int c, int d, int e, int f, int g) return a + b + c + d + e + f + g; } +__noinline __used +static int callback_9args(__u32 index, void *ctx, long a3, long a4, + long a5, long a6, long a7, long a8, long a9) +{ + return a9; +} + __noinline __used static long subprog_deref_arg6(long a, long b, long c, long d, long e, long *f) { @@ -79,6 +86,31 @@ __naked void stack_arg_two_subprogs(void) ); } +SEC("tc") +__description("stack_arg: callback with incoming stack args") +__failure +__naked void stack_arg_callback_many_args(void) +{ + asm volatile ( + "r6 = 0;" + "*(u64 *)(r11 - 32) = 0;" + "*(u64 *)(r11 - 24) = 0;" + "*(u64 *)(r11 - 16) = 0;" + "*(u64 *)(r11 - 8) = 0;" + "r1 = 1;" + "r2 = %[callback_9args];" + "r3 = 0;" + "r4 = 0;" + "call %[bpf_loop];" + "r0 = 0;" + "exit;" + : + : __imm_ptr(callback_9args), + __imm(bpf_loop) + : __clobber_common, "r6" + ); +} + SEC("tc") __description("stack_arg: read from uninitialized stack arg slot") __failure -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test 2026-08-17 20:48 ` [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test Jérémy Jean @ 2026-08-17 21:12 ` sashiko-bot 2026-08-17 21:24 ` bot+bpf-ci 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-08-17 21:12 UTC (permalink / raw) To: Jérémy Jean; +Cc: bpf Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The newly added selftest lacks a `__msg(...)` annotation and the verifier silently rejects the program, rendering the test ineffective at preventing regressions of the vulnerability. -- --- Patch [2]: [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test --- commit 3ed3bf88f0e2a4dcd1bfa96fc2ece2b85d98465e Author: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr> selftests/bpf: add callback stack-argument rejection test This commit adds verifier coverage for callback subprograms that declare incoming stack arguments. It passes a nine-argument callback to bpf_loop() and expects the verifier to reject it. > diff --git a/tools/testing/selftests/bpf/progs/verifier_stack_arg.c b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c > index 7e0ce5db28a0d..50b276fc2c3f2 100644 > --- a/tools/testing/selftests/bpf/progs/verifier_stack_arg.c > +++ b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c [ ... ] > @@ -79,6 +86,31 @@ __naked void stack_arg_two_subprogs(void) > ); > } > > +SEC("tc") > +__description("stack_arg: callback with incoming stack args") > +__failure [Severity: Medium] Should this test include a __msg(...) annotation to verify the exact rejection reason? Because the test manually prepares outgoing stack slots (*(u64 *)(r11 - 32) = 0; etc.), it might fall back to failing in bpf_fixup_call_args() if the primary callback check in push_callback_call() regresses. This would cause the test to falsely report success even if the intended security check is bypassed. Additionally, push_callback_call() in the verifier currently rejects the callback by silently returning -EINVAL without emitting a verbose() error message. Does the verifier need to be updated to emit a log message so that this selftest can properly assert against it? > +__naked void stack_arg_callback_many_args(void) > +{ > + asm volatile ( > + "r6 = 0;" > + "*(u64 *)(r11 - 32) = 0;" > + "*(u64 *)(r11 - 24) = 0;" > + "*(u64 *)(r11 - 16) = 0;" > + "*(u64 *)(r11 - 8) = 0;" > + "r1 = 1;" > + "r2 = %[callback_9args];" > + "r3 = 0;" > + "r4 = 0;" > + "call %[bpf_loop];" > + "r0 = 0;" > + "exit;" > + : > + : __imm_ptr(callback_9args), > + __imm(bpf_loop) > + : __clobber_common, "r6" > + ); > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260817204812.1637171-1-Jeremy.Jean@oss.cyber.gouv.fr?part=2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test 2026-08-17 20:48 ` [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test Jérémy Jean 2026-08-17 21:12 ` sashiko-bot @ 2026-08-17 21:24 ` bot+bpf-ci 1 sibling, 0 replies; 7+ messages in thread From: bot+bpf-ci @ 2026-08-17 21:24 UTC (permalink / raw) To: Jeremy.Jean, ast, daniel, andrii, eddyz87, memxor, yonghong.song Cc: bpf, linux-kernel, linux-kselftest, Jeremy.Jean, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 4122 bytes --] > diff --git a/tools/testing/selftests/bpf/progs/verifier_stack_arg.c b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c > index 7e0ce5db28a0..50b276fc2c3f 100644 > --- a/tools/testing/selftests/bpf/progs/verifier_stack_arg.c > +++ b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c > @@ -27,6 +27,13 @@ static int subprog_7args(int a, int b, int c, int d, int e, int f, int g) > return a + b + c + d + e + f + g; > } > > +__noinline __used > +static int callback_9args(__u32 index, void *ctx, long a3, long a4, > + long a5, long a6, long a7, long a8, long a9) > +{ > + return a9; > +} > + > __noinline __used > static long subprog_deref_arg6(long a, long b, long c, long d, long e, long *f) > { > @@ -79,6 +86,31 @@ __naked void stack_arg_two_subprogs(void) > ); > } > > +SEC("tc") > +__description("stack_arg: callback with incoming stack args") > +__failure > +__naked void stack_arg_callback_many_args(void) > +{ > + asm volatile ( > + "r6 = 0;" > + "*(u64 *)(r11 - 32) = 0;" > + "*(u64 *)(r11 - 24) = 0;" > + "*(u64 *)(r11 - 16) = 0;" > + "*(u64 *)(r11 - 8) = 0;" > + "r1 = 1;" > + "r2 = %[callback_9args];" > + "r3 = 0;" > + "r4 = 0;" > + "call %[bpf_loop];" > + "r0 = 0;" > + "exit;" > + : > + : __imm_ptr(callback_9args), > + __imm(bpf_loop) > + : __clobber_common, "r6" > + ); > +} A subsystem pattern flags this as potentially concerning: the test appears to be rejected by a pre-existing verifier check even without the kernel change it is supposed to cover (9b1ccc65b120 "bpf: reject stack-argument callback subprograms"), so it may pass on both a fixed and an unfixed kernel. On a kernel WITHOUT 9b1ccc65b120: The four `*(u64 *)(r11 - N) = 0` stores in func#0 go through check_stack_arg_write() which records `subprog->max_out_stack_arg_cnt = 4` for subprog 0. The only call in func#0 is the helper `bpf_loop`. Helper callback dispatch goes through push_callback_call(), which never updates `caller_info->stack_arg_cnt`. Only check_func_call() (pseudo-call to a real subprog) and check_kfunc_call() do that accounting. func#0's own arg_cnt is 1, so bpf_in_stack_arg_cnt(subprog 0) == 0 and subprog_info[0].stack_arg_cnt stays 0. At the end of a successful verification, bpf_check() calls bpf_fixup_call_args(), which computes for subprog 0: outgoing = subprog->stack_arg_cnt - bpf_in_stack_arg_cnt(subprog) = 0 - 0 = 0; if (subprog->max_out_stack_arg_cnt > outgoing) /* 4 > 0 */ verbose(env, "func#%d writes %u stack arg slots, but calls only require %u\n", ...); return -EINVAL; The program is refused with -EINVAL regardless of the new check. The four r11 stores are not optional: they are needed to get past check_outgoing_stack_args() which returns -EFAULT for uninitialized incoming callback slots, and push_callback_call() propagates -EFAULT. So any program shaped this way trips the pre-existing fixups check, and the shape that would actually load on a pre-patch kernel (and produce the KASAN OOB described in 9b1ccc65b120) additionally needs a same-frame pseudo-call to a >5-arg subprog so that subprog_info[0].stack_arg_cnt matches max_out_stack_arg_cnt. Additionally, discrimination is impossible as written: this is the only `__failure` case in the file with no `__msg()` (compare other failure cases in the file), and test_loader.c only asserts that bpf_object__load() returns any error. The new kernel check returns a bare -EINVAL with no verbose() output, so no __msg is even available. Could the test be strengthened to ensure it actually exercises the new check rather than a pre-existing one? For instance, by adding a same-frame pseudo-call to match stack_arg_cnt, or by adding a __msg() directive once the kernel patch includes a distinctive verbose() message? > + > SEC("tc") > __description("stack_arg: read from uninitialized stack arg slot") > __failure --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32068993990 ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-17 21:37 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-17 20:48 [PATCH v2 0/2] bpf: reject stack-argument callback subprograms Jérémy Jean 2026-08-17 20:48 ` [PATCH v2 1/2] " Jérémy Jean 2026-08-17 21:11 ` sashiko-bot 2026-08-17 21:37 ` bot+bpf-ci 2026-08-17 20:48 ` [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test Jérémy Jean 2026-08-17 21:12 ` sashiko-bot 2026-08-17 21:24 ` bot+bpf-ci
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.