[PATCH bpf-next v6 07/17] bpf: Support stack arguments for bpf functions

Yonghong Song
2026-04-19T16:33:56Z

Currently BPF functions (subprogs) are limited to 5 register arguments.
With [1], the compiler can emit code that passes additional arguments
via a dedicated stack area through bpf register BPF_REG_PARAMS (r11),
introduced in the previous patch.

The compiler uses positive r11 offsets for incoming (callee-side) args
and negative r11 offsets for outgoing (caller-side) args, following the
x86_64/arm64 calling convention direction. There is an 8-byte gap at
offset 0 separating the two regions:
Incoming (callee reads): r11+8 (arg6), r11+16 (arg7), ...
Outgoing (caller writes): r11-8 (arg6), r11-16 (arg7), ...

The following is an example to show how stack arguments are saved
and transferred between caller and callee:

int foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7) {
...
bar(a1, a2, a3, a4, a5, a6, a7, a8);
...
}

Caller (foo) Callee (bar)
============ ============
Incoming (positive offsets): Incoming (positive offsets):

r11+8: [incoming arg 6] r11+8: [incoming arg 6] <-+
r11+16: [incoming arg 7] r11+16: [incoming arg 7] <-|+
r11+24: [incoming arg 8] <-||+
Outgoing (negative offsets): |||
r11-8: [outgoing arg 6 to bar] -------->-------------------------+||
r11-16: [outgoing arg 7 to bar] -------->--------------------------+|
r11-24: [outgoing arg 8 to bar] -------->---------------------------+

If the bpf function has more than one call:

int foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7) {
...
bar1(a1, a2, a3, a4, a5, a6, a7, a8);
...
bar2(a1, a2, a3, a4, a5, a6, a7, a8, a9);
...
}

Caller (foo) Callee (bar2)
============ ==============
Incoming (positive offsets): Incoming (positive offsets):

r11+8: [incoming arg 6] r11+8: [incoming arg 6] <+
r11+16: [incoming arg 7] r11+16: [incoming arg 7] <|+
r11+24: [incoming arg 8] <||+
Outgoing for bar2 (negative offsets): r11+32: [incoming arg 9] <|||+
r11-8: [outgoing arg 6] ---->----------->-------------------------+|||
r11-16: [outgoing arg 7] ---->----------->--------------------------+||
r11-24: [outgoing arg 8] ---->----------->---------------------------+|
r11-32: [outgoing arg 9] ---->----------->----------------------------+

The verifier tracks stack arguments separately from the regular r10
stack. The stack_arg_regs are stored in bpf_func_state. This separation
keeps the stack arg area from interfering with the normal stack and
frame pointer (r10) bookkeeping. Similar to stacksafe(), introduce
stack_arg_safe() to do pruning check.

A per-state bitmask out_stack_arg_mask tracks which outgoing stack arg
slots have been written on the current path. Each bit corresponds to
an outgoing slot index (bit 0 = r11-8 = arg6, bit 1 = r11-16 = arg7,
etc.). At a call site, the verifier checks that all slots required by
the callee have their corresponding mask bits set. This enables
precise per-path tracking: if one branch of a conditional writes arg6
but another does not, the mask correctly reflects the difference and
the verifier rejects the uninitialized path. The mask is included in
stack_arg_safe() so that states with different sets of initialized
slots are not incorrectly pruned together.

Outgoing stack arg slots are invalidated after a call. This follows
native architecture calling convention where all function parameters
are caller saved. The following are a few examples.

Example 1:
*(u64 *)(r11 - 8) = r6;
*(u64 *)(r11 - 16) = r7;
call bar1; // arg6 = r6, arg7 = r7
call bar2; // expected with 2 stack arguments, failed

Example 2:
To fix the Example 1:
*(u64 *)(r11 - 8) = r6;
*(u64 *)(r11 - 16) = r7;
call bar1; // arg6 = r6, arg7 = r7
*(u64 *)(r11 - 8) = r8;
*(u64 *)(r11 - 16) = r9;
call bar2; // arg6 = r8, arg7 = r9

Example 3:
The compiler can hoist the shared stack arg stores above the branch:
*(u64 *)(r11 - 16) = r7;
...
if cond goto else;
*(u64 *)(r11 - 8) = r8;
call bar1; // arg6 = r8, arg7 = r7
goto end;
else:
*(u64 *)(r11 - 8) = r9;
call bar2; // arg6 = r9, arg7 = r7
end:

Example 4:
Within a loop:
loop:
*(u64 *)(r11 - 8) = r6; // arg6, before loop
call bar; // reuses arg6 each iteration
if ... goto loop;

A separate max_out_stack_arg_depth field in bpf_subprog_info tracks
the deepest outgoing offset actually written. This intends to
reject programs that write to offsets beyond what any callee expects.

Callback functions with stack arguments need kernel setup parameter
types (including stack parameters) properly and then callback function
can retrieve such information for verification purpose.

Global subprogs with >5 args are not yet supported.

[1] https://github.com/llvm/llvm-project/pull/189060

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
include/linux/bpf.h | 2 +
include/linux/bpf_verifier.h | 28 +++-
kernel/bpf/btf.c | 14 +-
kernel/bpf/fixups.c | 22 ++-
kernel/bpf/states.c | 41 +++++
kernel/bpf/verifier.c | 295 ++++++++++++++++++++++++++++++++++-
6 files changed, 390 insertions(+), 12 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index c2e614236e1c..8450c5dd1e20 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1669,6 +1669,8 @@ struct bpf_prog_aux {
u32 max_pkt_offset;
u32 max_tp_access;
u32 stack_depth;
+ u16 incoming_stack_arg_depth;
+ u16 stack_arg_depth; /* both incoming and max outgoing of stack arguments */
u32 id;
u32 func_cnt; /* used by non-func prog as the number of func progs */
u32 real_func_cnt; /* includes hidden progs, only used for JIT and freeing progs */
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 9fbbddc40d21..bb6d8cab3a35 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -372,6 +372,11 @@ struct bpf_func_state {
* `stack`. allocated_stack is always a multiple of BPF_REG_SIZE.
*/
int allocated_stack;
+
+ u16 stack_arg_depth; /* Size of incoming + max outgoing stack args in bytes. */
+ u16 incoming_stack_arg_depth; /* Size of incoming stack args in bytes. */
+ u16 out_stack_arg_mask; /* Bitmask of outgoing stack arg slots that have been written. */
+ struct bpf_reg_state *stack_arg_regs; /* On-stack arguments */
};

#define MAX_CALL_FRAMES 8
@@ -508,6 +513,17 @@ struct bpf_verifier_state {
iter < frame->allocated_stack / BPF_REG_SIZE; \
iter++, reg = bpf_get_spilled_reg(iter, frame, mask))

+#define bpf_get_spilled_stack_arg(slot, frame, mask) \
+ ((((slot) < frame->stack_arg_depth / BPF_REG_SIZE) && \
+ (frame->stack_arg_regs[slot].type != NOT_INIT)) \
+ ? &frame->stack_arg_regs[slot] : NULL)
+
+/* Iterate over 'frame', setting 'reg' to either NULL or a spilled stack arg. */
+#define bpf_for_each_spilled_stack_arg(iter, frame, reg, mask) \
+ for (iter = 0, reg = bpf_get_spilled_stack_arg(iter, frame, mask); \
+ iter < frame->stack_arg_depth / BPF_REG_SIZE; \
+ iter++, reg = bpf_get_spilled_stack_arg(iter, frame, mask))
+
#define bpf_for_each_reg_in_vstate_mask(__vst, __state, __reg, __mask, __expr) \
({ \
struct bpf_verifier_state *___vstate = __vst; \
@@ -525,6 +541,11 @@ struct bpf_verifier_state {
continue; \
(void)(__expr); \
} \
+ bpf_for_each_spilled_stack_arg(___j, __state, __reg, __mask) { \
+ if (!__reg) \
+ continue; \
+ (void)(__expr); \
+ } \
} \
})

@@ -739,10 +760,13 @@ struct bpf_subprog_info {
bool keep_fastcall_stack: 1;
bool changes_pkt_data: 1;
bool might_sleep: 1;
- u8 arg_cnt:3;
+ u8 arg_cnt:4;

enum priv_stack_mode priv_stack_mode;
- struct bpf_subprog_arg_info args[MAX_BPF_FUNC_REG_ARGS];
+ struct bpf_subprog_arg_info args[MAX_BPF_FUNC_ARGS];
+ u16 incoming_stack_arg_depth;
+ u16 outgoing_stack_arg_depth;
+ u16 max_out_stack_arg_depth;
};

