* [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
* [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 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 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
* 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
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.