Re: [PATCH bpf-next 10/18] bpf,x86: Implement JIT support for stack arguments

bot+bpf-ci
2026-04-24T18:00:42Z

[-- Attachment #1: Type: text/plain, Size: 3988 bytes --]

> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index ea9e707e8abf..06f33fef1e8e 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c

[ ... ]

> @@ -1664,16 +1692,45 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
> int i, excnt = 0;
> int ilen, proglen = 0;
> u8 *ip, *prog = temp;
> + u16 stack_arg_depth, incoming_stack_arg_depth, outgoing_stack_arg_depth;
> + u16 outgoing_rsp;
> u32 stack_depth;
> + int callee_saved_size;
> + s32 outgoing_arg_base;
> int err;
>
> stack_depth = bpf_prog->aux->stack_depth;
> + stack_arg_depth = bpf_prog->aux->stack_arg_depth;
> + incoming_stack_arg_depth = bpf_prog->aux->incoming_stack_arg_depth;
> + outgoing_stack_arg_depth = stack_arg_depth - incoming_stack_arg_depth;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Can this underflow? All three variables are u16, so the subtraction
wraps around modulo 65536.

Looking at btf_prepare_func_args() in kernel/bpf/btf.c, it sets
'sub->incoming_stack_arg_depth = (nargs - MAX_BPF_FUNC_REG_ARGS) *
BPF_REG_SIZE' for any static subprog with more than 5 arguments. It does
not initialize sub->stack_arg_depth.

The 'stack_arg_depth' field is only written by check_func_call() in
kernel/bpf/verifier.c and check_kfunc_call(), which update the *caller's*
subprog entry when the caller makes a call with more than 5 arguments.

Since env->subprog_info[] is zero-initialized (kvcalloc), leaf subprograms
that receive stack arguments but never call other functions with stack
arguments will have:
incoming_stack_arg_depth = (nargs - 5) * 8
stack_arg_depth = 0

For example:
static int leaf(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
{
return a1 + a7; /* r11+16 load, then BPF_EXIT - no calls */
}

/* some other subprog does: ret = leaf(1,2,3,4,5,6,7); */

For 'leaf', the verifier sets:
incoming_stack_arg_depth = (7 - 5) * 8 = 16
stack_arg_depth = 0 (never updated, no calls made)

When do_jit() computes:
outgoing_stack_arg_depth = 0 - 16 = 0xFFF0 (u16 underflow)

Then at the new code below:

> + /*
> + * Allocate outgoing stack arg area for args 7+ only.
> + * Arg 6 goes into r9 register, not on stack.
> + */
> + outgoing_rsp = outgoing_stack_arg_depth > 8 ? outgoing_stack_arg_depth - 8 : 0;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> + emit_sub_rsp(&prog, outgoing_rsp);

This emits 'sub rsp, 0xFFE8' (about 65 KB), which overflows the kernel
stack (THREAD_SIZE is only 16 KB on x86_64). The epilogue then emits a
matching 'add rsp, 0xFFE8', but the damage is already done at function
entry.

The commit message states "The verifier guarantees that neither
tail_call_reachable nor priv_stack is set when stack args exist, so R9 is
always available." It does not claim the verifier maintains
'stack_arg_depth >= incoming_stack_arg_depth', and that invariant is not
maintained for leaf subprograms.

The sanity check in bpf_fixup_call_args() at kernel/bpf/fixups.c does not
catch this because the same underflow happens there:
u16 outgoing = subprog->stack_arg_depth - subprog->incoming_stack_arg_depth;
but since leaf subprograms have max_out_stack_arg_depth == 0, the
comparison 'outgoing > max_out_stack_arg_depth' is false (0xFFF0 wraps
around in the comparison).

Should the JIT compute outgoing_stack_arg_depth safely to prevent the
underflow? For example:
outgoing_stack_arg_depth = stack_arg_depth > incoming_stack_arg_depth ?
stack_arg_depth - incoming_stack_arg_depth : 0;

Or should the verifier ensure the invariant 'stack_arg_depth >=
incoming_stack_arg_depth' after verification completes?

---
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/24902767240