The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [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; 6+ 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] 6+ 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:37   ` bot+bpf-ci
  2026-08-18 15:23   ` Yonghong Song
  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; 6+ 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] 6+ 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:24   ` bot+bpf-ci
  1 sibling, 1 reply; 6+ 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] 6+ 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:24   ` bot+bpf-ci
  0 siblings, 0 replies; 6+ 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] 6+ 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:37   ` bot+bpf-ci
  2026-08-18 15:23   ` Yonghong Song
  1 sibling, 0 replies; 6+ 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] 6+ 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:37   ` bot+bpf-ci
@ 2026-08-18 15:23   ` Yonghong Song
  1 sibling, 0 replies; 6+ messages in thread
From: Yonghong Song @ 2026-08-18 15:23 UTC (permalink / raw)
  To: Jérémy Jean, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: bpf, linux-kernel, linux-kselftest



On 8/17/26 1:48 PM, Jérémy Jean wrote:
> 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;

This is not good as user will not know why it failed. Your v1 does have an error message.

But this is not needed. Without above verifer.c change, user will get an error message:
    func#0 writes 4 stack arg slots, but calls only require 0

NACK, see my v1 comment: https://lore.kernel.org/bpf/14a7e7c2-36f7-4aa2-9b20-cc54700a9f1b@linux.dev/

>   
>   	/* set_callee_state is used for direct subprog calls, but we are
>   	 * interested in validating only BPF helpers that can call subprogs as


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-08-18 15:23 UTC | newest]

Thread overview: 6+ 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:37   ` bot+bpf-ci
2026-08-18 15:23   ` Yonghong Song
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:24   ` bot+bpf-ci

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox