Re: [PATCH v2 2/2] selftests/bpf: add callback stack-argument rejection test

[email protected]
Newsgroups org.kernel.vger.linux-kselftest,org.kernel.vger.bpf,org.kernel.vger.linux-kernel
Message-ID <43ebb3103b9f3c2ab8e5323a70262d80679c84389aebdf9980ec99217ead1271@mail.kernel.org>
> 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.