struct bpf_verifier_env;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index a62d78581207..c5f3aa05d5a3 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7887,13 +7887,19 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
}
args = (const struct btf_param *)(t + 1);
nargs = btf_type_vlen(t);
- if (nargs > MAX_BPF_FUNC_REG_ARGS) {
- if (!is_global)
- return -EINVAL;
- bpf_log(log, "Global function %s() with %d > %d args. Buggy compiler.\n",
+ if (nargs > MAX_BPF_FUNC_ARGS) {
+ bpf_log(log, "Function %s() with %d > %d args not supported.\n",
+ tname, nargs, MAX_BPF_FUNC_ARGS);
+ return -EINVAL;
+ }
+ if (is_global && nargs > MAX_BPF_FUNC_REG_ARGS) {
+ bpf_log(log, "Global function %s() with %d > %d args not supported.\n",
tname, nargs, MAX_BPF_FUNC_REG_ARGS);
return -EINVAL;
}
+ if (nargs > MAX_BPF_FUNC_REG_ARGS)
+ sub->incoming_stack_arg_depth = (nargs - MAX_BPF_FUNC_REG_ARGS) * BPF_REG_SIZE;
+
/* check that function is void or returns int, exception cb also requires this */
t = btf_type_by_id(btf, t->type);
while (btf_type_is_modifier(t))
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index fba9e8c00878..c4e0224ad2f2 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -1123,6 +1123,9 @@ static int jit_subprogs(struct bpf_verifier_env *env)

func[i]->aux->name[0] = 'F';
func[i]->aux->stack_depth = env->subprog_info[i].stack_depth;
+ func[i]->aux->incoming_stack_arg_depth = env->subprog_info[i].incoming_stack_arg_depth;
+ func[i]->aux->stack_arg_depth = env->subprog_info[i].incoming_stack_arg_depth +
+ env->subprog_info[i].outgoing_stack_arg_depth;
if (env->subprog_info[i].priv_stack_mode == PRIV_STACK_ADAPTIVE)
func[i]->aux->jits_use_priv_stack = true;

@@ -1301,8 +1304,10 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env)
struct bpf_insn_aux_data *orig_insn_aux;
u32 *orig_subprog_starts;

- if (env->subprog_cnt <= 1)
+ if (env->subprog_cnt <= 1) {
+ env->prog->aux->stack_arg_depth = env->subprog_info[0].outgoing_stack_arg_depth;
return 0;
+ }

prog = orig_prog = env->prog;
if (bpf_prog_need_blind(prog)) {
@@ -1378,9 +1383,20 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env)
struct bpf_prog *prog = env->prog;
struct bpf_insn *insn = prog->insnsi;
bool has_kfunc_call = bpf_prog_has_kfunc_call(prog);
- int i, depth;
+ int depth;
#endif
- int err = 0;
+ int i, err = 0;
+
+ for (i = 0; i < env->subprog_cnt; i++) {
+ struct bpf_subprog_info *subprog = &env->subprog_info[i];
+
+ if (subprog->max_out_stack_arg_depth > subprog->outgoing_stack_arg_depth) {
+ verbose(env,
+ "func#%d writes stack arg slot at depth %u, but calls only require %u bytes\n",
+ i, subprog->max_out_stack_arg_depth, subprog->outgoing_stack_arg_depth);
+ return -EINVAL;
+ }
+ }

if (env->prog->jit_requested &&
!bpf_prog_is_offloaded(env->prog->aux)) {
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 8478d2c6ed5b..235841d23fe3 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -838,6 +838,44 @@ static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,
return true;
}

+/*
+ * Compare stack arg slots between old and current states.
+ * Outgoing stack args are path-local state and must agree for pruning.
+ */
+static bool stack_arg_safe(struct bpf_verifier_env *env, struct bpf_func_state *old,
+ struct bpf_func_state *cur, struct bpf_idmap *idmap,
+ enum exact_level exact)
+{
+ int i, nslots;
+
+ if (old->incoming_stack_arg_depth != cur->incoming_stack_arg_depth)
+ return false;
+
+ /* Compare both incoming and outgoing stack arg slots. */
+ if (old->stack_arg_depth != cur->stack_arg_depth)
+ return false;
+
+ if (old->out_stack_arg_mask != cur->out_stack_arg_mask)
+ return false;
+
+ nslots = old->stack_arg_depth / BPF_REG_SIZE;
+ for (i = 0; i < nslots; i++) {
+ struct bpf_reg_state *old_arg = &old->stack_arg_regs[i];
+ struct bpf_reg_state *cur_arg = &cur->stack_arg_regs[i];
+
+ if (old_arg->type == NOT_INIT && cur_arg->type == NOT_INIT)
+ continue;
+
+ if (exact == EXACT && old_arg->type != cur_arg->type)
+ return false;
+
+ if (!regsafe(env, old_arg, cur_arg, idmap, exact))
+ return false;
+ }
+
+ return true;
+}
+
static bool refsafe(struct bpf_verifier_state *old, struct bpf_verifier_state *cur,
struct bpf_idmap *idmap)
{
@@ -929,6 +967,9 @@ static bool func_states_equal(struct bpf_verifier_env *env, struct bpf_func_stat
if (!stacksafe(env, old, cur, &env->idmap_scratch, exact))
return false;

+ if (!stack_arg_safe(env, old, cur, &env->idmap_scratch, exact))
+ return false;
+
return true;
}

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6aa4dc161a56..78c9322870a5 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -1340,6 +1340,20 @@ static int copy_stack_state(struct bpf_func_state *dst, const struct bpf_func_st
return -ENOMEM;

dst->allocated_stack = src->allocated_stack;
+
+ /* copy stack args state */
+ n = src->stack_arg_depth / BPF_REG_SIZE;
+ if (n) {
+ dst->stack_arg_regs = copy_array(dst->stack_arg_regs, src->stack_arg_regs, n,
+ sizeof(struct bpf_reg_state),
+ GFP_KERNEL_ACCOUNT);
+ if (!dst->stack_arg_regs)
+ return -ENOMEM;
+ }
+
+ dst->stack_arg_depth = src->stack_arg_depth;
+ dst->incoming_stack_arg_depth = src->incoming_stack_arg_depth;
+ dst->out_stack_arg_mask = src->out_stack_arg_mask;
return 0;
}

@@ -1381,6 +1395,25 @@ static int grow_stack_state(struct bpf_verifier_env *env, struct bpf_func_state
return 0;
}

+static int grow_stack_arg_slots(struct bpf_verifier_env *env,
+ struct bpf_func_state *state, int size)
+{
+ size_t old_n = state->stack_arg_depth / BPF_REG_SIZE, n;
+
+ size = round_up(size, BPF_REG_SIZE);
+ n = size / BPF_REG_SIZE;
+ if (old_n >= n)
+ return 0;
+
+ state->stack_arg_regs = realloc_array(state->stack_arg_regs, old_n, n,
+ sizeof(struct bpf_reg_state));
+ if (!state->stack_arg_regs)
+ return -ENOMEM;
+
+ state->stack_arg_depth = size;
+ return 0;
+}
+
/* Acquire a pointer id from the env and update the state->refs to include
* this new pointer reference.
* On success, returns a valid pointer id to associate with the register
@@ -1543,6 +1576,7 @@ static void free_func_state(struct bpf_func_state *state)
{
if (!state)
return;
+ kfree(state->stack_arg_regs);
kfree(state->stack);
kfree(state);
}
@@ -4220,6 +4254,13 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
}
if (type == STACK_INVALID && env->allow_uninit_stack)
continue;
+ /*
+ * Cross-frame reads may hit slots poisoned by dead code elimination.
+ * Static liveness can't track indirect references through pointers,
+ * so allow the read conservatively.
+ */
+ if (type == STACK_POISON && reg_state != state)
+ continue;
if (type == STACK_POISON) {
verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
off, i, size);
@@ -4275,6 +4316,8 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
continue;
if (type == STACK_INVALID && env->allow_uninit_stack)
continue;
+ if (type == STACK_POISON && reg_state != state)
+ continue;
if (type == STACK_POISON) {
verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
off, i, size);
@@ -4429,6 +4472,123 @@ static int check_stack_write(struct bpf_verifier_env *env,
return err;
}

+/* Validate that a stack arg access is 8-byte sized and aligned. */
+static int check_stack_arg_access(struct bpf_verifier_env *env,
+ struct bpf_insn *insn, const char *op)
+{
+ int size = bpf_size_to_bytes(BPF_SIZE(insn->code));
+
+ if (size != BPF_REG_SIZE) {
+ verbose(env, "stack arg %s must be %d bytes, got %d\n",
+ op, BPF_REG_SIZE, size);
+ return -EINVAL;
+ }
+ if (insn->off == 0 || insn->off % BPF_REG_SIZE) {
+ verbose(env, "stack arg %s offset %d not aligned to %d\n",
+ op, insn->off, BPF_REG_SIZE);
+ return -EINVAL;
+ }
+ /* Reads use positive offsets (incoming), writes use negative (outgoing) */
+ if (op[0] == 'r' && insn->off <= 0) {
+ verbose(env, "stack arg read must use positive offset, got %d\n",
+ insn->off);
+ return -EINVAL;
+ }
+ if (op[0] == 'w' && insn->off >= 0) {
+ verbose(env, "stack arg write must use negative offset, got %d\n",
+ insn->off);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int out_arg_idx_from_off(int off)
+{
+ return -off / BPF_REG_SIZE - 1;
+}
+
+static int out_arg_spi(const struct bpf_func_state *state, int idx)
+{
+ return state->incoming_stack_arg_depth / BPF_REG_SIZE + idx;
+}
+
+static u16 out_arg_req_mask(int nr_stack_arg_regs)
+{
+ return nr_stack_arg_regs ? (1U << nr_stack_arg_regs) - 1 : 0;
+}
+
+/*
+ * Write a value to the outgoing stack arg area.
+ * off is a negative offset from r11 (e.g. -8 for arg6, -16 for arg7).
+ * Callers ensure off < 0, 8-byte aligned, and size is BPF_REG_SIZE.
+ */
+static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_state *state,
+ int off, int value_regno)
+{
+ int max_stack_arg_regs = MAX_BPF_FUNC_ARGS - MAX_BPF_FUNC_REG_ARGS;
+ int idx = out_arg_idx_from_off(off);
+ int spi = out_arg_spi(state, idx);
+ struct bpf_subprog_info *subprog;
+ struct bpf_func_state *cur;
+ int err;
+
+ if (idx >= max_stack_arg_regs) {
+ verbose(env, "stack arg write offset %d exceeds max %d stack args\n",
+ off, max_stack_arg_regs);
+ return -EINVAL;
+ }
+
+ err = grow_stack_arg_slots(env, state, state->incoming_stack_arg_depth + (-off));
+ if (err)
+ return err;
+
+ /* Track the max outgoing stack arg access depth. */
+ subprog = &env->subprog_info[state->subprogno];
+ if (-off > subprog->max_out_stack_arg_depth)
+ subprog->max_out_stack_arg_depth = -off;
+
+ cur = env->cur_state->frame[env->cur_state->curframe];
+ if (value_regno >= 0) {
+ state->stack_arg_regs[spi] = cur->regs[value_regno];
+ } else {
+ /* BPF_ST: store immediate, treat as scalar */
+ struct bpf_reg_state *arg = &state->stack_arg_regs[spi];
+
+ arg->type = SCALAR_VALUE;
+ __mark_reg_known(arg, env->prog->insnsi[env->insn_idx].imm);
+ }
+ state->out_stack_arg_mask |= BIT(idx);
+ return 0;
+}
+
+/*
+ * Read a value from the incoming stack arg area.
+ * off is a positive offset from r11 (e.g. +8 for arg6, +16 for arg7).
+ * Callers ensure off > 0, 8-byte aligned, and size is BPF_REG_SIZE.
+ */
+static int check_stack_arg_read(struct bpf_verifier_env *env, struct bpf_func_state *state,
+ int off, int dst_regno)
+{
+ int spi = off / BPF_REG_SIZE - 1;
+ struct bpf_func_state *cur;
+ struct bpf_reg_state *arg;
+
+ if (off > state->incoming_stack_arg_depth) {
+ verbose(env, "invalid read from stack arg off %d depth %d\n",
+ off, state->incoming_stack_arg_depth);
+ return -EACCES;
+ }
+
+ arg = &state->stack_arg_regs[spi];
+ cur = env->cur_state->frame[env->cur_state->curframe];
+
+ if (is_spillable_regtype(arg->type))
+ copy_register_state(&cur->regs[dst_regno], arg);
+ else
+ mark_reg_unknown(env, cur->regs, dst_regno);
+ return 0;
+}
+
static int check_map_access_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg, u32 argno,
int off, int size, enum bpf_access_type type)
{
@@ -6614,10 +6774,23 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
bool strict_alignment_once, bool is_ldsx,
bool allow_trust_mismatch, const char *ctx)
{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ struct bpf_func_state *state = vstate->frame[vstate->curframe];
struct bpf_reg_state *regs = cur_regs(env);
enum bpf_reg_type src_reg_type;
int err;

+ /* Handle stack arg access */
+ if (insn->src_reg == BPF_REG_PARAMS) {
+ err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK);
+ if (err)
+ return err;
+ err = check_stack_arg_access(env, insn, "read");
+ if (err)
+ return err;
+ return check_stack_arg_read(env, state, insn->off, insn->dst_reg);
+ }
+
/* check src operand */
err = check_reg_arg(env, insn->src_reg, SRC_OP);
if (err)
@@ -6646,10 +6819,23 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
static int check_store_reg(struct bpf_verifier_env *env, struct bpf_insn *insn,
bool strict_alignment_once)
{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ struct bpf_func_state *state = vstate->frame[vstate->curframe];
struct bpf_reg_state *regs = cur_regs(env);
enum bpf_reg_type dst_reg_type;
int err;

+ /* Handle stack arg write */
+ if (insn->dst_reg == BPF_REG_PARAMS) {
+ err = check_reg_arg(env, insn->src_reg, SRC_OP);
+ if (err)
+ return err;
+ err = check_stack_arg_access(env, insn, "write");
+ if (err)
+ return err;
+ return check_stack_arg_write(env, state, insn->off, insn->src_reg);
+ }
+
/* check src1 operand */
err = check_reg_arg(env, insn->src_reg, SRC_OP);
if (err)
@@ -9278,6 +9464,16 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
}
}

+static void invalidate_outgoing_stack_args(struct bpf_func_state *state)
+{
+ int i, outgoing_start = state->incoming_stack_arg_depth / BPF_REG_SIZE;
+ int nslots = state->stack_arg_depth / BPF_REG_SIZE;
+
+ for (i = outgoing_start; i < nslots; i++)
+ state->stack_arg_regs[i].type = NOT_INIT;
+ state->out_stack_arg_mask = 0;
+}
+
typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,
struct bpf_func_state *caller,
struct bpf_func_state *callee,
@@ -9335,6 +9531,20 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls
return err;
}

+static struct bpf_reg_state *get_func_arg_reg(struct bpf_verifier_env *env,
+ struct bpf_reg_state *regs, int argno)
+{
+ struct bpf_func_state *caller;
+ int spi;
+
+ if (argno < MAX_BPF_FUNC_REG_ARGS)
+ return &regs[argno + 1];
+
+ caller = cur_func(env);
+ spi = out_arg_spi(caller, argno - MAX_BPF_FUNC_REG_ARGS);
+ return &caller->stack_arg_regs[spi];
+}
+
static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
const struct btf *btf,
struct bpf_reg_state *regs)
@@ -9353,8 +9563,24 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
*/
for (i = 0; i < sub->arg_cnt; i++) {
u32 argno = make_argno(i);
- u32 regno = i + 1;
- struct bpf_reg_state *reg = &regs[regno];
+ struct bpf_reg_state *reg;
+
+ if (i >= MAX_BPF_FUNC_REG_ARGS) {
+ struct bpf_func_state *caller = cur_func(env);
+ int spi = out_arg_spi(caller, i - MAX_BPF_FUNC_REG_ARGS);
+
+ /*
+ * The compiler may constant-fold stack arg values into the
+ * callee, eliminating the r11 stores. The BTF still declares
+ * these parameters, but no outgoing stack slots exist.
+ */
+ if (spi >= (caller->stack_arg_depth / BPF_REG_SIZE)) {
+ verbose(env, "stack %s not found in caller state\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ }
+ reg = get_func_arg_reg(env, regs, i);
struct bpf_subprog_arg_info *arg = &sub->args[i];

if (arg->arg_type == ARG_ANYTHING) {
@@ -9542,8 +9768,10 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
int *insn_idx)
{
struct bpf_verifier_state *state = env->cur_state;
+ struct bpf_subprog_info *caller_info;
struct bpf_func_state *caller;
int err, subprog, target_insn;
+ u16 callee_incoming;

target_insn = *insn_idx + insn->imm + 1;
subprog = bpf_find_subprog(env, target_insn);
@@ -9595,6 +9823,15 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
}

+ /*
+ * Track caller's outgoing stack arg depth (max across all callees).
+ * This is needed so the JIT knows how much stack arg space to allocate.
+ */
+ caller_info = &env->subprog_info[caller->subprogno];
+ callee_incoming = env->subprog_info[subprog].incoming_stack_arg_depth;
+ if (callee_incoming > caller_info->outgoing_stack_arg_depth)
+ caller_info->outgoing_stack_arg_depth = callee_incoming;
+
/* for regular function entry setup new frame and continue
* from that frame.
*/
@@ -9603,6 +9840,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return err;

clear_caller_saved_regs(env, caller->regs);
+ invalidate_outgoing_stack_args(caller);

/* and go analyze first insn of the callee */
*insn_idx = env->subprog_info[subprog].start - 1;
@@ -9648,6 +9886,7 @@ static int set_callee_state(struct bpf_verifier_env *env,
struct bpf_func_state *caller,
struct bpf_func_state *callee, int insn_idx)
{
+ struct bpf_subprog_info *callee_info;
int i;

/* copy r1 - r5 args that callee can access. The copy includes parent
@@ -9655,6 +9894,45 @@ static int set_callee_state(struct bpf_verifier_env *env,
*/
for (i = BPF_REG_1; i <= BPF_REG_5; i++)
callee->regs[i] = caller->regs[i];
+
+ /*
+ * Transfer stack args from caller's outgoing area to callee's incoming
+ * area.
+ *
+ * Caller stores outgoing args at negative r11 offsets: -8 (arg6),
+ * -16 (arg7), -24 (arg8), ... In the caller's slot array, outgoing
+ * spi 0 is arg6, spi 1 is arg7, and so on.
+ *
+ * Callee reads incoming args at positive r11 offsets: +8 (arg6),
+ * +16 (arg7), ... Incoming spi 0 is arg6.
+ */
+ callee_info = &env->subprog_info[callee->subprogno];
+ if (callee_info->incoming_stack_arg_depth) {
+ int callee_incoming_slots = callee_info->incoming_stack_arg_depth / BPF_REG_SIZE;
+ u16 req_mask = out_arg_req_mask(callee_incoming_slots);
+ int err, caller_spi;
+
+ callee->incoming_stack_arg_depth = callee_info->incoming_stack_arg_depth;
+ err = grow_stack_arg_slots(env, callee, callee_info->incoming_stack_arg_depth);
+ if (err)
+ return err;
+
+ if ((caller->out_stack_arg_mask & req_mask) != req_mask) {
+ for (i = 0; i < callee_incoming_slots; i++) {
+ if (caller->out_stack_arg_mask & BIT(i))
+ continue;
+ verbose(env, "stack arg#%d not properly initialized\n",
+ i + MAX_BPF_FUNC_REG_ARGS + 1);
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; i < callee_incoming_slots; i++) {
+ caller_spi = out_arg_spi(caller, i);
+ callee->stack_arg_regs[i] = caller->stack_arg_regs[caller_spi];
+ }
+ }
+
return 0;
}

@@ -17659,6 +17937,17 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
return check_store_reg(env, insn, false);

case BPF_ST: {
+ /* Handle stack arg write (store immediate) */
+ if (insn->dst_reg == BPF_REG_PARAMS) {
+ struct bpf_verifier_state *vstate = env->cur_state;
+ struct bpf_func_state *state = vstate->frame[vstate->curframe];
+
+ err = check_stack_arg_access(env, insn, "write");
+ if (err)
+ return err;
+ return check_stack_arg_write(env, state, insn->off, -1);
+ }
+
enum bpf_reg_type dst_reg_type;

err = check_reg_arg(env, insn->dst_reg, SRC_OP);
@@ -18808,7 +19097,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
goto out;
}
}
- for (i = BPF_REG_1; i <= sub->arg_cnt; i++) {
+ for (i = BPF_REG_1; i <= min_t(u32, sub->arg_cnt, MAX_BPF_FUNC_REG_ARGS); i++) {
arg = &sub->args[i - BPF_REG_1];
reg = &regs[i];

--
2.52.0