mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
Merge branch 'bpf-support-stack-arguments-for-bpf-functions-and-kfuncs'
Yonghong Song says: ==================== bpf: Support stack arguments for BPF functions and kfuncs Currently, bpf function calls and kfunc's are limited by 5 reg-level parameters. For function calls with more than 5 parameters, developers can use always inlining or pass a struct pointer after packing more parameters in that struct although it may have some inconvenience. But there is no workaround for kfunc if more than 5 parameters is needed. This patch set lifts the 5-argument limit by introducing stack-based argument passing for BPF functions and kfunc's, coordinated with compiler support in LLVM [1]. The compiler emits loads/stores through a new bpf register r11 (BPF_REG_PARAMS), to pass arguments beyond the 5th, keeping the stack arg area separate from the r10-based program stack. The current maximum number of arguments is capped at MAX_BPF_FUNC_ARGS (12), which is sufficient for the vast majority of use cases. All kfunc/bpf-function arguments are caller saved, including stack arguments. For register arguments (r1-r5), the verifier already marks them as clobbered after each call. For stack arguments, the verifier invalidates all outgoing stack arg slots immediately after a call, requiring the compiler to re-store them before any subsequent call. This follows the native calling convention where all function parameters are caller saved. The x86_64 JIT translates r11-relative accesses to RBP-relative native instructions. Each function's stack allocation is extended by 'max_outgoing' bytes to hold the outgoing arg area below the callee-saved registers. This makes implementation easier as the r10 can be reused for stack argument access. At both BPF-to-BPF and kfunc calls, outgoing args are pushed onto the expected calling convention locations directly. The incoming parameters can directly get the value from caller. Global subprogs and freplace progs with >5 args are not yet supported. Only x86_64 and arm64 are supported for now. Same selftests are tested by both x86_64 and arm64. Please see each individual patch for details. [1] https://github.com/llvm/llvm-project/pull/189060 Changelogs: v3 -> v4: - v3: https://lore.kernel.org/bpf/20260511053301.1878610-1-yonghong.song@linux.dev/ - Added no_stack_arg_load comparison in func_states_equal() to ensure correctness of pruning. - Shrink bpf_jmp_history_entry.flags to 4bit to match the number of flags. - Instead of passing bpf_subprog_info to JIT, use prog->aux->func_idx to find corresponding bpf_subprog_info from 'env'. - For patch 'bpf: Reject stack arguments if tail call reachable', use stack_arg_cnt instead of just incoming stack arg cnt. - Tighten invalidate_outgoing_stack_args() for kfunc/helper/bpf-to-bpf calls. - Disable private stack in verifier for x86_64 instead of in JIT. v2 -> v3: - v2: https://lore.kernel.org/bpf/20260507212942.1122000-1-yonghong.song@linux.dev/ - In do_check_common() and for main prog, if btf does not match with actual parameter, the verification will continue and will ignore arg_cnt. Make arg_cnt=1 explictly to prevent any incoming stack arguments. - Remove the loop which clear current frame stack slot and set the upper level frame stack slot. This is not needed unless there is a bug. Add a verifier_bug if the bug happens. - For liveness, avoid r11 based load/stores mixing with r10 based stack tracking. Also, print out stack arguments properly. - Pass bpf_subprog_info the JIT so we can avoid copy bpf_subprog_info fields to bpf_prog_aux. - Fix the missed allocation free for test infra BTF fixup. - Remove selftest result for precision backtracking test since the result would be change (two possible output). v1 -> v2: - v1: https://lore.kernel.org/bpf/20260424171433.2034470-1-yonghong.song@linux.dev/ - Several refactoring (convert bpf_get_spilled_reg macro to static inline func, Remove copy_register_state(), Refactor jmp history, Refactor record_call_access(), etc), suggested by Eduard. - Use incoming_stack_arg_cnt/stack_arg_cnt instead of incoming_stack_arg_depth/stack_arg_depth, suggested by Eduard. - Fix a stack arg pruning bug, from Eduard. - Fix a bug for precision marking and backtracking, basically callee needs to get the stack arg value from callers, helped from Eduard. - Set sub->arg_cnt earlier in btf_prepare_func_args(), this will avoid having incoming_stack_arg_cnt in bpf_subprog_info. - Do stack-arg liveness analysis together with r10 based liveness analysis, suggested by Eduard. - Fix a few tests to ensure that r11-based loads cannot be ahead of r11-based stores, and r11-based loads cannot be after kfunc/helper/bpf-function. ==================== Link: https://patch.msgid.link/20260513044949.2382019-1-yonghong.song@linux.dev Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
cd59fa185a
|
|
@ -47,7 +47,7 @@
|
|||
/* Map BPF registers to A64 registers */
|
||||
static const int bpf2a64[] = {
|
||||
/* return value from in-kernel function, and exit value from eBPF */
|
||||
[BPF_REG_0] = A64_R(7),
|
||||
[BPF_REG_0] = A64_R(8),
|
||||
/* arguments from eBPF program to in-kernel function */
|
||||
[BPF_REG_1] = A64_R(0),
|
||||
[BPF_REG_2] = A64_R(1),
|
||||
|
|
@ -86,6 +86,7 @@ struct jit_ctx {
|
|||
__le32 *image;
|
||||
__le32 *ro_image;
|
||||
u32 stack_size;
|
||||
u16 stack_arg_size;
|
||||
u64 user_vm_start;
|
||||
u64 arena_vm_start;
|
||||
bool fp_used;
|
||||
|
|
@ -533,13 +534,19 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf)
|
|||
* | |
|
||||
* +-----+ <= (BPF_FP - prog->aux->stack_depth)
|
||||
* |RSVD | padding
|
||||
* current A64_SP => +-----+ <= (BPF_FP - ctx->stack_size)
|
||||
* +-----+ <= (BPF_FP - ctx->stack_size)
|
||||
* | |
|
||||
* | ... | outgoing stack args (9+, if any)
|
||||
* | |
|
||||
* current A64_SP => +-----+
|
||||
* | |
|
||||
* | ... | Function call stack
|
||||
* | |
|
||||
* +-----+
|
||||
* low
|
||||
*
|
||||
* Stack args 6-8 are passed in x5-x7, args 9+ at [SP].
|
||||
* Incoming args 9+ are at [FP + 16], [FP + 24], ...
|
||||
*/
|
||||
|
||||
emit_kcfi(is_main_prog ? cfi_bpf_hash : cfi_bpf_subprog_hash, ctx);
|
||||
|
|
@ -613,6 +620,9 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf)
|
|||
if (ctx->stack_size && !ctx->priv_sp_used)
|
||||
emit(A64_SUB_I(1, A64_SP, A64_SP, ctx->stack_size), ctx);
|
||||
|
||||
if (ctx->stack_arg_size)
|
||||
emit(A64_SUB_I(1, A64_SP, A64_SP, ctx->stack_arg_size), ctx);
|
||||
|
||||
if (ctx->arena_vm_start)
|
||||
emit_a64_mov_i64(arena_vm_base, ctx->arena_vm_start, ctx);
|
||||
|
||||
|
|
@ -673,6 +683,9 @@ static int emit_bpf_tail_call(struct jit_ctx *ctx)
|
|||
/* Update tail_call_cnt if the slot is populated. */
|
||||
emit(A64_STR64I(tcc, ptr, 0), ctx);
|
||||
|
||||
if (ctx->stack_arg_size)
|
||||
emit(A64_ADD_I(1, A64_SP, A64_SP, ctx->stack_arg_size), ctx);
|
||||
|
||||
/* restore SP */
|
||||
if (ctx->stack_size && !ctx->priv_sp_used)
|
||||
emit(A64_ADD_I(1, A64_SP, A64_SP, ctx->stack_size), ctx);
|
||||
|
|
@ -1034,6 +1047,9 @@ static void build_epilogue(struct jit_ctx *ctx, bool was_classic)
|
|||
const u8 r0 = bpf2a64[BPF_REG_0];
|
||||
const u8 ptr = bpf2a64[TCCNT_PTR];
|
||||
|
||||
if (ctx->stack_arg_size)
|
||||
emit(A64_ADD_I(1, A64_SP, A64_SP, ctx->stack_arg_size), ctx);
|
||||
|
||||
/* We're done with BPF stack */
|
||||
if (ctx->stack_size && !ctx->priv_sp_used)
|
||||
emit(A64_ADD_I(1, A64_SP, A64_SP, ctx->stack_size), ctx);
|
||||
|
|
@ -1048,7 +1064,7 @@ static void build_epilogue(struct jit_ctx *ctx, bool was_classic)
|
|||
/* Restore FP/LR registers */
|
||||
emit(A64_POP(A64_FP, A64_LR, A64_SP), ctx);
|
||||
|
||||
/* Move the return value from bpf:r0 (aka x7) to x0 */
|
||||
/* Move the return value from bpf:r0 (aka x8) to x0 */
|
||||
emit(A64_MOV(1, A64_R(0), r0), ctx);
|
||||
|
||||
/* Authenticate lr */
|
||||
|
|
@ -1191,6 +1207,41 @@ static int add_exception_handler(const struct bpf_insn *insn,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const u8 stack_arg_reg[] = { A64_R(5), A64_R(6), A64_R(7) };
|
||||
|
||||
#define NR_STACK_ARG_REGS ARRAY_SIZE(stack_arg_reg)
|
||||
|
||||
static void emit_stack_arg_load(u8 dst, s16 bpf_off, struct jit_ctx *ctx)
|
||||
{
|
||||
int idx = bpf_off / sizeof(u64) - 1;
|
||||
|
||||
if (idx < NR_STACK_ARG_REGS)
|
||||
emit(A64_MOV(1, dst, stack_arg_reg[idx]), ctx);
|
||||
else
|
||||
emit(A64_LDR64I(dst, A64_FP, (idx - NR_STACK_ARG_REGS) * sizeof(u64) + 16), ctx);
|
||||
}
|
||||
|
||||
static void emit_stack_arg_store(u8 src_a64, s16 bpf_off, struct jit_ctx *ctx)
|
||||
{
|
||||
int idx = -bpf_off / sizeof(u64) - 1;
|
||||
|
||||
if (idx < NR_STACK_ARG_REGS)
|
||||
emit(A64_MOV(1, stack_arg_reg[idx], src_a64), ctx);
|
||||
else
|
||||
emit(A64_STR64I(src_a64, A64_SP, (idx - NR_STACK_ARG_REGS) * sizeof(u64)), ctx);
|
||||
}
|
||||
|
||||
static void emit_stack_arg_store_imm(s32 imm, s16 bpf_off, const u8 tmp, struct jit_ctx *ctx)
|
||||
{
|
||||
int idx = -bpf_off / sizeof(u64) - 1;
|
||||
|
||||
emit_a64_mov_i(1, tmp, imm, ctx);
|
||||
if (idx < NR_STACK_ARG_REGS)
|
||||
emit(A64_MOV(1, stack_arg_reg[idx], tmp), ctx);
|
||||
else
|
||||
emit(A64_STR64I(tmp, A64_SP, (idx - NR_STACK_ARG_REGS) * sizeof(u64)), ctx);
|
||||
}
|
||||
|
||||
/* JITs an eBPF instruction.
|
||||
* Returns:
|
||||
* 0 - successfully JITed an 8-byte eBPF instruction.
|
||||
|
|
@ -1646,6 +1697,11 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
|
|||
case BPF_LDX | BPF_MEM | BPF_H:
|
||||
case BPF_LDX | BPF_MEM | BPF_B:
|
||||
case BPF_LDX | BPF_MEM | BPF_DW:
|
||||
if (insn->src_reg == BPF_REG_PARAMS) {
|
||||
emit_stack_arg_load(dst, off, ctx);
|
||||
break;
|
||||
}
|
||||
fallthrough;
|
||||
case BPF_LDX | BPF_PROBE_MEM | BPF_DW:
|
||||
case BPF_LDX | BPF_PROBE_MEM | BPF_W:
|
||||
case BPF_LDX | BPF_PROBE_MEM | BPF_H:
|
||||
|
|
@ -1672,6 +1728,8 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
|
|||
if (src == fp) {
|
||||
src_adj = ctx->priv_sp_used ? priv_sp : A64_SP;
|
||||
off_adj = off + ctx->stack_size;
|
||||
if (!ctx->priv_sp_used)
|
||||
off_adj += ctx->stack_arg_size;
|
||||
} else {
|
||||
src_adj = src;
|
||||
off_adj = off;
|
||||
|
|
@ -1752,6 +1810,11 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
|
|||
case BPF_ST | BPF_MEM | BPF_H:
|
||||
case BPF_ST | BPF_MEM | BPF_B:
|
||||
case BPF_ST | BPF_MEM | BPF_DW:
|
||||
if (insn->dst_reg == BPF_REG_PARAMS) {
|
||||
emit_stack_arg_store_imm(imm, off, tmp, ctx);
|
||||
break;
|
||||
}
|
||||
fallthrough;
|
||||
case BPF_ST | BPF_PROBE_MEM32 | BPF_B:
|
||||
case BPF_ST | BPF_PROBE_MEM32 | BPF_H:
|
||||
case BPF_ST | BPF_PROBE_MEM32 | BPF_W:
|
||||
|
|
@ -1763,6 +1826,8 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
|
|||
if (dst == fp) {
|
||||
dst_adj = ctx->priv_sp_used ? priv_sp : A64_SP;
|
||||
off_adj = off + ctx->stack_size;
|
||||
if (!ctx->priv_sp_used)
|
||||
off_adj += ctx->stack_arg_size;
|
||||
} else {
|
||||
dst_adj = dst;
|
||||
off_adj = off;
|
||||
|
|
@ -1814,6 +1879,11 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
|
|||
case BPF_STX | BPF_MEM | BPF_H:
|
||||
case BPF_STX | BPF_MEM | BPF_B:
|
||||
case BPF_STX | BPF_MEM | BPF_DW:
|
||||
if (insn->dst_reg == BPF_REG_PARAMS) {
|
||||
emit_stack_arg_store(src, off, ctx);
|
||||
break;
|
||||
}
|
||||
fallthrough;
|
||||
case BPF_STX | BPF_PROBE_MEM32 | BPF_B:
|
||||
case BPF_STX | BPF_PROBE_MEM32 | BPF_H:
|
||||
case BPF_STX | BPF_PROBE_MEM32 | BPF_W:
|
||||
|
|
@ -1825,6 +1895,8 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
|
|||
if (dst == fp) {
|
||||
dst_adj = ctx->priv_sp_used ? priv_sp : A64_SP;
|
||||
off_adj = off + ctx->stack_size;
|
||||
if (!ctx->priv_sp_used)
|
||||
off_adj += ctx->stack_arg_size;
|
||||
} else {
|
||||
dst_adj = dst;
|
||||
off_adj = off;
|
||||
|
|
@ -2018,6 +2090,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
|
|||
u8 *ro_image_ptr;
|
||||
int body_idx;
|
||||
int exentry_idx;
|
||||
int out_cnt;
|
||||
|
||||
if (!prog->jit_requested)
|
||||
return prog;
|
||||
|
|
@ -2065,6 +2138,14 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
|
|||
ctx.user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena);
|
||||
ctx.arena_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena);
|
||||
|
||||
out_cnt = bpf_out_stack_arg_cnt(env, prog);
|
||||
if (out_cnt) {
|
||||
int nr_on_stack = out_cnt - NR_STACK_ARG_REGS;
|
||||
|
||||
if (nr_on_stack > 0)
|
||||
ctx.stack_arg_size = round_up(nr_on_stack * sizeof(u64), 16);
|
||||
}
|
||||
|
||||
if (priv_stack_ptr)
|
||||
ctx.priv_sp_used = true;
|
||||
|
||||
|
|
@ -2229,6 +2310,11 @@ bool bpf_jit_supports_kfunc_call(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool bpf_jit_supports_stack_args(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void *bpf_arch_text_copy(void *dst, void *src, size_t len)
|
||||
{
|
||||
if (!aarch64_insn_copy(dst, src, len))
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ SYM_FUNC_START(arch_bpf_timed_may_goto)
|
|||
stp x29, x30, [sp, #-64]!
|
||||
mov x29, sp
|
||||
|
||||
/* Save BPF registers R0 - R5 (x7, x0-x4)*/
|
||||
stp x7, x0, [sp, #16]
|
||||
/* Save BPF registers R0 - R5 (x8, x0-x4)*/
|
||||
stp x8, x0, [sp, #16]
|
||||
stp x1, x2, [sp, #32]
|
||||
stp x3, x4, [sp, #48]
|
||||
|
||||
|
|
@ -28,8 +28,8 @@ SYM_FUNC_START(arch_bpf_timed_may_goto)
|
|||
/* BPF_REG_AX(x9) will be stored into count, so move return value to it. */
|
||||
mov x9, x0
|
||||
|
||||
/* Restore BPF registers R0 - R5 (x7, x0-x4) */
|
||||
ldp x7, x0, [sp, #16]
|
||||
/* Restore BPF registers R0 - R5 (x8, x0-x4) */
|
||||
ldp x8, x0, [sp, #16]
|
||||
ldp x1, x2, [sp, #32]
|
||||
ldp x3, x4, [sp, #48]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/if_vlan.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/bpf_verifier.h>
|
||||
#include <linux/memory.h>
|
||||
#include <linux/sort.h>
|
||||
#include <asm/extable.h>
|
||||
|
|
@ -390,6 +391,34 @@ static void pop_callee_regs(u8 **pprog, bool *callee_regs_used)
|
|||
*pprog = prog;
|
||||
}
|
||||
|
||||
/* add rsp, depth */
|
||||
static void emit_add_rsp(u8 **pprog, u16 depth)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
|
||||
if (!depth)
|
||||
return;
|
||||
if (is_imm8(depth))
|
||||
EMIT4(0x48, 0x83, 0xC4, depth); /* add rsp, imm8 */
|
||||
else
|
||||
EMIT3_off32(0x48, 0x81, 0xC4, depth); /* add rsp, imm32 */
|
||||
*pprog = prog;
|
||||
}
|
||||
|
||||
/* sub rsp, depth */
|
||||
static void emit_sub_rsp(u8 **pprog, u16 depth)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
|
||||
if (!depth)
|
||||
return;
|
||||
if (is_imm8(depth))
|
||||
EMIT4(0x48, 0x83, 0xEC, depth); /* sub rsp, imm8 */
|
||||
else
|
||||
EMIT3_off32(0x48, 0x81, 0xEC, depth); /* sub rsp, imm32 */
|
||||
*pprog = prog;
|
||||
}
|
||||
|
||||
static void emit_nops(u8 **pprog, int len)
|
||||
{
|
||||
u8 *prog = *pprog;
|
||||
|
|
@ -1659,21 +1688,47 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
|
|||
bool seen_exit = false;
|
||||
u8 temp[BPF_MAX_INSN_SIZE + BPF_INSN_SAFETY];
|
||||
void __percpu *priv_frame_ptr = NULL;
|
||||
u16 out_stack_arg_cnt, outgoing_rsp;
|
||||
u64 arena_vm_start, user_vm_start;
|
||||
void __percpu *priv_stack_ptr;
|
||||
int i, excnt = 0;
|
||||
int ilen, proglen = 0;
|
||||
u8 *ip, *prog = temp;
|
||||
u32 stack_depth;
|
||||
int callee_saved_size;
|
||||
s32 outgoing_arg_base;
|
||||
int err;
|
||||
|
||||
stack_depth = bpf_prog->aux->stack_depth;
|
||||
out_stack_arg_cnt = bpf_out_stack_arg_cnt(env, bpf_prog);
|
||||
priv_stack_ptr = bpf_prog->aux->priv_stack_ptr;
|
||||
if (priv_stack_ptr) {
|
||||
priv_frame_ptr = priv_stack_ptr + PRIV_STACK_GUARD_SZ + round_up(stack_depth, 8);
|
||||
stack_depth = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Follow x86-64 calling convention for both BPF-to-BPF and
|
||||
* kfunc calls:
|
||||
* - Arg 6 is passed in R9 register
|
||||
* - Args 7+ are passed on the stack at [rsp]
|
||||
*
|
||||
* Incoming arg 6 is read from R9 (BPF r11+8 → MOV from R9).
|
||||
* Incoming args 7+ are read from [rbp + 16], [rbp + 24], ...
|
||||
* (BPF r11+16, r11+24, ... map directly with no offset change).
|
||||
*
|
||||
* tail_call_reachable is rejected by the verifier and priv_stack
|
||||
* is disabled by the JIT when stack args exist, so R9 is always
|
||||
* available.
|
||||
*
|
||||
* Stack layout (high to low):
|
||||
* [rbp + 16 + ...] incoming stack args 7+ (from caller)
|
||||
* [rbp + 8] return address
|
||||
* [rbp] saved rbp
|
||||
* [rbp - prog_stack] program stack
|
||||
* [below] callee-saved regs
|
||||
* [below] outgoing args 7+ (= rsp)
|
||||
*/
|
||||
arena_vm_start = bpf_arena_get_kern_vm_start(bpf_prog->aux->arena);
|
||||
user_vm_start = bpf_arena_get_user_vm_start(bpf_prog->aux->arena);
|
||||
|
||||
|
|
@ -1700,6 +1755,42 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
|
|||
push_r12(&prog);
|
||||
push_callee_regs(&prog, callee_regs_used);
|
||||
}
|
||||
|
||||
/* Compute callee-saved register area size. */
|
||||
callee_saved_size = 0;
|
||||
if (bpf_prog->aux->exception_boundary || arena_vm_start)
|
||||
callee_saved_size += 8; /* r12 */
|
||||
if (bpf_prog->aux->exception_boundary) {
|
||||
callee_saved_size += 4 * 8; /* rbx, r13, r14, r15 */
|
||||
} else {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
if (callee_regs_used[j])
|
||||
callee_saved_size += 8;
|
||||
}
|
||||
/*
|
||||
* Base offset from rbp for translating BPF outgoing args 7+
|
||||
* to native offsets. BPF uses negative offsets from r11
|
||||
* (r11-8 for arg6, r11-16 for arg7, ...) while x86 uses
|
||||
* positive offsets from rsp ([rsp+0] for arg7, [rsp+8] for
|
||||
* arg8, ...). Arg 6 goes to R9 directly.
|
||||
*
|
||||
* The translation reverses direction:
|
||||
* native_off = outgoing_arg_base - outgoing_rsp - bpf_off - 16
|
||||
*
|
||||
* Note that tail_call_reachable is guaranteed to be false when
|
||||
* stack args exist, so tcc pushes need not be accounted for.
|
||||
*/
|
||||
outgoing_arg_base = -(round_up(stack_depth, 8) + callee_saved_size);
|
||||
|
||||
/*
|
||||
* Allocate outgoing stack arg area for args 7+ only.
|
||||
* Arg 6 goes into r9 register, not on stack.
|
||||
*/
|
||||
outgoing_rsp = out_stack_arg_cnt > 1 ? (out_stack_arg_cnt - 1) * 8 : 0;
|
||||
emit_sub_rsp(&prog, outgoing_rsp);
|
||||
|
||||
if (arena_vm_start)
|
||||
emit_mov_imm64(&prog, X86_REG_R12,
|
||||
arena_vm_start >> 32, (u32) arena_vm_start);
|
||||
|
|
@ -1721,7 +1812,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
|
|||
u8 b2 = 0, b3 = 0;
|
||||
u8 *start_of_ldx;
|
||||
s64 jmp_offset;
|
||||
s16 insn_off;
|
||||
s32 insn_off;
|
||||
u8 jmp_cond;
|
||||
u8 *func;
|
||||
int nops;
|
||||
|
|
@ -2134,12 +2225,27 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
|
|||
EMIT1(0xC7);
|
||||
goto st;
|
||||
case BPF_ST | BPF_MEM | BPF_DW:
|
||||
if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
|
||||
/* Arg 6: store immediate in r9 register */
|
||||
emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, (u32)imm32);
|
||||
break;
|
||||
}
|
||||
EMIT2(add_1mod(0x48, dst_reg), 0xC7);
|
||||
|
||||
st: if (is_imm8(insn->off))
|
||||
EMIT2(add_1reg(0x40, dst_reg), insn->off);
|
||||
st: insn_off = insn->off;
|
||||
if (dst_reg == BPF_REG_PARAMS) {
|
||||
/*
|
||||
* Args 7+: reverse BPF negative offsets to
|
||||
* x86 positive rsp offsets.
|
||||
* BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
|
||||
*/
|
||||
insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
|
||||
dst_reg = BPF_REG_FP;
|
||||
}
|
||||
if (is_imm8(insn_off))
|
||||
EMIT2(add_1reg(0x40, dst_reg), insn_off);
|
||||
else
|
||||
EMIT1_off32(add_1reg(0x80, dst_reg), insn->off);
|
||||
EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
|
||||
|
||||
EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
|
||||
break;
|
||||
|
|
@ -2149,7 +2255,17 @@ st: if (is_imm8(insn->off))
|
|||
case BPF_STX | BPF_MEM | BPF_H:
|
||||
case BPF_STX | BPF_MEM | BPF_W:
|
||||
case BPF_STX | BPF_MEM | BPF_DW:
|
||||
emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn->off);
|
||||
if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
|
||||
/* Arg 6: store register value in r9 */
|
||||
EMIT_mov(X86_REG_R9, src_reg);
|
||||
break;
|
||||
}
|
||||
insn_off = insn->off;
|
||||
if (dst_reg == BPF_REG_PARAMS) {
|
||||
insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
|
||||
dst_reg = BPF_REG_FP;
|
||||
}
|
||||
emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
|
||||
break;
|
||||
|
||||
case BPF_ST | BPF_PROBE_MEM32 | BPF_B:
|
||||
|
|
@ -2248,6 +2364,19 @@ st: if (is_imm8(insn->off))
|
|||
case BPF_LDX | BPF_PROBE_MEMSX | BPF_H:
|
||||
case BPF_LDX | BPF_PROBE_MEMSX | BPF_W:
|
||||
insn_off = insn->off;
|
||||
if (src_reg == BPF_REG_PARAMS) {
|
||||
if (insn_off == 8) {
|
||||
/* Incoming arg 6: read from r9 */
|
||||
EMIT_mov(dst_reg, X86_REG_R9);
|
||||
break;
|
||||
}
|
||||
src_reg = BPF_REG_FP;
|
||||
/*
|
||||
* Incoming args 7+: native_off == bpf_off
|
||||
* (r11+16 → [rbp+16], r11+24 → [rbp+24], ...)
|
||||
* No offset adjustment needed.
|
||||
*/
|
||||
}
|
||||
|
||||
if (BPF_MODE(insn->code) == BPF_PROBE_MEM ||
|
||||
BPF_MODE(insn->code) == BPF_PROBE_MEMSX) {
|
||||
|
|
@ -2736,6 +2865,8 @@ st: if (is_imm8(insn->off))
|
|||
if (emit_spectre_bhb_barrier(&prog, ip, bpf_prog))
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Deallocate outgoing args 7+ area. */
|
||||
emit_add_rsp(&prog, outgoing_rsp);
|
||||
if (bpf_prog->aux->exception_boundary) {
|
||||
pop_callee_regs(&prog, all_callee_regs_used);
|
||||
pop_r12(&prog);
|
||||
|
|
@ -3793,7 +3924,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
|
|||
for (pass = 0; pass < MAX_PASSES || image; pass++) {
|
||||
if (!padding && pass >= PADDING_PASSES)
|
||||
padding = true;
|
||||
proglen = do_jit(env, prog, addrs, image, rw_image, oldproglen, &ctx, padding);
|
||||
proglen = do_jit(env, prog, addrs, image, rw_image, oldproglen,
|
||||
&ctx, padding);
|
||||
if (proglen <= 0) {
|
||||
out_image:
|
||||
image = NULL;
|
||||
|
|
@ -3910,6 +4042,11 @@ bool bpf_jit_supports_kfunc_call(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool bpf_jit_supports_stack_args(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void *bpf_arch_text_copy(void *dst, void *src, size_t len)
|
||||
{
|
||||
if (text_poke_copy(dst, src, len) == NULL)
|
||||
|
|
|
|||
|
|
@ -1548,6 +1548,7 @@ void bpf_jit_uncharge_modmem(u32 size);
|
|||
bool bpf_prog_has_trampoline(const struct bpf_prog *prog);
|
||||
bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struct bpf_prog *prog,
|
||||
int insn_idx);
|
||||
u16 bpf_out_stack_arg_cnt(const struct bpf_verifier_env *env, const struct bpf_prog *prog);
|
||||
#else
|
||||
static inline int bpf_trampoline_link_prog(struct bpf_tramp_link *link,
|
||||
struct bpf_trampoline *tr,
|
||||
|
|
|
|||
|
|
@ -402,6 +402,7 @@ struct bpf_func_state {
|
|||
bool in_callback_fn;
|
||||
bool in_async_callback_fn;
|
||||
bool in_exception_callback_fn;
|
||||
bool no_stack_arg_load;
|
||||
/* For callback calling functions that limit number of possible
|
||||
* callback executions (e.g. bpf_loop) keeps track of current
|
||||
* simulated iteration number.
|
||||
|
|
@ -427,46 +428,48 @@ struct bpf_func_state {
|
|||
* `stack`. allocated_stack is always a multiple of BPF_REG_SIZE.
|
||||
*/
|
||||
int allocated_stack;
|
||||
|
||||
u16 out_stack_arg_cnt; /* Number of outgoing on-stack argument slots */
|
||||
struct bpf_reg_state *stack_arg_regs; /* Outgoing on-stack arguments */
|
||||
};
|
||||
|
||||
#define MAX_CALL_FRAMES 8
|
||||
|
||||
/* instruction history flags, used in bpf_jmp_history_entry.flags field */
|
||||
/* instruction history flags, used in bpf_jmp_history_entry.flags field.
|
||||
* Frame number and SPI are stored in dedicated fields of bpf_jmp_history_entry.
|
||||
*/
|
||||
enum {
|
||||
/* instruction references stack slot through PTR_TO_STACK register;
|
||||
* we also store stack's frame number in lower 3 bits (MAX_CALL_FRAMES is 8)
|
||||
* and accessed stack slot's index in next 6 bits (MAX_BPF_STACK is 512,
|
||||
* 8 bytes per slot, so slot index (spi) is [0, 63])
|
||||
*/
|
||||
INSN_F_FRAMENO_MASK = 0x7, /* 3 bits */
|
||||
INSN_F_STACK_ACCESS = BIT(0),
|
||||
|
||||
INSN_F_SPI_MASK = 0x3f, /* 6 bits */
|
||||
INSN_F_SPI_SHIFT = 3, /* shifted 3 bits to the left */
|
||||
INSN_F_DST_REG_STACK = BIT(1), /* dst_reg is PTR_TO_STACK */
|
||||
INSN_F_SRC_REG_STACK = BIT(2), /* src_reg is PTR_TO_STACK */
|
||||
|
||||
INSN_F_STACK_ACCESS = BIT(9),
|
||||
|
||||
INSN_F_DST_REG_STACK = BIT(10), /* dst_reg is PTR_TO_STACK */
|
||||
INSN_F_SRC_REG_STACK = BIT(11), /* src_reg is PTR_TO_STACK */
|
||||
/* total 12 bits are used now. */
|
||||
INSN_F_STACK_ARG_ACCESS = BIT(3),
|
||||
};
|
||||
|
||||
static_assert(INSN_F_FRAMENO_MASK + 1 >= MAX_CALL_FRAMES);
|
||||
static_assert(INSN_F_SPI_MASK + 1 >= MAX_BPF_STACK / 8);
|
||||
|
||||
struct bpf_jmp_history_entry {
|
||||
u32 idx;
|
||||
/* insn idx can't be bigger than 1 million */
|
||||
u32 idx : 20;
|
||||
u32 frame : 3; /* stack access frame number */
|
||||
u32 spi : 6; /* stack slot index (0..63) */
|
||||
u32 : 3;
|
||||
u32 prev_idx : 20;
|
||||
/* special INSN_F_xxx flags */
|
||||
u32 flags : 12;
|
||||
u32 flags : 4;
|
||||
u32 : 8;
|
||||
/* additional registers that need precision tracking when this
|
||||
* jump is backtracked, vector of six 10-bit records
|
||||
*/
|
||||
u64 linked_regs;
|
||||
};
|
||||
|
||||
/* Maximum number of register states that can exist at once */
|
||||
#define BPF_ID_MAP_SIZE ((MAX_BPF_REG + MAX_BPF_STACK / BPF_REG_SIZE) * MAX_CALL_FRAMES)
|
||||
static_assert(MAX_CALL_FRAMES <= (1 << 3));
|
||||
static_assert(MAX_BPF_STACK / 8 <= (1 << 6));
|
||||
|
||||
/* Maximum number of bpf_reg_state objects that can exist at once */
|
||||
#define MAX_STACK_ARG_SLOTS (MAX_BPF_FUNC_ARGS - MAX_BPF_FUNC_REG_ARGS)
|
||||
#define BPF_ID_MAP_SIZE ((MAX_BPF_REG + MAX_BPF_STACK / BPF_REG_SIZE + \
|
||||
MAX_STACK_ARG_SLOTS) * MAX_CALL_FRAMES)
|
||||
struct bpf_verifier_state {
|
||||
/* call stack tracking */
|
||||
struct bpf_func_state *frame[MAX_CALL_FRAMES];
|
||||
|
|
@ -552,10 +555,23 @@ struct bpf_verifier_state {
|
|||
u32 may_goto_depth;
|
||||
};
|
||||
|
||||
#define bpf_get_spilled_reg(slot, frame, mask) \
|
||||
(((slot < frame->allocated_stack / BPF_REG_SIZE) && \
|
||||
((1 << frame->stack[slot].slot_type[BPF_REG_SIZE - 1]) & (mask))) \
|
||||
? &frame->stack[slot].spilled_ptr : NULL)
|
||||
static inline struct bpf_reg_state *
|
||||
bpf_get_spilled_reg(int slot, struct bpf_func_state *frame, u32 mask)
|
||||
{
|
||||
if (slot < frame->allocated_stack / BPF_REG_SIZE &&
|
||||
(1 << frame->stack[slot].slot_type[BPF_REG_SIZE - 1]) & mask)
|
||||
return &frame->stack[slot].spilled_ptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline struct bpf_reg_state *
|
||||
bpf_get_spilled_stack_arg(int slot, struct bpf_func_state *frame)
|
||||
{
|
||||
if (slot < frame->out_stack_arg_cnt &&
|
||||
frame->stack_arg_regs[slot].type != NOT_INIT)
|
||||
return &frame->stack_arg_regs[slot];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Iterate over 'frame', setting 'reg' to either NULL or a spilled register. */
|
||||
#define bpf_for_each_spilled_reg(iter, frame, reg, mask) \
|
||||
|
|
@ -563,6 +579,12 @@ struct bpf_verifier_state {
|
|||
iter < frame->allocated_stack / BPF_REG_SIZE; \
|
||||
iter++, reg = bpf_get_spilled_reg(iter, frame, mask))
|
||||
|
||||
/* Iterate over 'frame', setting 'reg' to either NULL or a spilled stack arg. */
|
||||
#define bpf_for_each_spilled_stack_arg(iter, frame, reg) \
|
||||
for (iter = 0, reg = bpf_get_spilled_stack_arg(iter, frame); \
|
||||
iter < frame->out_stack_arg_cnt; \
|
||||
iter++, reg = bpf_get_spilled_stack_arg(iter, frame))
|
||||
|
||||
#define bpf_for_each_reg_in_vstate_mask(__vst, __state, __reg, __mask, __expr) \
|
||||
({ \
|
||||
struct bpf_verifier_state *___vstate = __vst; \
|
||||
|
|
@ -580,6 +602,11 @@ struct bpf_verifier_state {
|
|||
continue; \
|
||||
(void)(__expr); \
|
||||
} \
|
||||
bpf_for_each_spilled_stack_arg(___j, __state, __reg) { \
|
||||
if (!__reg) \
|
||||
continue; \
|
||||
(void)(__expr); \
|
||||
} \
|
||||
} \
|
||||
})
|
||||
|
||||
|
|
@ -811,12 +838,21 @@ 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 stack_arg_cnt; /* incoming + max outgoing */
|
||||
u16 max_out_stack_arg_cnt;
|
||||
};
|
||||
|
||||
static inline u16 bpf_in_stack_arg_cnt(const struct bpf_subprog_info *sub)
|
||||
{
|
||||
if (sub->arg_cnt > MAX_BPF_FUNC_REG_ARGS)
|
||||
return sub->arg_cnt - MAX_BPF_FUNC_REG_ARGS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct bpf_verifier_env;
|
||||
|
||||
struct backtrack_state {
|
||||
|
|
@ -824,6 +860,7 @@ struct backtrack_state {
|
|||
u32 frame;
|
||||
u32 reg_masks[MAX_CALL_FRAMES];
|
||||
u64 stack_masks[MAX_CALL_FRAMES];
|
||||
u8 stack_arg_masks[MAX_CALL_FRAMES];
|
||||
};
|
||||
|
||||
struct bpf_id_pair {
|
||||
|
|
@ -1159,7 +1196,7 @@ struct list_head *bpf_explored_state(struct bpf_verifier_env *env, int idx);
|
|||
void bpf_free_verifier_state(struct bpf_verifier_state *state, bool free_self);
|
||||
void bpf_free_backedges(struct bpf_scc_visit *visit);
|
||||
int bpf_push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_state *cur,
|
||||
int insn_flags, u64 linked_regs);
|
||||
int insn_flags, int spi, int frame, u64 linked_regs);
|
||||
void bpf_bt_sync_linked_regs(struct backtrack_state *bt, struct bpf_jmp_history_entry *hist);
|
||||
void bpf_mark_reg_not_init(const struct bpf_verifier_env *env,
|
||||
struct bpf_reg_state *reg);
|
||||
|
|
@ -1222,6 +1259,11 @@ static inline void bpf_bt_set_frame_slot(struct backtrack_state *bt, u32 frame,
|
|||
bt->stack_masks[frame] |= 1ull << slot;
|
||||
}
|
||||
|
||||
static inline void bt_set_frame_stack_arg_slot(struct backtrack_state *bt, u32 frame, u32 slot)
|
||||
{
|
||||
bt->stack_arg_masks[frame] |= 1 << slot;
|
||||
}
|
||||
|
||||
static inline bool bt_is_frame_reg_set(struct backtrack_state *bt, u32 frame, u32 reg)
|
||||
{
|
||||
return bt->reg_masks[frame] & (1 << reg);
|
||||
|
|
|
|||
|
|
@ -749,6 +749,27 @@ static inline u32 bpf_prog_run_pin_on_cpu(const struct bpf_prog *prog,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static inline bool is_stack_arg_ldx(const struct bpf_insn *insn)
|
||||
{
|
||||
return insn->code == (BPF_LDX | BPF_MEM | BPF_DW) &&
|
||||
insn->src_reg == BPF_REG_PARAMS &&
|
||||
insn->off > 0 && insn->off % 8 == 0;
|
||||
}
|
||||
|
||||
static inline bool is_stack_arg_st(const struct bpf_insn *insn)
|
||||
{
|
||||
return insn->code == (BPF_ST | BPF_MEM | BPF_DW) &&
|
||||
insn->dst_reg == BPF_REG_PARAMS &&
|
||||
insn->off < 0 && insn->off % 8 == 0;
|
||||
}
|
||||
|
||||
static inline bool is_stack_arg_stx(const struct bpf_insn *insn)
|
||||
{
|
||||
return insn->code == (BPF_STX | BPF_MEM | BPF_DW) &&
|
||||
insn->dst_reg == BPF_REG_PARAMS &&
|
||||
insn->off < 0 && insn->off % 8 == 0;
|
||||
}
|
||||
|
||||
#define BPF_SKB_CB_LEN QDISC_CB_PRIV_LEN
|
||||
|
||||
struct bpf_skb_data_end {
|
||||
|
|
@ -1163,6 +1184,7 @@ bool bpf_jit_inlines_helper_call(s32 imm);
|
|||
bool bpf_jit_supports_subprog_tailcalls(void);
|
||||
bool bpf_jit_supports_percpu_insn(void);
|
||||
bool bpf_jit_supports_kfunc_call(void);
|
||||
bool bpf_jit_supports_stack_args(void);
|
||||
bool bpf_jit_supports_far_kfunc_call(void);
|
||||
bool bpf_jit_supports_exceptions(void);
|
||||
bool bpf_jit_supports_ptr_xchg(void);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
/* for any branch, call, exit record the history of jmps in the given state */
|
||||
int bpf_push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_state *cur,
|
||||
int insn_flags, u64 linked_regs)
|
||||
int insn_flags, int spi, int frame, u64 linked_regs)
|
||||
{
|
||||
u32 cnt = cur->jmp_history_cnt;
|
||||
struct bpf_jmp_history_entry *p;
|
||||
|
|
@ -25,6 +25,8 @@ int bpf_push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_state
|
|||
env, "insn history: insn_idx %d cur flags %x new flags %x",
|
||||
env->insn_idx, env->cur_hist_ent->flags, insn_flags);
|
||||
env->cur_hist_ent->flags |= insn_flags;
|
||||
env->cur_hist_ent->spi = spi;
|
||||
env->cur_hist_ent->frame = frame;
|
||||
verifier_bug_if(env->cur_hist_ent->linked_regs != 0, env,
|
||||
"insn history: insn_idx %d linked_regs: %#llx",
|
||||
env->insn_idx, env->cur_hist_ent->linked_regs);
|
||||
|
|
@ -43,6 +45,8 @@ int bpf_push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_state
|
|||
p->idx = env->insn_idx;
|
||||
p->prev_idx = env->prev_insn_idx;
|
||||
p->flags = insn_flags;
|
||||
p->spi = spi;
|
||||
p->frame = frame;
|
||||
p->linked_regs = linked_regs;
|
||||
cur->jmp_history_cnt = cnt;
|
||||
env->cur_hist_ent = p;
|
||||
|
|
@ -64,16 +68,6 @@ static bool is_atomic_fetch_insn(const struct bpf_insn *insn)
|
|||
(insn->imm & BPF_FETCH);
|
||||
}
|
||||
|
||||
static int insn_stack_access_spi(int insn_flags)
|
||||
{
|
||||
return (insn_flags >> INSN_F_SPI_SHIFT) & INSN_F_SPI_MASK;
|
||||
}
|
||||
|
||||
static int insn_stack_access_frameno(int insn_flags)
|
||||
{
|
||||
return insn_flags & INSN_F_FRAMENO_MASK;
|
||||
}
|
||||
|
||||
/* Backtrack one insn at a time. If idx is not at the top of recorded
|
||||
* history then previous instruction came from straight line execution.
|
||||
* Return -ENOENT if we exhausted all instructions within given state.
|
||||
|
|
@ -135,11 +129,21 @@ static inline u32 bt_empty(struct backtrack_state *bt)
|
|||
int i;
|
||||
|
||||
for (i = 0; i <= bt->frame; i++)
|
||||
mask |= bt->reg_masks[i] | bt->stack_masks[i];
|
||||
mask |= bt->reg_masks[i] | bt->stack_masks[i] | bt->stack_arg_masks[i];
|
||||
|
||||
return mask == 0;
|
||||
}
|
||||
|
||||
static inline void bt_clear_frame_stack_arg_slot(struct backtrack_state *bt, u32 frame, u32 slot)
|
||||
{
|
||||
bt->stack_arg_masks[frame] &= ~(1 << slot);
|
||||
}
|
||||
|
||||
static inline bool bt_is_frame_stack_arg_slot_set(struct backtrack_state *bt, u32 frame, u32 slot)
|
||||
{
|
||||
return bt->stack_arg_masks[frame] & (1 << slot);
|
||||
}
|
||||
|
||||
static inline int bt_subprog_enter(struct backtrack_state *bt)
|
||||
{
|
||||
if (bt->frame == MAX_CALL_FRAMES - 1) {
|
||||
|
|
@ -200,6 +204,11 @@ static inline u64 bt_stack_mask(struct backtrack_state *bt)
|
|||
return bt->stack_masks[bt->frame];
|
||||
}
|
||||
|
||||
static inline u8 bt_stack_arg_mask(struct backtrack_state *bt)
|
||||
{
|
||||
return bt->stack_arg_masks[bt->frame];
|
||||
}
|
||||
|
||||
static inline bool bt_is_reg_set(struct backtrack_state *bt, u32 reg)
|
||||
{
|
||||
return bt->reg_masks[bt->frame] & (1 << reg);
|
||||
|
|
@ -341,6 +350,19 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
|
|||
return 0;
|
||||
bt_clear_reg(bt, load_reg);
|
||||
|
||||
if (hist && hist->flags & INSN_F_STACK_ARG_ACCESS) {
|
||||
spi = hist->spi;
|
||||
/*
|
||||
* Stack arg read: callee reads from r11+off, but
|
||||
* the data lives in the caller's stack_arg_regs.
|
||||
* Set the mask in the caller frame so precision
|
||||
* is marked in the caller's slot at the callee
|
||||
* entry checkpoint.
|
||||
*/
|
||||
bt_set_frame_stack_arg_slot(bt, bt->frame - 1, spi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* scalars can only be spilled into stack w/o losing precision.
|
||||
* Load from any other memory can be zero extended.
|
||||
* The desire to keep that precision is already indicated
|
||||
|
|
@ -353,8 +375,8 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
|
|||
* that [fp - off] slot contains scalar that needs to be
|
||||
* tracked with precision
|
||||
*/
|
||||
spi = insn_stack_access_spi(hist->flags);
|
||||
fr = insn_stack_access_frameno(hist->flags);
|
||||
spi = hist->spi;
|
||||
fr = hist->frame;
|
||||
bpf_bt_set_frame_slot(bt, fr, spi);
|
||||
} else if (class == BPF_STX || class == BPF_ST) {
|
||||
if (bt_is_reg_set(bt, dreg))
|
||||
|
|
@ -363,11 +385,22 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
|
|||
* encountered a case of pointer subtraction.
|
||||
*/
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (hist && hist->flags & INSN_F_STACK_ARG_ACCESS) {
|
||||
spi = hist->spi;
|
||||
if (!bt_is_frame_stack_arg_slot_set(bt, bt->frame, spi))
|
||||
return 0;
|
||||
bt_clear_frame_stack_arg_slot(bt, bt->frame, spi);
|
||||
if (class == BPF_STX)
|
||||
bt_set_reg(bt, sreg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* scalars can only be spilled into stack */
|
||||
if (!hist || !(hist->flags & INSN_F_STACK_ACCESS))
|
||||
return 0;
|
||||
spi = insn_stack_access_spi(hist->flags);
|
||||
fr = insn_stack_access_frameno(hist->flags);
|
||||
spi = hist->spi;
|
||||
fr = hist->frame;
|
||||
if (!bt_is_frame_slot_set(bt, fr, spi))
|
||||
return 0;
|
||||
bt_clear_frame_slot(bt, fr, spi);
|
||||
|
|
@ -431,6 +464,12 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
|
|||
bpf_bt_set_frame_reg(bt, bt->frame - 1, i);
|
||||
}
|
||||
}
|
||||
if (bt_stack_arg_mask(bt)) {
|
||||
verifier_bug(env,
|
||||
"static subprog leftover stack arg slots %x",
|
||||
bt_stack_arg_mask(bt));
|
||||
return -EFAULT;
|
||||
}
|
||||
if (bt_subprog_exit(bt))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
|
|
@ -901,6 +940,17 @@ int bpf_mark_chain_precision(struct bpf_verifier_env *env,
|
|||
*changed = true;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < func->out_stack_arg_cnt; i++) {
|
||||
if (!bt_is_frame_stack_arg_slot_set(bt, fr, i))
|
||||
continue;
|
||||
reg = &func->stack_arg_regs[i];
|
||||
if (reg->type != SCALAR_VALUE || reg->precise) {
|
||||
bt_clear_frame_stack_arg_slot(bt, fr, i);
|
||||
} else {
|
||||
reg->precise = true;
|
||||
*changed = true;
|
||||
}
|
||||
}
|
||||
if (env->log.level & BPF_LOG_LEVEL2) {
|
||||
fmt_reg_mask(env->tmp_str_buf, TMP_STR_BUF_LEN,
|
||||
bt_frame_reg_mask(bt, fr));
|
||||
|
|
|
|||
|
|
@ -7864,10 +7864,23 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
|
|||
}
|
||||
args = (const struct btf_param *)(t + 1);
|
||||
nargs = btf_type_vlen(t);
|
||||
sub->arg_cnt = nargs;
|
||||
if (nargs > MAX_BPF_FUNC_ARGS) {
|
||||
bpf_log(log, "kernel supports at most %d parameters, function %s has %d\n",
|
||||
MAX_BPF_FUNC_ARGS, tname, nargs);
|
||||
return -EFAULT;
|
||||
}
|
||||
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 (!bpf_jit_supports_stack_args()) {
|
||||
bpf_log(log, "JIT does not support function %s() with %d args\n",
|
||||
tname, nargs);
|
||||
return -EFAULT;
|
||||
}
|
||||
sub->stack_arg_cnt = nargs - MAX_BPF_FUNC_REG_ARGS;
|
||||
}
|
||||
|
||||
if (is_global && nargs > MAX_BPF_FUNC_REG_ARGS) {
|
||||
bpf_log(log, "global function %s has %d > %d args, stack args not supported\n",
|
||||
tname, nargs, MAX_BPF_FUNC_REG_ARGS);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -8051,7 +8064,6 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
sub->arg_cnt = nargs;
|
||||
sub->args_cached = true;
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ static void const_reg_xfer(struct bpf_verifier_env *env, struct const_arg_info *
|
|||
u8 opcode = BPF_OP(insn->code) | BPF_SRC(insn->code);
|
||||
int r;
|
||||
|
||||
/* Stack arg stores (r11-based) are outside the tracked register set. */
|
||||
if (is_stack_arg_st(insn) || is_stack_arg_stx(insn))
|
||||
return;
|
||||
if (is_stack_arg_ldx(insn)) {
|
||||
ci_out[insn->dst_reg] = unknown;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (class) {
|
||||
case BPF_ALU:
|
||||
case BPF_ALU64:
|
||||
|
|
|
|||
|
|
@ -1582,6 +1582,16 @@ bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struc
|
|||
insn_idx += prog->aux->subprog_start;
|
||||
return env->insn_aux_data[insn_idx].indirect_target;
|
||||
}
|
||||
|
||||
u16 bpf_out_stack_arg_cnt(const struct bpf_verifier_env *env, const struct bpf_prog *prog)
|
||||
{
|
||||
const struct bpf_subprog_info *sub;
|
||||
|
||||
if (!env)
|
||||
return 0;
|
||||
sub = &env->subprog_info[prog->aux->func_idx];
|
||||
return sub->stack_arg_cnt - bpf_in_stack_arg_cnt(sub);
|
||||
}
|
||||
#endif /* CONFIG_BPF_JIT */
|
||||
|
||||
/* Base function for offset calculation. Needs to go into .text section,
|
||||
|
|
@ -2599,7 +2609,7 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct
|
|||
goto finalize;
|
||||
|
||||
if (IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON) ||
|
||||
bpf_prog_has_kfunc_call(fp))
|
||||
bpf_prog_has_kfunc_call(fp) || (env && env->subprog_info[0].stack_arg_cnt))
|
||||
jit_needed = true;
|
||||
|
||||
if (!bpf_prog_select_interpreter(fp))
|
||||
|
|
@ -3217,6 +3227,11 @@ bool __weak bpf_jit_supports_kfunc_call(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool __weak bpf_jit_supports_stack_args(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool __weak bpf_jit_supports_far_kfunc_call(void)
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1378,9 +1378,21 @@ 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];
|
||||
u16 outgoing = subprog->stack_arg_cnt - bpf_in_stack_arg_cnt(subprog);
|
||||
|
||||
if (subprog->max_out_stack_arg_cnt > outgoing) {
|
||||
verbose(env,
|
||||
"func#%d writes %u stack arg slots, but calls only require %u\n",
|
||||
i, subprog->max_out_stack_arg_cnt, outgoing);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (env->prog->jit_requested &&
|
||||
!bpf_prog_is_offloaded(env->prog->aux)) {
|
||||
|
|
@ -1395,6 +1407,12 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env)
|
|||
verbose(env, "calling kernel functions are not allowed in non-JITed programs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
for (i = 1; i < env->subprog_cnt; i++) {
|
||||
if (bpf_in_stack_arg_cnt(&env->subprog_info[i])) {
|
||||
verbose(env, "stack args are not supported in non-JITed programs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (env->subprog_cnt > 1 && env->prog->aux->tail_call_reachable) {
|
||||
/* When JIT fails the progs with bpf2bpf calls and tail_calls
|
||||
* have to be rejected, since interpreter doesn't support them yet.
|
||||
|
|
|
|||
|
|
@ -610,6 +610,21 @@ enum arg_track_state {
|
|||
/* Track callee stack slots fp-8 through fp-512 (64 slots of 8 bytes each) */
|
||||
#define MAX_ARG_SPILL_SLOTS 64
|
||||
|
||||
/*
|
||||
* Combined register + stack arg tracking: R0-R10 at indices 0-10,
|
||||
* outgoing stack arg slots at indices MAX_BPF_REG..MAX_BPF_REG+6.
|
||||
*/
|
||||
#define MAX_AT_TRACK_REGS (MAX_BPF_REG + MAX_STACK_ARG_SLOTS)
|
||||
|
||||
static int stack_arg_off_to_slot(s16 off)
|
||||
{
|
||||
int aoff = off < 0 ? -off : off;
|
||||
|
||||
if (aoff / 8 > MAX_STACK_ARG_SLOTS)
|
||||
return -1;
|
||||
return aoff / 8 - 1;
|
||||
}
|
||||
|
||||
static bool arg_is_visited(const struct arg_track *at)
|
||||
{
|
||||
return at->frame != ARG_UNVISITED;
|
||||
|
|
@ -1032,6 +1047,21 @@ static void arg_track_log(struct bpf_verifier_env *env, struct bpf_insn *insn, i
|
|||
verbose(env, "\tr%d: ", i); verbose_arg_track(env, &at_in[i]);
|
||||
verbose(env, " -> "); verbose_arg_track(env, &at_out[i]);
|
||||
}
|
||||
/* Log outgoing stack arg slot transitions at indices MAX_BPF_REG..MAX_AT_TRACK_REGS-1 */
|
||||
for (i = 0; i < MAX_STACK_ARG_SLOTS; i++) {
|
||||
int ai = MAX_BPF_REG + i;
|
||||
|
||||
if (arg_track_eq(&at_out[ai], &at_in[ai]))
|
||||
continue;
|
||||
if (!printed) {
|
||||
verbose(env, "%3d: ", idx);
|
||||
bpf_verbose_insn(env, insn);
|
||||
bpf_vlog_reset(&env->log, env->log.end_pos - 1);
|
||||
printed = true;
|
||||
}
|
||||
verbose(env, "\tsa%d: ", i); verbose_arg_track(env, &at_in[ai]);
|
||||
verbose(env, " -> "); verbose_arg_track(env, &at_out[ai]);
|
||||
}
|
||||
for (i = 0; i < MAX_ARG_SPILL_SLOTS; i++) {
|
||||
if (arg_track_eq(&at_stack_out[i], &at_stack_in[i]))
|
||||
continue;
|
||||
|
|
@ -1062,6 +1092,7 @@ static bool can_be_local_fp(int depth, int regno, struct arg_track *at)
|
|||
static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
||||
int insn_idx,
|
||||
struct arg_track *at_out, struct arg_track *at_stack_out,
|
||||
const struct arg_track *at_stack_arg_entry,
|
||||
struct func_instance *instance,
|
||||
u32 *callsites)
|
||||
{
|
||||
|
|
@ -1071,9 +1102,21 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
struct arg_track *dst = &at_out[insn->dst_reg];
|
||||
struct arg_track *src = &at_out[insn->src_reg];
|
||||
struct arg_track none = { .frame = ARG_NONE };
|
||||
int r;
|
||||
int r, slot;
|
||||
|
||||
if (class == BPF_ALU64 && BPF_SRC(insn->code) == BPF_K) {
|
||||
/* Handle stack arg stores and loads. */
|
||||
if (is_stack_arg_st(insn) || is_stack_arg_stx(insn)) {
|
||||
slot = stack_arg_off_to_slot(insn->off);
|
||||
if (slot >= 0) {
|
||||
if (is_stack_arg_stx(insn))
|
||||
at_out[MAX_BPF_REG + slot] = at_out[insn->src_reg];
|
||||
else
|
||||
at_out[MAX_BPF_REG + slot] = none;
|
||||
}
|
||||
} else if (is_stack_arg_ldx(insn)) {
|
||||
slot = stack_arg_off_to_slot(insn->off);
|
||||
at_out[insn->dst_reg] = (slot >= 0) ? at_stack_arg_entry[slot] : none;
|
||||
} else if (class == BPF_ALU64 && BPF_SRC(insn->code) == BPF_K) {
|
||||
if (code == BPF_MOV) {
|
||||
*dst = none;
|
||||
} else if (dst->frame >= 0) {
|
||||
|
|
@ -1297,6 +1340,16 @@ static int record_load_store_access(struct bpf_verifier_env *env,
|
|||
struct arg_track resolved, *ptr;
|
||||
int oi;
|
||||
|
||||
/*
|
||||
* Stack arg insns use dst_reg/src_reg=BPF_REG_PARAMS(11). Since at[]
|
||||
* is extended to MAX_AT_TRACK_REGS, at[11] holds the arg_track for
|
||||
* outgoing stack arg slot 0 — not the pointer used for the memory
|
||||
* access. Skip so the slot's tracked value isn't confused with the
|
||||
* base register that record_stack_access() expects.
|
||||
*/
|
||||
if (is_stack_arg_stx(insn) || is_stack_arg_st(insn) || is_stack_arg_ldx(insn))
|
||||
return 0;
|
||||
|
||||
switch (class) {
|
||||
case BPF_LDX:
|
||||
ptr = &at[insn->src_reg];
|
||||
|
|
@ -1343,6 +1396,42 @@ static int record_load_store_access(struct bpf_verifier_env *env,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int record_arg_access(struct bpf_verifier_env *env,
|
||||
struct func_instance *instance,
|
||||
struct bpf_insn *insn,
|
||||
struct arg_track *at, int arg_idx,
|
||||
int insn_idx)
|
||||
{
|
||||
int depth = instance->depth;
|
||||
int frame = at->frame;
|
||||
int err = 0;
|
||||
s64 bytes;
|
||||
|
||||
if (!arg_is_fp(at))
|
||||
return 0;
|
||||
|
||||
if (bpf_helper_call(insn)) {
|
||||
bytes = bpf_helper_stack_access_bytes(env, insn, arg_idx, insn_idx);
|
||||
} else if (bpf_pseudo_kfunc_call(insn)) {
|
||||
bytes = bpf_kfunc_stack_access_bytes(env, insn, arg_idx, insn_idx);
|
||||
} else {
|
||||
for (int f = 0; f <= depth; f++) {
|
||||
err = mark_stack_read(instance, f, insn_idx, SPIS_ALL);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (bytes == 0)
|
||||
return 0;
|
||||
|
||||
if (frame >= 0 && frame <= depth)
|
||||
err = record_stack_access(instance, at, bytes, frame, insn_idx);
|
||||
else if (frame == ARG_IMPRECISE)
|
||||
err = record_imprecise(instance, at->mask, insn_idx);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Record stack access for a given 'at' state of helper/kfunc 'insn' */
|
||||
static int record_call_access(struct bpf_verifier_env *env,
|
||||
struct func_instance *instance,
|
||||
|
|
@ -1350,9 +1439,8 @@ static int record_call_access(struct bpf_verifier_env *env,
|
|||
int insn_idx)
|
||||
{
|
||||
struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
|
||||
int depth = instance->depth;
|
||||
struct bpf_call_summary cs;
|
||||
int r, err = 0, num_params = 5;
|
||||
int r, err, num_params = 5;
|
||||
|
||||
if (bpf_pseudo_call(insn))
|
||||
return 0;
|
||||
|
|
@ -1360,32 +1448,15 @@ static int record_call_access(struct bpf_verifier_env *env,
|
|||
if (bpf_get_call_summary(env, insn, &cs))
|
||||
num_params = cs.num_params;
|
||||
|
||||
for (r = BPF_REG_1; r < BPF_REG_1 + num_params; r++) {
|
||||
int frame = at[r].frame;
|
||||
s64 bytes;
|
||||
for (r = BPF_REG_1; r < BPF_REG_1 + min(num_params, MAX_BPF_FUNC_REG_ARGS); r++) {
|
||||
err = record_arg_access(env, instance, insn, &at[r], r - 1, insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!arg_is_fp(&at[r]))
|
||||
continue;
|
||||
|
||||
if (bpf_helper_call(insn)) {
|
||||
bytes = bpf_helper_stack_access_bytes(env, insn, r - 1, insn_idx);
|
||||
} else if (bpf_pseudo_kfunc_call(insn)) {
|
||||
bytes = bpf_kfunc_stack_access_bytes(env, insn, r - 1, insn_idx);
|
||||
} else {
|
||||
for (int f = 0; f <= depth; f++) {
|
||||
err = mark_stack_read(instance, f, insn_idx, SPIS_ALL);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (bytes == 0)
|
||||
continue;
|
||||
|
||||
if (frame >= 0 && frame <= depth)
|
||||
err = record_stack_access(instance, &at[r], bytes, frame, insn_idx);
|
||||
else if (frame == ARG_IMPRECISE)
|
||||
err = record_imprecise(instance, at[r].mask, insn_idx);
|
||||
for (r = 0; r < MAX_STACK_ARG_SLOTS && r < num_params - MAX_BPF_FUNC_REG_ARGS; r++) {
|
||||
err = record_arg_access(env, instance, insn, &at[MAX_BPF_REG + r],
|
||||
r + MAX_BPF_FUNC_REG_ARGS, insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
|
@ -1445,7 +1516,7 @@ static int find_callback_subprog(struct bpf_verifier_env *env,
|
|||
|
||||
/* Per-subprog intermediate state kept alive across analysis phases */
|
||||
struct subprog_at_info {
|
||||
struct arg_track (*at_in)[MAX_BPF_REG];
|
||||
struct arg_track (*at_in)[MAX_AT_TRACK_REGS];
|
||||
int len;
|
||||
};
|
||||
|
||||
|
|
@ -1479,6 +1550,9 @@ static void print_subprog_arg_access(struct bpf_verifier_env *env,
|
|||
for (r = 0; r < MAX_BPF_REG - 1; r++)
|
||||
if (arg_is_fp(&info->at_in[i][r]))
|
||||
has_extra = true;
|
||||
for (r = 0; r < MAX_STACK_ARG_SLOTS; r++)
|
||||
if (arg_is_fp(&info->at_in[i][MAX_BPF_REG + r]))
|
||||
has_extra = true;
|
||||
}
|
||||
if (is_ldx_stx_call) {
|
||||
for (r = 0; r < MAX_ARG_SPILL_SLOTS; r++)
|
||||
|
|
@ -1503,6 +1577,12 @@ static void print_subprog_arg_access(struct bpf_verifier_env *env,
|
|||
verbose(env, " r%d=", r);
|
||||
verbose_arg_track(env, &info->at_in[i][r]);
|
||||
}
|
||||
for (r = 0; r < MAX_STACK_ARG_SLOTS; r++) {
|
||||
if (!arg_is_fp(&info->at_in[i][MAX_BPF_REG + r]))
|
||||
continue;
|
||||
verbose(env, " sa%d=", r);
|
||||
verbose_arg_track(env, &info->at_in[i][MAX_BPF_REG + r]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_ldx_stx_call) {
|
||||
|
|
@ -1525,7 +1605,7 @@ static void print_subprog_arg_access(struct bpf_verifier_env *env,
|
|||
* Runs forward fixed-point with arg_track_xfer(), then records
|
||||
* memory accesses in a single linear pass over converged state.
|
||||
*
|
||||
* @callee_entry: pre-populated entry state for R1-R5
|
||||
* @callee_entry: pre-populated entry state for R1-R5 and stack args
|
||||
* NULL for main (subprog 0).
|
||||
* @info: stores at_in, len for debug printing.
|
||||
*/
|
||||
|
|
@ -1543,10 +1623,11 @@ static int compute_subprog_args(struct bpf_verifier_env *env,
|
|||
int end = env->subprog_info[subprog + 1].start;
|
||||
int po_end = env->subprog_info[subprog + 1].postorder_start;
|
||||
int len = end - start;
|
||||
struct arg_track (*at_in)[MAX_BPF_REG] = NULL;
|
||||
struct arg_track at_out[MAX_BPF_REG];
|
||||
struct arg_track (*at_in)[MAX_AT_TRACK_REGS] = NULL;
|
||||
struct arg_track at_out[MAX_AT_TRACK_REGS];
|
||||
struct arg_track (*at_stack_in)[MAX_ARG_SPILL_SLOTS] = NULL;
|
||||
struct arg_track *at_stack_out = NULL;
|
||||
struct arg_track at_stack_arg_entry[MAX_STACK_ARG_SLOTS];
|
||||
struct arg_track unvisited = { .frame = ARG_UNVISITED };
|
||||
struct arg_track none = { .frame = ARG_NONE };
|
||||
bool changed;
|
||||
|
|
@ -1565,13 +1646,13 @@ static int compute_subprog_args(struct bpf_verifier_env *env,
|
|||
goto err_free;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
for (r = 0; r < MAX_BPF_REG; r++)
|
||||
for (r = 0; r < MAX_AT_TRACK_REGS; r++)
|
||||
at_in[i][r] = unvisited;
|
||||
for (r = 0; r < MAX_ARG_SPILL_SLOTS; r++)
|
||||
at_stack_in[i][r] = unvisited;
|
||||
}
|
||||
|
||||
for (r = 0; r < MAX_BPF_REG; r++)
|
||||
for (r = 0; r < MAX_AT_TRACK_REGS; r++)
|
||||
at_in[0][r] = none;
|
||||
|
||||
/* Entry: R10 is always precisely the current frame's FP */
|
||||
|
|
@ -1587,6 +1668,10 @@ static int compute_subprog_args(struct bpf_verifier_env *env,
|
|||
for (r = 0; r < MAX_ARG_SPILL_SLOTS; r++)
|
||||
at_stack_in[0][r] = none;
|
||||
|
||||
/* Entry: incoming stack args from caller, or ARG_NONE for main */
|
||||
for (r = 0; r < MAX_STACK_ARG_SLOTS; r++)
|
||||
at_stack_arg_entry[r] = callee_entry ? callee_entry[MAX_BPF_REG + r] : none;
|
||||
|
||||
if (env->log.level & BPF_LOG_LEVEL2)
|
||||
verbose(env, "subprog#%d: analyzing (depth %d)...\n", subprog, depth);
|
||||
|
||||
|
|
@ -1605,7 +1690,8 @@ static int compute_subprog_args(struct bpf_verifier_env *env,
|
|||
memcpy(at_out, at_in[i], sizeof(at_out));
|
||||
memcpy(at_stack_out, at_stack_in[i], MAX_ARG_SPILL_SLOTS * sizeof(*at_stack_out));
|
||||
|
||||
arg_track_xfer(env, insn, idx, at_out, at_stack_out, instance, callsites);
|
||||
arg_track_xfer(env, insn, idx, at_out, at_stack_out,
|
||||
at_stack_arg_entry, instance, callsites);
|
||||
arg_track_log(env, insn, idx, at_in[i], at_stack_in[i], at_out, at_stack_out);
|
||||
|
||||
/* Propagate to successors within this subprogram */
|
||||
|
|
@ -1619,7 +1705,7 @@ static int compute_subprog_args(struct bpf_verifier_env *env,
|
|||
continue;
|
||||
ti = target - start;
|
||||
|
||||
for (r = 0; r < MAX_BPF_REG; r++)
|
||||
for (r = 0; r < MAX_AT_TRACK_REGS; r++)
|
||||
changed |= arg_track_join(env, idx, target, r,
|
||||
&at_in[ti][r], at_out[r]);
|
||||
|
||||
|
|
@ -1674,11 +1760,14 @@ static int compute_subprog_args(struct bpf_verifier_env *env,
|
|||
return err;
|
||||
}
|
||||
|
||||
/* Return true if any of R1-R5 is derived from a frame pointer. */
|
||||
/* Return true if any of R1-R5 or stack args is derived from a frame pointer. */
|
||||
static bool has_fp_args(struct arg_track *args)
|
||||
{
|
||||
for (int r = BPF_REG_1; r <= BPF_REG_5; r++)
|
||||
if (args[r].frame != ARG_NONE)
|
||||
if (arg_is_fp(&args[r]))
|
||||
return true;
|
||||
for (int r = 0; r < MAX_STACK_ARG_SLOTS; r++)
|
||||
if (arg_is_fp(&args[MAX_BPF_REG + r]))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1803,7 +1892,7 @@ static int analyze_subprog(struct bpf_verifier_env *env,
|
|||
/* For each reachable call site in the subprog, recurse into callees */
|
||||
for (int p = po_start; p < po_end; p++) {
|
||||
int idx = env->cfg.insn_postorder[p];
|
||||
struct arg_track callee_args[BPF_REG_5 + 1];
|
||||
struct arg_track callee_args[MAX_AT_TRACK_REGS] = {};
|
||||
struct arg_track none = { .frame = ARG_NONE };
|
||||
struct bpf_insn *insn = &insns[idx];
|
||||
struct func_instance *callee_instance;
|
||||
|
|
@ -1818,9 +1907,11 @@ static int analyze_subprog(struct bpf_verifier_env *env,
|
|||
if (callee < 0)
|
||||
continue;
|
||||
|
||||
/* Build entry args: R1-R5 from at_in at call site */
|
||||
/* Build entry args: R1-R5 and stack args from at_in at call site */
|
||||
for (int r = BPF_REG_1; r <= BPF_REG_5; r++)
|
||||
callee_args[r] = info[subprog].at_in[j][r];
|
||||
for (int r = 0; r < MAX_STACK_ARG_SLOTS; r++)
|
||||
callee_args[MAX_BPF_REG + r] = info[subprog].at_in[j][MAX_BPF_REG + r];
|
||||
} else if (bpf_calls_callback(env, idx)) {
|
||||
callee = find_callback_subprog(env, insn, idx, &caller_reg, &cb_callee_reg);
|
||||
if (callee == -2) {
|
||||
|
|
@ -1842,6 +1933,8 @@ static int analyze_subprog(struct bpf_verifier_env *env,
|
|||
|
||||
for (int r = BPF_REG_1; r <= BPF_REG_5; r++)
|
||||
callee_args[r] = none;
|
||||
for (int r = 0; r < MAX_STACK_ARG_SLOTS; r++)
|
||||
callee_args[MAX_BPF_REG + r] = none;
|
||||
callee_args[cb_callee_reg] = info[subprog].at_in[j][caller_reg];
|
||||
} else {
|
||||
continue;
|
||||
|
|
@ -2085,7 +2178,7 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
|
|||
def = ALL_CALLER_SAVED_REGS;
|
||||
use = def & ~BIT(BPF_REG_0);
|
||||
if (bpf_get_call_summary(env, insn, &cs))
|
||||
use = GENMASK(cs.num_params, 1);
|
||||
use = GENMASK(min_t(u8, cs.num_params, MAX_BPF_FUNC_REG_ARGS), 1);
|
||||
break;
|
||||
default:
|
||||
def = 0;
|
||||
|
|
|
|||
|
|
@ -833,6 +833,32 @@ 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;
|
||||
|
||||
nslots = max(old->out_stack_arg_cnt, cur->out_stack_arg_cnt);
|
||||
for (i = 0; i < nslots; i++) {
|
||||
struct bpf_reg_state *old_arg, *cur_arg;
|
||||
struct bpf_reg_state not_init = { .type = NOT_INIT };
|
||||
|
||||
old_arg = i < old->out_stack_arg_cnt ?
|
||||
&old->stack_arg_regs[i] : ¬_init;
|
||||
cur_arg = i < cur->out_stack_arg_cnt ?
|
||||
&cur->stack_arg_regs[i] : ¬_init;
|
||||
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)
|
||||
{
|
||||
|
|
@ -915,6 +941,9 @@ static bool func_states_equal(struct bpf_verifier_env *env, struct bpf_func_stat
|
|||
if (old->callback_depth > cur->callback_depth)
|
||||
return false;
|
||||
|
||||
if (!old->no_stack_arg_load && cur->no_stack_arg_load)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < MAX_BPF_REG; i++)
|
||||
if (((1 << i) & live_regs) &&
|
||||
!regsafe(env, &old->regs[i], &cur->regs[i],
|
||||
|
|
@ -924,6 +953,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;
|
||||
}
|
||||
|
||||
|
|
@ -1371,7 +1403,7 @@ int bpf_is_state_visited(struct bpf_verifier_env *env, int insn_idx)
|
|||
*/
|
||||
err = 0;
|
||||
if (bpf_is_jmp_point(env, env->insn_idx))
|
||||
err = bpf_push_jmp_history(env, cur, 0, 0);
|
||||
err = bpf_push_jmp_history(env, cur, 0, 0, 0, 0);
|
||||
err = err ? : propagate_precision(env, &sl->state, cur, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -292,6 +292,11 @@ static int arg_from_argno(argno_t a)
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int arg_idx_from_argno(argno_t a)
|
||||
{
|
||||
return arg_from_argno(a) - 1;
|
||||
}
|
||||
|
||||
static const char *btf_type_name(const struct btf *btf, u32 id)
|
||||
{
|
||||
return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off);
|
||||
|
|
@ -1362,6 +1367,18 @@ 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->out_stack_arg_cnt;
|
||||
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->out_stack_arg_cnt = src->out_stack_arg_cnt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1403,6 +1420,23 @@ 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 cnt)
|
||||
{
|
||||
size_t old_n = state->out_stack_arg_cnt;
|
||||
|
||||
if (old_n >= cnt)
|
||||
return 0;
|
||||
|
||||
state->stack_arg_regs = realloc_array(state->stack_arg_regs, old_n, cnt,
|
||||
sizeof(struct bpf_reg_state));
|
||||
if (!state->stack_arg_regs)
|
||||
return -ENOMEM;
|
||||
|
||||
state->out_stack_arg_cnt = cnt;
|
||||
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
|
||||
|
|
@ -1565,6 +1599,7 @@ static void free_func_state(struct bpf_func_state *state)
|
|||
{
|
||||
if (!state)
|
||||
return;
|
||||
kfree(state->stack_arg_regs);
|
||||
kfree(state->stack);
|
||||
kfree(state);
|
||||
}
|
||||
|
|
@ -3168,11 +3203,6 @@ static int check_reg_arg(struct bpf_verifier_env *env, u32 regno,
|
|||
return __check_reg_arg(env, state->regs, regno, t);
|
||||
}
|
||||
|
||||
static int insn_stack_access_flags(int frameno, int spi)
|
||||
{
|
||||
return INSN_F_STACK_ACCESS | (spi << INSN_F_SPI_SHIFT) | frameno;
|
||||
}
|
||||
|
||||
static void mark_indirect_target(struct bpf_verifier_env *env, int idx)
|
||||
{
|
||||
env->insn_aux_data[idx].indirect_target = true;
|
||||
|
|
@ -3410,12 +3440,6 @@ static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
|
|||
src_reg->id = ++env->id_gen;
|
||||
}
|
||||
|
||||
/* Copy src state preserving dst->parent and dst->live fields */
|
||||
static void copy_register_state(struct bpf_reg_state *dst, const struct bpf_reg_state *src)
|
||||
{
|
||||
*dst = *src;
|
||||
}
|
||||
|
||||
static void save_register_state(struct bpf_verifier_env *env,
|
||||
struct bpf_func_state *state,
|
||||
int spi, struct bpf_reg_state *reg,
|
||||
|
|
@ -3423,7 +3447,7 @@ static void save_register_state(struct bpf_verifier_env *env,
|
|||
{
|
||||
int i;
|
||||
|
||||
copy_register_state(&state->stack[spi].spilled_ptr, reg);
|
||||
state->stack[spi].spilled_ptr = *reg;
|
||||
|
||||
for (i = BPF_REG_SIZE; i > BPF_REG_SIZE - size; i--)
|
||||
state->stack[spi].slot_type[i - 1] = STACK_SPILL;
|
||||
|
|
@ -3493,7 +3517,8 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
|
|||
int i, slot = -off - 1, spi = slot / BPF_REG_SIZE, err;
|
||||
struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
|
||||
struct bpf_reg_state *reg = NULL;
|
||||
int insn_flags = insn_stack_access_flags(state->frameno, spi);
|
||||
int insn_flags = INSN_F_STACK_ACCESS;
|
||||
int hist_spi = spi, hist_frame = state->frameno;
|
||||
|
||||
/* caller checked that off % size == 0 and -MAX_BPF_STACK <= off < 0,
|
||||
* so it's aligned access and [off, off + size) are within stack limits
|
||||
|
|
@ -3589,7 +3614,8 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
|
|||
}
|
||||
|
||||
if (insn_flags)
|
||||
return bpf_push_jmp_history(env, env->cur_state, insn_flags, 0);
|
||||
return bpf_push_jmp_history(env, env->cur_state, insn_flags,
|
||||
hist_spi, hist_frame, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -3785,7 +3811,8 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
|
|||
int i, slot = -off - 1, spi = slot / BPF_REG_SIZE;
|
||||
struct bpf_reg_state *reg;
|
||||
u8 *stype, type;
|
||||
int insn_flags = insn_stack_access_flags(reg_state->frameno, spi);
|
||||
int insn_flags = INSN_F_STACK_ACCESS;
|
||||
int hist_spi = spi, hist_frame = reg_state->frameno;
|
||||
|
||||
stype = reg_state->stack[spi].slot_type;
|
||||
reg = ®_state->stack[spi].spilled_ptr;
|
||||
|
|
@ -3822,7 +3849,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
|
|||
* with the destination register on fill.
|
||||
*/
|
||||
assign_scalar_id_before_mov(env, reg);
|
||||
copy_register_state(&state->regs[dst_regno], reg);
|
||||
state->regs[dst_regno] = *reg;
|
||||
state->regs[dst_regno].subreg_def = subreg_def;
|
||||
|
||||
/* Break the relation on a narrowing fill.
|
||||
|
|
@ -3877,7 +3904,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
|
|||
* with the destination register on fill.
|
||||
*/
|
||||
assign_scalar_id_before_mov(env, reg);
|
||||
copy_register_state(&state->regs[dst_regno], reg);
|
||||
state->regs[dst_regno] = *reg;
|
||||
/* mark reg as written since spilled pointer state likely
|
||||
* has its liveness marks cleared by is_state_visited()
|
||||
* which resets stack/reg liveness for state transitions
|
||||
|
|
@ -3916,7 +3943,8 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
|
|||
insn_flags = 0; /* we are not restoring spilled register */
|
||||
}
|
||||
if (insn_flags)
|
||||
return bpf_push_jmp_history(env, env->cur_state, insn_flags, 0);
|
||||
return bpf_push_jmp_history(env, env->cur_state, insn_flags,
|
||||
hist_spi, hist_frame, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -4056,6 +4084,114 @@ static int check_stack_write(struct bpf_verifier_env *env,
|
|||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a value to the outgoing stack arg area.
|
||||
* off is a negative offset from r11 (e.g. -8 for arg6, -16 for arg7).
|
||||
*/
|
||||
static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_state *state,
|
||||
int off, struct bpf_reg_state *value_reg)
|
||||
{
|
||||
int max_stack_arg_regs = MAX_BPF_FUNC_ARGS - MAX_BPF_FUNC_REG_ARGS;
|
||||
struct bpf_subprog_info *subprog = &env->subprog_info[state->subprogno];
|
||||
int spi = -off / BPF_REG_SIZE - 1;
|
||||
struct bpf_reg_state *arg;
|
||||
int err;
|
||||
|
||||
if (spi >= 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, spi + 1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Track the max outgoing stack arg slot count. */
|
||||
if (spi + 1 > subprog->max_out_stack_arg_cnt)
|
||||
subprog->max_out_stack_arg_cnt = spi + 1;
|
||||
|
||||
if (value_reg) {
|
||||
state->stack_arg_regs[spi] = *value_reg;
|
||||
} else {
|
||||
/* BPF_ST: store immediate, treat as scalar */
|
||||
arg = &state->stack_arg_regs[spi];
|
||||
arg->type = SCALAR_VALUE;
|
||||
__mark_reg_known(arg, env->prog->insnsi[env->insn_idx].imm);
|
||||
}
|
||||
state->no_stack_arg_load = true;
|
||||
return bpf_push_jmp_history(env, env->cur_state,
|
||||
INSN_F_STACK_ARG_ACCESS, spi, 0, 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).
|
||||
*/
|
||||
static int check_stack_arg_read(struct bpf_verifier_env *env, struct bpf_func_state *state,
|
||||
int off, int dst_regno)
|
||||
{
|
||||
struct bpf_subprog_info *subprog = &env->subprog_info[state->subprogno];
|
||||
struct bpf_verifier_state *vstate = env->cur_state;
|
||||
int spi = off / BPF_REG_SIZE - 1;
|
||||
struct bpf_func_state *caller, *cur;
|
||||
struct bpf_reg_state *arg;
|
||||
|
||||
if (state->no_stack_arg_load) {
|
||||
verbose(env, "r11 load must be before any r11 store or call insn\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (spi + 1 > bpf_in_stack_arg_cnt(subprog)) {
|
||||
verbose(env, "invalid read from stack arg off %d depth %d\n",
|
||||
off, bpf_in_stack_arg_cnt(subprog) * BPF_REG_SIZE);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
caller = vstate->frame[vstate->curframe - 1];
|
||||
arg = &caller->stack_arg_regs[spi];
|
||||
cur = vstate->frame[vstate->curframe];
|
||||
cur->regs[dst_regno] = *arg;
|
||||
return bpf_push_jmp_history(env, env->cur_state,
|
||||
INSN_F_STACK_ARG_ACCESS, spi, 0, 0);
|
||||
}
|
||||
|
||||
static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)
|
||||
{
|
||||
struct bpf_func_state *caller = cur_func(env);
|
||||
int spi = arg_idx - MAX_BPF_FUNC_REG_ARGS;
|
||||
|
||||
bt_set_frame_stack_arg_slot(&env->bt, caller->frameno, spi);
|
||||
return mark_chain_precision_batch(env, env->cur_state);
|
||||
}
|
||||
|
||||
static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,
|
||||
int nargs)
|
||||
{
|
||||
int i, spi;
|
||||
|
||||
for (i = MAX_BPF_FUNC_REG_ARGS; i < nargs; i++) {
|
||||
spi = i - MAX_BPF_FUNC_REG_ARGS;
|
||||
if (spi >= caller->out_stack_arg_cnt ||
|
||||
caller->stack_arg_regs[spi].type == NOT_INIT) {
|
||||
verbose(env, "callee expects %d args, stack arg%d is not initialized\n",
|
||||
nargs, spi + 1);
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bpf_reg_state *get_func_arg_reg(struct bpf_func_state *caller,
|
||||
struct bpf_reg_state *regs, int arg)
|
||||
{
|
||||
if (arg < MAX_BPF_FUNC_REG_ARGS)
|
||||
return ®s[arg + 1];
|
||||
|
||||
return &caller->stack_arg_regs[arg - MAX_BPF_FUNC_REG_ARGS];
|
||||
}
|
||||
|
||||
static int check_map_access_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
|
||||
int off, int size, enum bpf_access_type type)
|
||||
{
|
||||
|
|
@ -5027,7 +5163,10 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
|
|||
}
|
||||
|
||||
subprog_depth = round_up_stack_depth(env, subprog[idx].stack_depth);
|
||||
if (priv_stack_supported) {
|
||||
if (IS_ENABLED(CONFIG_X86_64) && subprog[idx].stack_arg_cnt) {
|
||||
/* x86-64 uses R9 for both private stack frame pointer and arg6. */
|
||||
subprog[idx].priv_stack_mode = NO_PRIV_STACK;
|
||||
} else if (priv_stack_supported) {
|
||||
/* Request private stack support only if the subprog stack
|
||||
* depth is no less than BPF_PRIV_STACK_MIN_SIZE. This is to
|
||||
* avoid jit penalty if the stack usage is small.
|
||||
|
|
@ -5131,14 +5270,23 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
|
|||
* this info will be utilized by JIT so that we will be preserving the
|
||||
* tail call counter throughout bpf2bpf calls combined with tailcalls
|
||||
*/
|
||||
if (tail_call_reachable)
|
||||
if (tail_call_reachable) {
|
||||
for (tmp = idx; tmp >= 0; tmp = dinfo[tmp].caller) {
|
||||
if (subprog[tmp].is_exception_cb) {
|
||||
verbose(env, "cannot tail call within exception cb\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (subprog[tmp].stack_arg_cnt) {
|
||||
verbose(env, "tail_calls are not allowed in programs with stack args\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
subprog[tmp].tail_call_reachable = true;
|
||||
}
|
||||
} else if (!idx && subprog[0].has_tail_call && subprog[0].stack_arg_cnt) {
|
||||
verbose(env, "tail_calls are not allowed in programs with stack args\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (subprog[0].tail_call_reachable)
|
||||
env->prog->aux->tail_call_reachable = true;
|
||||
|
||||
|
|
@ -6031,7 +6179,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
|
|||
size);
|
||||
return -EACCES;
|
||||
}
|
||||
copy_register_state(®s[value_regno], reg);
|
||||
regs[value_regno] = *reg;
|
||||
add_scalar_to_reg(®s[value_regno], off);
|
||||
regs[value_regno].type = PTR_TO_INSN;
|
||||
} else {
|
||||
|
|
@ -6223,10 +6371,20 @@ 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 read */
|
||||
if (is_stack_arg_ldx(insn)) {
|
||||
err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK);
|
||||
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)
|
||||
|
|
@ -6255,10 +6413,20 @@ 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 (is_stack_arg_stx(insn)) {
|
||||
err = check_reg_arg(env, insn->src_reg, SRC_OP);
|
||||
if (err)
|
||||
return err;
|
||||
return check_stack_arg_write(env, state, insn->off, regs + insn->src_reg);
|
||||
}
|
||||
|
||||
/* check src1 operand */
|
||||
err = check_reg_arg(env, insn->src_reg, SRC_OP);
|
||||
if (err)
|
||||
|
|
@ -6735,8 +6903,14 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
|
|||
}
|
||||
err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
|
||||
access_type, zero_size_allowed, meta);
|
||||
if (!err)
|
||||
err = mark_chain_precision(env, reg_from_argno(size_argno));
|
||||
if (!err) {
|
||||
int regno = reg_from_argno(size_argno);
|
||||
|
||||
if (regno >= 0)
|
||||
err = mark_chain_precision(env, regno);
|
||||
else
|
||||
err = mark_stack_arg_precision(env, arg_idx_from_argno(size_argno));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -7185,7 +7359,7 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
|
|||
struct bpf_kfunc_call_arg_meta *meta)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
u32 arg_idx = arg_from_argno(argno) - 1;
|
||||
u32 arg_idx = arg_idx_from_argno(argno);
|
||||
int spi, err, i, nr_slots, btf_id;
|
||||
|
||||
if (reg->type != PTR_TO_STACK) {
|
||||
|
|
@ -8866,6 +9040,15 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
|
|||
}
|
||||
}
|
||||
|
||||
static void invalidate_outgoing_stack_args(const struct bpf_verifier_env *env,
|
||||
struct bpf_func_state *state)
|
||||
{
|
||||
int i, nslots = state->out_stack_arg_cnt;
|
||||
|
||||
for (i = 0; i < nslots; i++)
|
||||
bpf_mark_reg_not_init(env, &state->stack_arg_regs[i]);
|
||||
}
|
||||
|
||||
typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,
|
||||
struct bpf_func_state *caller,
|
||||
struct bpf_func_state *callee,
|
||||
|
|
@ -8928,6 +9111,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
|
|||
struct bpf_reg_state *regs)
|
||||
{
|
||||
struct bpf_subprog_info *sub = subprog_info(env, subprog);
|
||||
struct bpf_func_state *caller = cur_func(env);
|
||||
struct bpf_verifier_log *log = &env->log;
|
||||
u32 i;
|
||||
int ret;
|
||||
|
|
@ -8936,13 +9120,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = check_outgoing_stack_args(env, caller, sub->arg_cnt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* check that BTF function arguments match actual types that the
|
||||
* verifier sees.
|
||||
*/
|
||||
for (i = 0; i < sub->arg_cnt; i++) {
|
||||
argno_t argno = argno_from_arg(i + 1);
|
||||
u32 regno = i + 1;
|
||||
struct bpf_reg_state *reg = ®s[regno];
|
||||
struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);
|
||||
struct bpf_subprog_arg_info *arg = &sub->args[i];
|
||||
|
||||
if (arg->arg_type == ARG_ANYTHING) {
|
||||
|
|
@ -9130,6 +9317,8 @@ 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;
|
||||
u16 callee_incoming, stack_arg_cnt;
|
||||
struct bpf_func_state *caller;
|
||||
int err, subprog, target_insn;
|
||||
|
||||
|
|
@ -9172,6 +9361,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
/* mark global subprog for verifying after main prog */
|
||||
subprog_aux(env, subprog)->called = true;
|
||||
clear_caller_saved_regs(env, caller->regs);
|
||||
invalidate_outgoing_stack_args(env, cur_func(env));
|
||||
|
||||
/* All non-void global functions return a 64-bit SCALAR_VALUE. */
|
||||
if (!subprog_returns_void(env, subprog)) {
|
||||
|
|
@ -9183,6 +9373,16 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Track caller's total stack arg count (incoming + max outgoing).
|
||||
* This is needed so the JIT knows how much stack arg space to allocate.
|
||||
*/
|
||||
caller_info = &env->subprog_info[caller->subprogno];
|
||||
callee_incoming = bpf_in_stack_arg_cnt(&env->subprog_info[subprog]);
|
||||
stack_arg_cnt = bpf_in_stack_arg_cnt(caller_info) + callee_incoming;
|
||||
if (stack_arg_cnt > caller_info->stack_arg_cnt)
|
||||
caller_info->stack_arg_cnt = stack_arg_cnt;
|
||||
|
||||
/* for regular function entry setup new frame and continue
|
||||
* from that frame.
|
||||
*/
|
||||
|
|
@ -9540,6 +9740,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
|
|||
* bpf_throw, this will be done by copy_verifier_state for extra frames. */
|
||||
free_func_state(callee);
|
||||
state->frame[state->curframe--] = NULL;
|
||||
invalidate_outgoing_stack_args(env, caller);
|
||||
|
||||
/* for callbacks widen imprecise scalars to make programs like below verify:
|
||||
*
|
||||
|
|
@ -10166,6 +10367,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
|
|||
bpf_mark_reg_not_init(env, ®s[caller_saved[i]]);
|
||||
check_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);
|
||||
}
|
||||
invalidate_outgoing_stack_args(env, cur_func(env));
|
||||
|
||||
/* helper call returns 64-bit value. */
|
||||
regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
|
||||
|
|
@ -10970,14 +11172,12 @@ bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta)
|
|||
}
|
||||
|
||||
static enum kfunc_ptr_arg_type
|
||||
get_kfunc_ptr_arg_type(struct bpf_verifier_env *env,
|
||||
struct bpf_kfunc_call_arg_meta *meta,
|
||||
get_kfunc_ptr_arg_type(struct bpf_verifier_env *env, struct bpf_func_state *caller,
|
||||
struct bpf_reg_state *regs, struct bpf_kfunc_call_arg_meta *meta,
|
||||
const struct btf_type *t, const struct btf_type *ref_t,
|
||||
const char *ref_tname, const struct btf_param *args,
|
||||
int arg, int nargs, argno_t argno, struct bpf_reg_state *reg)
|
||||
{
|
||||
u32 regno = arg + 1;
|
||||
struct bpf_reg_state *regs = cur_regs(env);
|
||||
bool arg_mem_size = false;
|
||||
|
||||
if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
|
||||
|
|
@ -10986,8 +11186,8 @@ get_kfunc_ptr_arg_type(struct bpf_verifier_env *env,
|
|||
return KF_ARG_PTR_TO_CTX;
|
||||
|
||||
if (arg + 1 < nargs &&
|
||||
(is_kfunc_arg_mem_size(meta->btf, &args[arg + 1], ®s[regno + 1]) ||
|
||||
is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1], ®s[regno + 1])))
|
||||
(is_kfunc_arg_mem_size(meta->btf, &args[arg + 1], get_func_arg_reg(caller, regs, arg + 1)) ||
|
||||
is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1], get_func_arg_reg(caller, regs, arg + 1))))
|
||||
arg_mem_size = true;
|
||||
|
||||
/* In this function, we verify the kfunc's BTF as per the argument type,
|
||||
|
|
@ -11652,6 +11852,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
int insn_idx)
|
||||
{
|
||||
const char *func_name = meta->func_name, *ref_tname;
|
||||
struct bpf_func_state *caller = cur_func(env);
|
||||
struct bpf_reg_state *regs = cur_regs(env);
|
||||
const struct btf *btf = meta->btf;
|
||||
const struct btf_param *args;
|
||||
struct btf_record *rec;
|
||||
|
|
@ -11660,21 +11862,31 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
|
||||
args = (const struct btf_param *)(meta->func_proto + 1);
|
||||
nargs = btf_type_vlen(meta->func_proto);
|
||||
if (nargs > MAX_BPF_FUNC_REG_ARGS) {
|
||||
if (nargs > MAX_BPF_FUNC_ARGS) {
|
||||
verbose(env, "Function %s has %d > %d args\n", func_name, nargs,
|
||||
MAX_BPF_FUNC_REG_ARGS);
|
||||
MAX_BPF_FUNC_ARGS);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (nargs > MAX_BPF_FUNC_REG_ARGS && !bpf_jit_supports_stack_args()) {
|
||||
verbose(env, "JIT does not support kfunc %s() with %d args\n",
|
||||
func_name, nargs);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
ret = check_outgoing_stack_args(env, caller, nargs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Check that BTF function arguments match actual types that the
|
||||
* verifier sees.
|
||||
*/
|
||||
for (i = 0; i < nargs; i++) {
|
||||
struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[i + 1];
|
||||
struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);
|
||||
const struct btf_type *t, *ref_t, *resolve_ret;
|
||||
enum bpf_arg_type arg_type = ARG_DONTCARE;
|
||||
argno_t argno = argno_from_arg(i + 1);
|
||||
u32 regno = i + 1, ref_id, type_size;
|
||||
int regno = reg_from_argno(argno);
|
||||
u32 ref_id, type_size;
|
||||
bool is_ret_buf_sz = false;
|
||||
int kf_arg_type;
|
||||
|
||||
|
|
@ -11684,6 +11896,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc");
|
||||
return -EFAULT;
|
||||
}
|
||||
if (regno < 0) {
|
||||
verbose(env, "%s prog->aux cannot be a stack argument\n",
|
||||
reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
meta->arg_prog = true;
|
||||
cur_aux(env)->arg_prog = regno;
|
||||
continue;
|
||||
|
|
@ -11710,7 +11927,10 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = mark_chain_precision(env, regno);
|
||||
if (regno >= 0)
|
||||
ret = mark_chain_precision(env, regno);
|
||||
else
|
||||
ret = mark_stack_arg_precision(env, i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
meta->arg_constant.found = true;
|
||||
|
|
@ -11735,7 +11955,10 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
}
|
||||
|
||||
meta->r0_size = reg->var_off.value;
|
||||
ret = mark_chain_precision(env, regno);
|
||||
if (regno >= 0)
|
||||
ret = mark_chain_precision(env, regno);
|
||||
else
|
||||
ret = mark_stack_arg_precision(env, i);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -11763,14 +11986,21 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
return -EFAULT;
|
||||
}
|
||||
meta->ref_obj_id = reg->ref_obj_id;
|
||||
if (is_kfunc_release(meta))
|
||||
if (is_kfunc_release(meta)) {
|
||||
if (regno < 0) {
|
||||
verbose(env, "%s release arg cannot be a stack argument\n",
|
||||
reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
meta->release_regno = regno;
|
||||
}
|
||||
}
|
||||
|
||||
ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
|
||||
ref_tname = btf_name_by_offset(btf, ref_t->name_off);
|
||||
|
||||
kf_arg_type = get_kfunc_ptr_arg_type(env, meta, t, ref_t, ref_tname, args, i, nargs, argno, reg);
|
||||
kf_arg_type = get_kfunc_ptr_arg_type(env, caller, regs, meta, t, ref_t, ref_tname,
|
||||
args, i, nargs, argno, reg);
|
||||
if (kf_arg_type < 0)
|
||||
return kf_arg_type;
|
||||
|
||||
|
|
@ -11920,6 +12150,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
dynptr_arg_type |= DYNPTR_TYPE_FILE;
|
||||
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) {
|
||||
dynptr_arg_type |= DYNPTR_TYPE_FILE | OBJ_RELEASE;
|
||||
if (regno < 0) {
|
||||
verbose(env, "%s release arg cannot be a stack argument\n",
|
||||
reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
meta->release_regno = regno;
|
||||
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_clone] &&
|
||||
(dynptr_arg_type & MEM_UNINIT)) {
|
||||
|
|
@ -12074,9 +12309,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
|
|||
break;
|
||||
case KF_ARG_PTR_TO_MEM_SIZE:
|
||||
{
|
||||
struct bpf_reg_state *buff_reg = ®s[regno];
|
||||
struct bpf_reg_state *buff_reg = reg;
|
||||
const struct btf_param *buff_arg = &args[i];
|
||||
struct bpf_reg_state *size_reg = ®s[regno + 1];
|
||||
struct bpf_reg_state *size_reg = get_func_arg_reg(caller, regs, i + 1);
|
||||
const struct btf_param *size_arg = &args[i + 1];
|
||||
argno_t next_argno = argno_from_arg(i + 2);
|
||||
|
||||
|
|
@ -12848,6 +13083,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
bpf_mark_reg_not_init(env, ®s[regno]);
|
||||
regs[regno].subreg_def = DEF_NOT_SUBREG;
|
||||
}
|
||||
invalidate_outgoing_stack_args(env, cur_func(env));
|
||||
|
||||
/* Check return type */
|
||||
t = btf_type_skip_modifiers(desc_btf, meta.func_proto->type, NULL);
|
||||
|
|
@ -12980,8 +13216,18 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
clear_all_pkt_pointers(env);
|
||||
|
||||
nargs = btf_type_vlen(meta.func_proto);
|
||||
if (nargs > MAX_BPF_FUNC_REG_ARGS) {
|
||||
struct bpf_func_state *caller = cur_func(env);
|
||||
struct bpf_subprog_info *caller_info = &env->subprog_info[caller->subprogno];
|
||||
u16 out_stack_arg_cnt = nargs - MAX_BPF_FUNC_REG_ARGS;
|
||||
u16 stack_arg_cnt = bpf_in_stack_arg_cnt(caller_info) + out_stack_arg_cnt;
|
||||
|
||||
if (stack_arg_cnt > caller_info->stack_arg_cnt)
|
||||
caller_info->stack_arg_cnt = stack_arg_cnt;
|
||||
}
|
||||
|
||||
args = (const struct btf_param *)(meta.func_proto + 1);
|
||||
for (i = 0; i < nargs; i++) {
|
||||
for (i = 0; i < min_t(int, nargs, MAX_BPF_FUNC_REG_ARGS); i++) {
|
||||
u32 regno = i + 1;
|
||||
|
||||
t = btf_type_skip_modifiers(desc_btf, args[i].type, NULL);
|
||||
|
|
@ -13248,7 +13494,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
|
|||
*/
|
||||
if (!ptr_is_dst_reg) {
|
||||
tmp = *dst_reg;
|
||||
copy_register_state(dst_reg, ptr_reg);
|
||||
*dst_reg = *ptr_reg;
|
||||
}
|
||||
err = sanitize_speculative_path(env, NULL, env->insn_idx + 1, env->insn_idx);
|
||||
if (err < 0)
|
||||
|
|
@ -14698,7 +14944,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
|||
* copy register state to dest reg
|
||||
*/
|
||||
assign_scalar_id_before_mov(env, src_reg);
|
||||
copy_register_state(dst_reg, src_reg);
|
||||
*dst_reg = *src_reg;
|
||||
dst_reg->subreg_def = DEF_NOT_SUBREG;
|
||||
} else {
|
||||
/* case: R1 = (s8, s16 s32)R2 */
|
||||
|
|
@ -14713,7 +14959,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
|||
no_sext = reg_umax(src_reg) < (1ULL << (insn->off - 1));
|
||||
if (no_sext)
|
||||
assign_scalar_id_before_mov(env, src_reg);
|
||||
copy_register_state(dst_reg, src_reg);
|
||||
*dst_reg = *src_reg;
|
||||
if (!no_sext)
|
||||
clear_scalar_id(dst_reg);
|
||||
coerce_reg_to_size_sx(dst_reg, insn->off >> 3);
|
||||
|
|
@ -14735,7 +14981,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
|||
|
||||
if (is_src_reg_u32)
|
||||
assign_scalar_id_before_mov(env, src_reg);
|
||||
copy_register_state(dst_reg, src_reg);
|
||||
*dst_reg = *src_reg;
|
||||
/* Make sure ID is cleared if src_reg is not in u32
|
||||
* range otherwise dst_reg min/max could be incorrectly
|
||||
* propagated into src_reg by sync_linked_regs()
|
||||
|
|
@ -14749,7 +14995,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
|||
|
||||
if (no_sext)
|
||||
assign_scalar_id_before_mov(env, src_reg);
|
||||
copy_register_state(dst_reg, src_reg);
|
||||
*dst_reg = *src_reg;
|
||||
if (!no_sext)
|
||||
clear_scalar_id(dst_reg);
|
||||
dst_reg->subreg_def = env->insn_idx + 1;
|
||||
|
|
@ -15629,7 +15875,7 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
|
|||
reg->delta == known_reg->delta) {
|
||||
s32 saved_subreg_def = reg->subreg_def;
|
||||
|
||||
copy_register_state(reg, known_reg);
|
||||
*reg = *known_reg;
|
||||
reg->subreg_def = saved_subreg_def;
|
||||
} else {
|
||||
s32 saved_subreg_def = reg->subreg_def;
|
||||
|
|
@ -15640,7 +15886,7 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
|
|||
__mark_reg_known(&fake_reg, (s64)reg->delta - (s64)known_reg->delta);
|
||||
|
||||
/* reg = known_reg; reg += delta */
|
||||
copy_register_state(reg, known_reg);
|
||||
*reg = *known_reg;
|
||||
/*
|
||||
* Must preserve off, id and subreg_def flag,
|
||||
* otherwise another sync_linked_regs() will be incorrect.
|
||||
|
|
@ -15737,16 +15983,16 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
|
|||
}
|
||||
|
||||
if (insn_flags) {
|
||||
err = bpf_push_jmp_history(env, this_branch, insn_flags, 0);
|
||||
err = bpf_push_jmp_history(env, this_branch, insn_flags, 0, 0, 0);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
is_jmp32 = BPF_CLASS(insn->code) == BPF_JMP32;
|
||||
copy_register_state(&env->false_reg1, dst_reg);
|
||||
copy_register_state(&env->false_reg2, src_reg);
|
||||
copy_register_state(&env->true_reg1, dst_reg);
|
||||
copy_register_state(&env->true_reg2, src_reg);
|
||||
env->false_reg1 = *dst_reg;
|
||||
env->false_reg2 = *src_reg;
|
||||
env->true_reg1 = *dst_reg;
|
||||
env->true_reg2 = *src_reg;
|
||||
pred = is_branch_taken(env, dst_reg, src_reg, opcode, is_jmp32);
|
||||
if (pred >= 0) {
|
||||
/* If we get here with a dst_reg pointer type it is because
|
||||
|
|
@ -15801,7 +16047,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
|
|||
if (dst_reg->type == SCALAR_VALUE && dst_reg->id)
|
||||
collect_linked_regs(env, this_branch, dst_reg->id, &linked_regs);
|
||||
if (linked_regs.cnt > 1) {
|
||||
err = bpf_push_jmp_history(env, this_branch, 0, linked_regs_pack(&linked_regs));
|
||||
err = bpf_push_jmp_history(env, this_branch, 0, 0, 0, linked_regs_pack(&linked_regs));
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
|
@ -15815,11 +16061,11 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
copy_register_state(dst_reg, &env->false_reg1);
|
||||
copy_register_state(src_reg, &env->false_reg2);
|
||||
copy_register_state(&other_branch_regs[insn->dst_reg], &env->true_reg1);
|
||||
*dst_reg = env->false_reg1;
|
||||
*src_reg = env->false_reg2;
|
||||
other_branch_regs[insn->dst_reg] = env->true_reg1;
|
||||
if (BPF_SRC(insn->code) == BPF_X)
|
||||
copy_register_state(&other_branch_regs[insn->src_reg], &env->true_reg2);
|
||||
other_branch_regs[insn->src_reg] = env->true_reg2;
|
||||
|
||||
if (BPF_SRC(insn->code) == BPF_X &&
|
||||
src_reg->type == SCALAR_VALUE && src_reg->id &&
|
||||
|
|
@ -16967,6 +17213,14 @@ 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 (is_stack_arg_st(insn)) {
|
||||
struct bpf_verifier_state *vstate = env->cur_state;
|
||||
struct bpf_func_state *state = vstate->frame[vstate->curframe];
|
||||
|
||||
return check_stack_arg_write(env, state, insn->off, NULL);
|
||||
}
|
||||
|
||||
enum bpf_reg_type dst_reg_type;
|
||||
|
||||
err = check_reg_arg(env, insn->dst_reg, SRC_OP);
|
||||
|
|
@ -17001,6 +17255,8 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
|
|||
}
|
||||
}
|
||||
mark_reg_scratched(env, BPF_REG_0);
|
||||
if (bpf_in_stack_arg_cnt(&env->subprog_info[cur_func(env)->subprogno]))
|
||||
cur_func(env)->no_stack_arg_load = true;
|
||||
if (insn->src_reg == BPF_PSEUDO_CALL)
|
||||
return check_func_call(env, insn, &env->insn_idx);
|
||||
if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL)
|
||||
|
|
@ -17098,7 +17354,7 @@ static int do_check(struct bpf_verifier_env *env)
|
|||
}
|
||||
|
||||
if (bpf_is_jmp_point(env, env->insn_idx)) {
|
||||
err = bpf_push_jmp_history(env, state, 0, 0);
|
||||
err = bpf_push_jmp_history(env, state, 0, 0, 0, 0);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
|
@ -17805,11 +18061,12 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
|
|||
return err;
|
||||
|
||||
for (i = 0; i < insn_cnt; i++, insn++) {
|
||||
if (insn->dst_reg >= MAX_BPF_REG) {
|
||||
if (insn->dst_reg >= MAX_BPF_REG &&
|
||||
!is_stack_arg_st(insn) && !is_stack_arg_stx(insn)) {
|
||||
verbose(env, "R%d is invalid\n", insn->dst_reg);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (insn->src_reg >= MAX_BPF_REG) {
|
||||
if (insn->src_reg >= MAX_BPF_REG && !is_stack_arg_ldx(insn)) {
|
||||
verbose(env, "R%d is invalid\n", insn->src_reg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -18116,7 +18373,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 = ®s[i];
|
||||
|
||||
|
|
@ -18159,6 +18416,12 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
|
|||
goto out;
|
||||
}
|
||||
}
|
||||
if (env->prog->type == BPF_PROG_TYPE_EXT && sub->arg_cnt > MAX_BPF_FUNC_REG_ARGS) {
|
||||
verbose(env, "freplace programs with >%d args not supported yet\n",
|
||||
MAX_BPF_FUNC_REG_ARGS);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
/* if main BPF program has associated BTF info, validate that
|
||||
* it's matching expected signature, and otherwise mark BTF
|
||||
|
|
@ -18166,8 +18429,11 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
|
|||
*/
|
||||
if (env->prog->aux->func_info_aux) {
|
||||
ret = btf_prepare_func_args(env, 0);
|
||||
if (ret || sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_PTR_TO_CTX)
|
||||
if (ret || sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_PTR_TO_CTX) {
|
||||
env->prog->aux->func_info_aux[0].unreliable = true;
|
||||
sub->arg_cnt = 1;
|
||||
sub->stack_arg_cnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 1st arg to a function */
|
||||
|
|
|
|||
139
tools/testing/selftests/bpf/prog_tests/stack_arg.c
Normal file
139
tools/testing/selftests/bpf/prog_tests/stack_arg.c
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <test_progs.h>
|
||||
#include <network_helpers.h>
|
||||
#include "stack_arg.skel.h"
|
||||
#include "stack_arg_kfunc.skel.h"
|
||||
|
||||
static void run_subtest(struct bpf_program *prog, int expected)
|
||||
{
|
||||
int err, prog_fd;
|
||||
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
||||
.data_in = &pkt_v4,
|
||||
.data_size_in = sizeof(pkt_v4),
|
||||
.repeat = 1,
|
||||
);
|
||||
|
||||
prog_fd = bpf_program__fd(prog);
|
||||
err = bpf_prog_test_run_opts(prog_fd, &topts);
|
||||
ASSERT_OK(err, "test_run");
|
||||
ASSERT_EQ(topts.retval, expected, "retval");
|
||||
}
|
||||
|
||||
static void test_global_many(void)
|
||||
{
|
||||
struct stack_arg *skel;
|
||||
|
||||
skel = stack_arg__open();
|
||||
if (!ASSERT_OK_PTR(skel, "open"))
|
||||
return;
|
||||
|
||||
if (!skel->rodata->has_stack_arg) {
|
||||
test__skip();
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!ASSERT_OK(stack_arg__load(skel), "load"))
|
||||
goto out;
|
||||
|
||||
run_subtest(skel->progs.test_global_many_args, 36);
|
||||
|
||||
out:
|
||||
stack_arg__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_async_cb_many(void)
|
||||
{
|
||||
struct stack_arg *skel;
|
||||
|
||||
skel = stack_arg__open();
|
||||
if (!ASSERT_OK_PTR(skel, "open"))
|
||||
return;
|
||||
|
||||
if (!skel->rodata->has_stack_arg) {
|
||||
test__skip();
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!ASSERT_OK(stack_arg__load(skel), "load"))
|
||||
goto out;
|
||||
|
||||
run_subtest(skel->progs.test_async_cb_many_args, 0);
|
||||
|
||||
/* Wait for the timer callback to fire and verify the result.
|
||||
* 10+20+30+40+50+60+70+80 = 360
|
||||
*/
|
||||
usleep(50);
|
||||
ASSERT_EQ(skel->bss->timer_result, 360, "timer_result");
|
||||
|
||||
out:
|
||||
stack_arg__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_bpf2bpf(void)
|
||||
{
|
||||
struct stack_arg *skel;
|
||||
|
||||
skel = stack_arg__open();
|
||||
if (!ASSERT_OK_PTR(skel, "open"))
|
||||
return;
|
||||
|
||||
if (!skel->rodata->has_stack_arg) {
|
||||
test__skip();
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!ASSERT_OK(stack_arg__load(skel), "load"))
|
||||
goto out;
|
||||
|
||||
run_subtest(skel->progs.test_bpf2bpf_ptr_stack_arg, 45);
|
||||
run_subtest(skel->progs.test_bpf2bpf_mix_stack_args, 51);
|
||||
run_subtest(skel->progs.test_bpf2bpf_nesting_stack_arg, 50);
|
||||
run_subtest(skel->progs.test_bpf2bpf_dynptr_stack_arg, 69);
|
||||
run_subtest(skel->progs.test_two_callees, 91);
|
||||
|
||||
out:
|
||||
stack_arg__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_kfunc(void)
|
||||
{
|
||||
struct stack_arg_kfunc *skel;
|
||||
|
||||
skel = stack_arg_kfunc__open();
|
||||
if (!ASSERT_OK_PTR(skel, "open"))
|
||||
return;
|
||||
|
||||
if (!skel->rodata->has_stack_arg) {
|
||||
test__skip();
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!ASSERT_OK(stack_arg_kfunc__load(skel), "load"))
|
||||
goto out;
|
||||
|
||||
run_subtest(skel->progs.test_stack_arg_scalar, 36);
|
||||
run_subtest(skel->progs.test_stack_arg_ptr, 45);
|
||||
run_subtest(skel->progs.test_stack_arg_mix, 51);
|
||||
run_subtest(skel->progs.test_stack_arg_dynptr, 69);
|
||||
run_subtest(skel->progs.test_stack_arg_mem, 151);
|
||||
run_subtest(skel->progs.test_stack_arg_iter, 115);
|
||||
run_subtest(skel->progs.test_stack_arg_const_str, 15);
|
||||
run_subtest(skel->progs.test_stack_arg_timer, 15);
|
||||
|
||||
out:
|
||||
stack_arg_kfunc__destroy(skel);
|
||||
}
|
||||
|
||||
void test_stack_arg(void)
|
||||
{
|
||||
if (test__start_subtest("global_many_args"))
|
||||
test_global_many();
|
||||
if (test__start_subtest("async_cb_many_args"))
|
||||
test_async_cb_many();
|
||||
if (test__start_subtest("bpf2bpf"))
|
||||
test_bpf2bpf();
|
||||
if (test__start_subtest("kfunc"))
|
||||
test_kfunc();
|
||||
}
|
||||
10
tools/testing/selftests/bpf/prog_tests/stack_arg_fail.c
Normal file
10
tools/testing/selftests/bpf/prog_tests/stack_arg_fail.c
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <test_progs.h>
|
||||
#include "stack_arg_fail.skel.h"
|
||||
|
||||
void test_stack_arg_fail(void)
|
||||
{
|
||||
RUN_TESTS(stack_arg_fail);
|
||||
}
|
||||
10
tools/testing/selftests/bpf/prog_tests/stack_arg_precision.c
Normal file
10
tools/testing/selftests/bpf/prog_tests/stack_arg_precision.c
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <test_progs.h>
|
||||
#include "stack_arg_precision.skel.h"
|
||||
|
||||
void test_stack_arg_precision(void)
|
||||
{
|
||||
RUN_TESTS(stack_arg_precision);
|
||||
}
|
||||
|
|
@ -91,6 +91,8 @@
|
|||
#include "verifier_sockmap_mutate.skel.h"
|
||||
#include "verifier_spill_fill.skel.h"
|
||||
#include "verifier_spin_lock.skel.h"
|
||||
#include "verifier_stack_arg.skel.h"
|
||||
#include "verifier_stack_arg_order.skel.h"
|
||||
#include "verifier_stack_ptr.skel.h"
|
||||
#include "verifier_store_release.skel.h"
|
||||
#include "verifier_subprog_precision.skel.h"
|
||||
|
|
@ -238,6 +240,8 @@ void test_verifier_sock_addr(void) { RUN(verifier_sock_addr); }
|
|||
void test_verifier_sockmap_mutate(void) { RUN(verifier_sockmap_mutate); }
|
||||
void test_verifier_spill_fill(void) { RUN(verifier_spill_fill); }
|
||||
void test_verifier_spin_lock(void) { RUN(verifier_spin_lock); }
|
||||
void test_verifier_stack_arg(void) { RUN(verifier_stack_arg); }
|
||||
void test_verifier_stack_arg_order(void) { RUN(verifier_stack_arg_order); }
|
||||
void test_verifier_stack_ptr(void) { RUN(verifier_stack_ptr); }
|
||||
void test_verifier_store_release(void) { RUN(verifier_store_release); }
|
||||
void test_verifier_subprog_precision(void) { RUN(verifier_subprog_precision); }
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@
|
|||
#define __auxiliary __test_tag("test_auxiliary")
|
||||
#define __auxiliary_unpriv __test_tag("test_auxiliary_unpriv")
|
||||
#define __btf_path(path) __test_tag("test_btf_path=" path)
|
||||
#define __btf_func_path(path) __test_tag("test_btf_func_path=" path)
|
||||
#define __arch(arch) __test_tag("test_arch=" arch)
|
||||
#define __arch_x86_64 __arch("X86_64")
|
||||
#define __arch_arm64 __arch("ARM64")
|
||||
|
|
|
|||
24
tools/testing/selftests/bpf/progs/btf__stack_arg_precision.c
Normal file
24
tools/testing/selftests/bpf/progs/btf__stack_arg_precision.c
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "../test_kmods/bpf_testmod_kfunc.h"
|
||||
|
||||
#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
|
||||
defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
long subprog_call_mem_kfunc(long a, long b, long c, long d, long e, long size)
|
||||
{
|
||||
char buf[8] = {};
|
||||
|
||||
return bpf_kfunc_call_stack_arg_mem(a, b, c, d, e, buf, size);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
long subprog_call_mem_kfunc(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
|
||||
defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
int subprog_bad_order_6args(int a, int b, int c, int d, int e, int f)
|
||||
{
|
||||
return a + b + c + d + e + f;
|
||||
}
|
||||
|
||||
int subprog_call_before_load_6args(int a, int b, int c, int d, int e, int f)
|
||||
{
|
||||
return a + b + c + d + e + f;
|
||||
}
|
||||
|
||||
int subprog_pruning_call_before_load_6args(int a, int b, int c, int d, int e, int f)
|
||||
{
|
||||
return a + b + c + d + e + f;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int subprog_bad_order_6args(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int subprog_call_before_load_6args(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int subprog_pruning_call_before_load_6args(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
253
tools/testing/selftests/bpf/progs/stack_arg.c
Normal file
253
tools/testing/selftests/bpf/progs/stack_arg.c
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <stdbool.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_kfuncs.h"
|
||||
|
||||
#define CLOCK_MONOTONIC 1
|
||||
|
||||
struct timer_elem {
|
||||
struct bpf_timer timer;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, int);
|
||||
__type(value, struct timer_elem);
|
||||
} timer_map SEC(".maps");
|
||||
|
||||
int timer_result;
|
||||
|
||||
#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
|
||||
defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
const volatile bool has_stack_arg = true;
|
||||
|
||||
__noinline static int static_func_many_args(int a, int b, int c, int d,
|
||||
int e, int f, int g, int h)
|
||||
{
|
||||
return a + b + c + d + e + f + g + h;
|
||||
}
|
||||
|
||||
__noinline int global_calls_many_args(int a, int b, int c)
|
||||
{
|
||||
return static_func_many_args(a, b, c, 4, 5, 6, 7, 8);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_global_many_args(void)
|
||||
{
|
||||
return global_calls_many_args(1, 2, 3);
|
||||
}
|
||||
|
||||
struct test_data {
|
||||
long x;
|
||||
long y;
|
||||
};
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 + 10 + 20 = 45 */
|
||||
__noinline static long func_with_ptr_stack_arg(long a, long b, long c, long d,
|
||||
long e, struct test_data *p)
|
||||
{
|
||||
return a + b + c + d + e + p->x + p->y;
|
||||
}
|
||||
|
||||
__noinline long global_ptr_stack_arg(long a, long b, long c, long d, long e)
|
||||
{
|
||||
struct test_data data = { .x = 10, .y = 20 };
|
||||
|
||||
return func_with_ptr_stack_arg(a, b, c, d, e, &data);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_ptr_stack_arg(void)
|
||||
{
|
||||
return global_ptr_stack_arg(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 + 10 + 6 + 20 = 51 */
|
||||
__noinline static long func_with_mix_stack_args(long a, long b, long c, long d,
|
||||
long e, struct test_data *p,
|
||||
long f, struct test_data *q)
|
||||
{
|
||||
return a + b + c + d + e + p->x + f + q->y;
|
||||
}
|
||||
|
||||
__noinline long global_mix_stack_args(long a, long b, long c, long d, long e)
|
||||
{
|
||||
struct test_data p = { .x = 10 };
|
||||
struct test_data q = { .y = 20 };
|
||||
|
||||
return func_with_mix_stack_args(a, b, c, d, e, &p, e + 1, &q);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_mix_stack_args(void)
|
||||
{
|
||||
return global_mix_stack_args(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
/*
|
||||
* Nesting test: func_outer calls func_inner, both with struct pointer
|
||||
* as stack arg.
|
||||
*
|
||||
* func_inner: (a+1) + (b+1) + (c+1) + (d+1) + (e+1) + p->x + p->y
|
||||
* = 2 + 3 + 4 + 5 + 6 + 10 + 20 = 50
|
||||
*/
|
||||
__noinline static long func_inner_ptr(long a, long b, long c, long d,
|
||||
long e, struct test_data *p)
|
||||
{
|
||||
return a + b + c + d + e + p->x + p->y;
|
||||
}
|
||||
|
||||
__noinline static long func_outer_ptr(long a, long b, long c, long d,
|
||||
long e, struct test_data *p)
|
||||
{
|
||||
return func_inner_ptr(a + 1, b + 1, c + 1, d + 1, e + 1, p);
|
||||
}
|
||||
|
||||
__noinline long global_nesting_ptr(long a, long b, long c, long d, long e)
|
||||
{
|
||||
struct test_data data = { .x = 10, .y = 20 };
|
||||
|
||||
return func_outer_ptr(a, b, c, d, e, &data);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_nesting_stack_arg(void)
|
||||
{
|
||||
return global_nesting_ptr(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 + sizeof(pkt_v4) = 15 + 54 = 69 */
|
||||
__noinline static long func_with_dynptr(long a, long b, long c, long d,
|
||||
long e, struct bpf_dynptr *ptr)
|
||||
{
|
||||
return a + b + c + d + e + bpf_dynptr_size(ptr);
|
||||
}
|
||||
|
||||
__noinline long global_dynptr_stack_arg(void *ctx __arg_ctx, long a, long b,
|
||||
long c, long d)
|
||||
{
|
||||
struct bpf_dynptr ptr;
|
||||
|
||||
bpf_dynptr_from_skb(ctx, 0, &ptr);
|
||||
return func_with_dynptr(a, b, c, d, d + 1, &ptr);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_dynptr_stack_arg(struct __sk_buff *skb)
|
||||
{
|
||||
return global_dynptr_stack_arg(skb, 1, 2, 3, 4);
|
||||
}
|
||||
|
||||
/* foo1: a+b+c+d+e+f+g+h */
|
||||
__noinline static int foo1(int a, int b, int c, int d,
|
||||
int e, int f, int g, int h)
|
||||
{
|
||||
return a + b + c + d + e + f + g + h;
|
||||
}
|
||||
|
||||
/* foo2: a+b+c+d+e+f+g+h+i+j */
|
||||
__noinline static int foo2(int a, int b, int c, int d, int e,
|
||||
int f, int g, int h, int i, int j)
|
||||
{
|
||||
return a + b + c + d + e + f + g + h + i + j;
|
||||
}
|
||||
|
||||
/* global_two_callees calls foo1 (3 stack args) and foo2 (5 stack args).
|
||||
* The outgoing stack arg area is sized for foo2 (the larger callee).
|
||||
* Stores for foo1 are a subset of the area used by foo2.
|
||||
* Result: foo1(1,2,3,4,5,6,7,8) + foo2(1,2,3,4,5,6,7,8,9,10) = 36 + 55 = 91
|
||||
*
|
||||
* Pass a-e through so the compiler can't constant-fold the stack args away.
|
||||
*/
|
||||
__noinline int global_two_callees(int a, int b, int c, int d, int e)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = foo1(a, b, c, d, e, a + 5, a + 6, a + 7);
|
||||
ret += foo2(a, b, c, d, e, a + 5, a + 6, a + 7, a + 8, a + 9);
|
||||
return ret;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_two_callees(void)
|
||||
{
|
||||
return global_two_callees(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
static int timer_cb_many_args(void *map, int *key, struct bpf_timer *timer)
|
||||
{
|
||||
timer_result = static_func_many_args(10, 20, 30, 40, 50, 60, 70, 80);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_async_cb_many_args(void)
|
||||
{
|
||||
struct timer_elem *elem;
|
||||
int key = 0;
|
||||
|
||||
elem = bpf_map_lookup_elem(&timer_map, &key);
|
||||
if (!elem)
|
||||
return -1;
|
||||
|
||||
bpf_timer_init(&elem->timer, &timer_map, CLOCK_MONOTONIC);
|
||||
bpf_timer_set_callback(&elem->timer, timer_cb_many_args);
|
||||
bpf_timer_start(&elem->timer, 1, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
const volatile bool has_stack_arg = false;
|
||||
|
||||
SEC("tc")
|
||||
int test_global_many_args(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_ptr_stack_arg(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_mix_stack_args(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_nesting_stack_arg(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_bpf2bpf_dynptr_stack_arg(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_two_callees(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_async_cb_many_args(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
114
tools/testing/selftests/bpf/progs/stack_arg_fail.c
Normal file
114
tools/testing/selftests/bpf/progs/stack_arg_fail.c
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "../test_kmods/bpf_testmod_kfunc.h"
|
||||
#include "bpf_misc.h"
|
||||
|
||||
#if defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
SEC("tc")
|
||||
__failure __msg("Unrecognized *(R11-8) type STRUCT")
|
||||
int test_stack_arg_big(struct __sk_buff *skb)
|
||||
{
|
||||
struct prog_test_big_arg s = { .a = 1, .b = 2 };
|
||||
|
||||
return bpf_kfunc_call_stack_arg_big(1, 2, 3, 4, 5, s);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("r11 in ALU instruction")
|
||||
__failure __msg("R11 is invalid")
|
||||
__naked void r11_alu_reject(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r11 += 1;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("r11 store with non-DW size")
|
||||
__failure __msg("R11 is invalid")
|
||||
__naked void r11_store_non_dw(void)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u32 *)(r11 - 8) = r1;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("r11 store with unaligned offset")
|
||||
__failure __msg("R11 is invalid")
|
||||
__naked void r11_store_unaligned(void)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u64 *)(r11 - 4) = r1;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("r11 store with positive offset")
|
||||
__failure __msg("R11 is invalid")
|
||||
__naked void r11_store_positive_off(void)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u64 *)(r11 + 8) = r1;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("r11 load with negative offset")
|
||||
__failure __msg("R11 is invalid")
|
||||
__naked void r11_load_negative_off(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = *(u64 *)(r11 - 8);"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("r11 load with non-DW size")
|
||||
__failure __msg("R11 is invalid")
|
||||
__naked void r11_load_non_dw(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = *(u32 *)(r11 + 8);"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("r11 store with zero offset")
|
||||
__failure __msg("R11 is invalid")
|
||||
__naked void r11_store_zero_off(void)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u64 *)(r11 + 0) = r1;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg_fail: not supported, dummy test")
|
||||
__success
|
||||
int test_stack_arg_big(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
164
tools/testing/selftests/bpf/progs/stack_arg_kfunc.c
Normal file
164
tools/testing/selftests/bpf/progs/stack_arg_kfunc.c
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_kfuncs.h"
|
||||
#include "../test_kmods/bpf_testmod_kfunc.h"
|
||||
|
||||
#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
|
||||
defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
const volatile bool has_stack_arg = true;
|
||||
|
||||
struct bpf_iter_testmod_seq {
|
||||
u64 :64;
|
||||
u64 :64;
|
||||
};
|
||||
|
||||
extern int bpf_iter_testmod_seq_new(struct bpf_iter_testmod_seq *it, s64 value, int cnt) __ksym;
|
||||
extern void bpf_iter_testmod_seq_destroy(struct bpf_iter_testmod_seq *it) __ksym;
|
||||
|
||||
struct timer_map_value {
|
||||
struct bpf_timer timer;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, int);
|
||||
__type(value, struct timer_map_value);
|
||||
} kfunc_timer_map SEC(".maps");
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_scalar(struct __sk_buff *skb)
|
||||
{
|
||||
return bpf_kfunc_call_stack_arg(1, 2, 3, 4, 5, 6, 7, 8);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_ptr(struct __sk_buff *skb)
|
||||
{
|
||||
struct prog_test_pass1 p = { .x0 = 10, .x1 = 20 };
|
||||
|
||||
return bpf_kfunc_call_stack_arg_ptr(1, 2, 3, 4, 5, &p);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_mix(struct __sk_buff *skb)
|
||||
{
|
||||
struct prog_test_pass1 p = { .x0 = 10 };
|
||||
struct prog_test_pass1 q = { .x1 = 20 };
|
||||
|
||||
return bpf_kfunc_call_stack_arg_mix(1, 2, 3, 4, 5, &p, 6, &q);
|
||||
}
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 + sizeof(pkt_v4) = 15 + 54 = 69 */
|
||||
SEC("tc")
|
||||
int test_stack_arg_dynptr(struct __sk_buff *skb)
|
||||
{
|
||||
struct bpf_dynptr ptr;
|
||||
|
||||
bpf_dynptr_from_skb(skb, 0, &ptr);
|
||||
return bpf_kfunc_call_stack_arg_dynptr(1, 2, 3, 4, 5, &ptr);
|
||||
}
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 + (1 + 2 + ... + 16) = 15 + 136 = 151 */
|
||||
SEC("tc")
|
||||
int test_stack_arg_mem(struct __sk_buff *skb)
|
||||
{
|
||||
char buf[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
|
||||
return bpf_kfunc_call_stack_arg_mem(1, 2, 3, 4, 5, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 + 100 = 115 */
|
||||
SEC("tc")
|
||||
int test_stack_arg_iter(struct __sk_buff *skb)
|
||||
{
|
||||
struct bpf_iter_testmod_seq it;
|
||||
u64 ret;
|
||||
|
||||
bpf_iter_testmod_seq_new(&it, 100, 10);
|
||||
ret = bpf_kfunc_call_stack_arg_iter(1, 2, 3, 4, 5, &it);
|
||||
bpf_iter_testmod_seq_destroy(&it);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char cstr[] = "hello";
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 = 15 */
|
||||
SEC("tc")
|
||||
int test_stack_arg_const_str(struct __sk_buff *skb)
|
||||
{
|
||||
return bpf_kfunc_call_stack_arg_const_str(1, 2, 3, 4, 5, cstr);
|
||||
}
|
||||
|
||||
/* 1 + 2 + 3 + 4 + 5 = 15 */
|
||||
SEC("tc")
|
||||
int test_stack_arg_timer(struct __sk_buff *skb)
|
||||
{
|
||||
struct timer_map_value *val;
|
||||
int key = 0;
|
||||
|
||||
val = bpf_map_lookup_elem(&kfunc_timer_map, &key);
|
||||
if (!val)
|
||||
return 0;
|
||||
return bpf_kfunc_call_stack_arg_timer(1, 2, 3, 4, 5, &val->timer);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
const volatile bool has_stack_arg = false;
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_scalar(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_ptr(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_mix(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_dynptr(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_mem(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_iter(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_const_str(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
int test_stack_arg_timer(struct __sk_buff *skb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
135
tools/testing/selftests/bpf/progs/stack_arg_precision.c
Normal file
135
tools/testing/selftests/bpf/progs/stack_arg_precision.c
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "../test_kmods/bpf_testmod_kfunc.h"
|
||||
#include "bpf_misc.h"
|
||||
|
||||
#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
|
||||
defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
/* Force kfunc extern BTF generation for inline asm call below.
|
||||
* Uses its own SEC so it's not included as a .text subprog.
|
||||
* The '?' prefix sets autoload=false so libbpf won't load it.
|
||||
*/
|
||||
SEC("?tc")
|
||||
int __btf_kfunc_gen(struct __sk_buff *ctx)
|
||||
{
|
||||
char buf[8] = {};
|
||||
|
||||
return bpf_kfunc_call_stack_arg_mem(0, 0, 0, 0, 0, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test precision backtracking across bpf-to-bpf call for kfunc stack arg.
|
||||
* subprog_call_mem_kfunc receives a size as incoming stack arg (arg6)
|
||||
* and forwards it as mem__sz (arg7) to bpf_kfunc_call_stack_arg_mem.
|
||||
*/
|
||||
__naked __noinline __used
|
||||
static long subprog_call_mem_kfunc(long a, long b, long c, long d, long e, long size)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = *(u64 *)(r11 + 8);" /* r1 = incoming arg6 (size) */
|
||||
"r2 = 0x0807060504030201 ll;" /* r2 = buf contents */
|
||||
"*(u64 *)(r10 - 8) = r2;" /* store buf to stack */
|
||||
"r2 = r10;"
|
||||
"r2 += -8;" /* r2 = &buf */
|
||||
"*(u64 *)(r11 - 8) = r2;" /* outgoing arg6 = buf */
|
||||
"*(u64 *)(r11 - 16) = r1;" /* outgoing arg7 = size */
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call %[bpf_kfunc_call_stack_arg_mem];"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(bpf_kfunc_call_stack_arg_mem)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: precision backtracking across bpf2bpf call for kfunc")
|
||||
__success
|
||||
__log_level(2)
|
||||
__flag(BPF_F_TEST_STATE_FREQ)
|
||||
__btf_func_path("btf__stack_arg_precision.bpf.o")
|
||||
__msg("mark_precise: frame1: last_idx 26 first_idx 13 subseq_idx -1")
|
||||
__msg("mark_precise: frame1: regs= stack= before 25: (b7) r5 = 5")
|
||||
__msg("mark_precise: frame1: regs= stack= before 24: (b7) r4 = 4")
|
||||
__msg("mark_precise: frame1: regs= stack= before 23: (b7) r3 = 3")
|
||||
__msg("mark_precise: frame1: regs= stack= before 22: (b7) r2 = 2")
|
||||
__msg("mark_precise: frame1: regs= stack= before 21: (b7) r1 = 1")
|
||||
__msg("mark_precise: frame1: regs= stack= before 20: (7b) *(u64 *)(r11 -16) = r1")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 19: (7b) *(u64 *)(r11 -8) = r2")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 18: (07) r2 += -8")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 17: (bf) r2 = r10")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 16: (7b) *(u64 *)(r10 -8) = r2")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 14: (18) r2 = 0x807060504030201")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 13: (79) r1 = *(u64 *)(r11 +8)")
|
||||
__msg("mark_precise: frame1: parent state regs= stack=: frame1: R10=fp0")
|
||||
__msg("mark_precise: frame0: parent state regs= stack=: R10=fp0")
|
||||
__msg("mark_precise: frame1: last_idx 11 first_idx 11 subseq_idx 13")
|
||||
__msg("mark_precise: frame1: regs= stack= before 11: (85) call pc+1")
|
||||
__msg("mark_precise: frame0: parent state regs= stack=: R1=1 R2=2 R3=3 R4=4 R5=5 R10=fp0")
|
||||
__msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx 11")
|
||||
__msg("mark_precise: frame0: regs= stack= before 9: (05) goto pc+1")
|
||||
__msg("mark_precise: frame0: regs= stack= before 8: (7a) *(u64 *)(r11 -8) = 4")
|
||||
__msg("mark_precise: frame1: last_idx 26 first_idx 13 subseq_idx -1 ")
|
||||
__msg("mark_precise: frame1: regs= stack= before 25: (b7) r5 = 5")
|
||||
__msg("mark_precise: frame1: regs= stack= before 24: (b7) r4 = 4")
|
||||
__msg("mark_precise: frame1: regs= stack= before 23: (b7) r3 = 3")
|
||||
__msg("mark_precise: frame1: regs= stack= before 22: (b7) r2 = 2")
|
||||
__msg("mark_precise: frame1: regs= stack= before 21: (b7) r1 = 1")
|
||||
__msg("mark_precise: frame1: regs= stack= before 20: (7b) *(u64 *)(r11 -16) = r1")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 19: (7b) *(u64 *)(r11 -8) = r2")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 18: (07) r2 += -8")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 17: (bf) r2 = r10")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 16: (7b) *(u64 *)(r10 -8) = r2")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 14: (18) r2 = 0x807060504030201")
|
||||
__msg("mark_precise: frame1: regs=r1 stack= before 13: (79) r1 = *(u64 *)(r11 +8)")
|
||||
__msg("mark_precise: frame1: parent state regs= stack=: frame1: R10=fp0")
|
||||
__msg("mark_precise: frame0: parent state regs= stack=: R10=fp0")
|
||||
__msg("mark_precise: frame1: last_idx 11 first_idx 11 subseq_idx 13 ")
|
||||
__msg("mark_precise: frame1: regs= stack= before 11: (85) call pc+1")
|
||||
__msg("mark_precise: frame0: parent state regs= stack=: R1=1 R2=2 R3=3 R4=4 R5=5 R10=fp0")
|
||||
__msg("mark_precise: frame0: last_idx 10 first_idx 10 subseq_idx 11 ")
|
||||
__msg("mark_precise: frame0: regs= stack= before 10: (7a) *(u64 *)(r11 -8) = 6")
|
||||
__naked void stack_arg_precision_bpf2bpf(void)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"r6 = r0;"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"if r6 < 2 goto l0_%=;"
|
||||
"*(u64 *)(r11 - 8) = 4;"
|
||||
"goto l1_%=;"
|
||||
"l0_%=:"
|
||||
"*(u64 *)(r11 - 8) = 6;"
|
||||
"l1_%=:"
|
||||
"call subprog_call_mem_kfunc;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
SEC("socket")
|
||||
__description("stack_arg_precision: not supported, dummy test")
|
||||
__success
|
||||
int dummy_test(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
|
@ -9,7 +9,7 @@ __success __retval(0)
|
|||
__arch_x86_64
|
||||
__jited(" addq %gs:{{.*}}, %rax")
|
||||
__arch_arm64
|
||||
__jited(" mrs x7, SP_EL0")
|
||||
__jited(" mrs x8, SP_EL0")
|
||||
int inline_bpf_get_current_task(void)
|
||||
{
|
||||
bpf_get_current_task();
|
||||
|
|
|
|||
|
|
@ -274,11 +274,11 @@ __jited("movslq 0x10(%rdi,%r12), %r15")
|
|||
__jited("movswq 0x18(%rdi,%r12), %r15")
|
||||
__jited("movsbq 0x20(%rdi,%r12), %r15")
|
||||
__arch_arm64
|
||||
__jited("add x11, x7, x28")
|
||||
__jited("add x11, x8, x28")
|
||||
__jited("ldrsw x21, [x11, #0x10]")
|
||||
__jited("add x11, x7, x28")
|
||||
__jited("add x11, x8, x28")
|
||||
__jited("ldrsh x21, [x11, #0x18]")
|
||||
__jited("add x11, x7, x28")
|
||||
__jited("add x11, x8, x28")
|
||||
__jited("ldrsb x21, [x11, #0x20]")
|
||||
__jited("add x11, x0, x28")
|
||||
__jited("ldrsw x22, [x11, #0x10]")
|
||||
|
|
|
|||
|
|
@ -170,12 +170,12 @@ __jited(" mrs x10, TPIDR_EL{{[0-1]}}")
|
|||
__jited(" add x27, x27, x10")
|
||||
__jited(" add x25, x27, {{.*}}")
|
||||
__jited(" bl 0x{{.*}}")
|
||||
__jited(" mov x7, x0")
|
||||
__jited(" mov x8, x0")
|
||||
__jited(" mov x0, #0x2a")
|
||||
__jited(" str x0, [x27]")
|
||||
__jited(" bl 0x{{.*}}")
|
||||
__jited(" mov x7, x0")
|
||||
__jited(" mov x7, #0x0")
|
||||
__jited(" mov x8, x0")
|
||||
__jited(" mov x8, #0x0")
|
||||
__jited(" ldp x25, x27, [sp], {{.*}}")
|
||||
__naked void private_stack_callback(void)
|
||||
{
|
||||
|
|
@ -220,7 +220,7 @@ __jited(" mov x0, #0x2a")
|
|||
__jited(" str x0, [x27]")
|
||||
__jited(" mov x0, #0x0")
|
||||
__jited(" bl 0x{{.*}}")
|
||||
__jited(" mov x7, x0")
|
||||
__jited(" mov x8, x0")
|
||||
__jited(" ldp x27, x28, [sp], #0x10")
|
||||
int private_stack_exception_main_prog(void)
|
||||
{
|
||||
|
|
@ -258,7 +258,7 @@ __jited(" add x25, x27, {{.*}}")
|
|||
__jited(" mov x0, #0x2a")
|
||||
__jited(" str x0, [x27]")
|
||||
__jited(" bl 0x{{.*}}")
|
||||
__jited(" mov x7, x0")
|
||||
__jited(" mov x8, x0")
|
||||
__jited(" ldp x27, x28, [sp], #0x10")
|
||||
int private_stack_exception_sub_prog(void)
|
||||
{
|
||||
|
|
|
|||
445
tools/testing/selftests/bpf/progs/verifier_stack_arg.c
Normal file
445
tools/testing/selftests/bpf/progs/verifier_stack_arg.c
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_misc.h"
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, long long);
|
||||
__type(value, long long);
|
||||
} map_hash_8b SEC(".maps");
|
||||
|
||||
#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
|
||||
defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
__noinline __used
|
||||
static int subprog_6args(int a, int b, int c, int d, int e, int f)
|
||||
{
|
||||
return a + b + c + d + e + f;
|
||||
}
|
||||
|
||||
__noinline __used
|
||||
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 long subprog_deref_arg6(long a, long b, long c, long d, long e, long *f)
|
||||
{
|
||||
return *f;
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: subprog with 6 args")
|
||||
__success __retval(21)
|
||||
__naked void stack_arg_6args(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 8) = 6;"
|
||||
"call subprog_6args;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: two subprogs with >5 args")
|
||||
__success __retval(90)
|
||||
__naked void stack_arg_two_subprogs(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 8) = 10;"
|
||||
"call subprog_6args;"
|
||||
"r6 = r0;"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 16) = 30;"
|
||||
"*(u64 *)(r11 - 8) = 20;"
|
||||
"call subprog_7args;"
|
||||
"r0 += r6;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: read from uninitialized stack arg slot")
|
||||
__failure
|
||||
__msg("invalid read from stack arg off 8 depth 0")
|
||||
__naked void stack_arg_read_uninitialized(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = *(u64 *)(r11 + 8);"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: gap at offset -8, only wrote -16")
|
||||
__failure
|
||||
__msg("callee expects 7 args, stack arg1 is not initialized")
|
||||
__naked void stack_arg_gap_at_minus8(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 16) = 30;"
|
||||
"call subprog_7args;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: pruning with different stack arg types")
|
||||
__failure
|
||||
__flag(BPF_F_TEST_STATE_FREQ)
|
||||
__msg("R{{[0-9]}} invalid mem access 'scalar'")
|
||||
__naked void stack_arg_pruning_type_mismatch(void)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"r6 = r0;"
|
||||
/* local = 0 on program stack */
|
||||
"r7 = 0;"
|
||||
"*(u64 *)(r10 - 8) = r7;"
|
||||
/* Branch based on random value */
|
||||
"if r6 s> 3 goto l0_%=;"
|
||||
/* Path 1: store stack pointer to outgoing arg6 */
|
||||
"r1 = r10;"
|
||||
"r1 += -8;"
|
||||
"*(u64 *)(r11 - 8) = r1;"
|
||||
"goto l1_%=;"
|
||||
"l0_%=:"
|
||||
/* Path 2: store scalar to outgoing arg6 */
|
||||
"*(u64 *)(r11 - 8) = 42;"
|
||||
"l1_%=:"
|
||||
/* Call subprog that dereferences arg6 */
|
||||
"r1 = r6;"
|
||||
"r2 = 0;"
|
||||
"r3 = 0;"
|
||||
"r4 = 0;"
|
||||
"r5 = 0;"
|
||||
"call subprog_deref_arg6;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: release_reference invalidates stack arg slot")
|
||||
__failure
|
||||
__msg("R{{[0-9]}} !read_ok")
|
||||
__naked void stack_arg_release_ref(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r6 = r1;"
|
||||
/* struct bpf_sock_tuple tuple = {} */
|
||||
"r2 = 0;"
|
||||
"*(u32 *)(r10 - 8) = r2;"
|
||||
"*(u64 *)(r10 - 16) = r2;"
|
||||
"*(u64 *)(r10 - 24) = r2;"
|
||||
"*(u64 *)(r10 - 32) = r2;"
|
||||
"*(u64 *)(r10 - 40) = r2;"
|
||||
"*(u64 *)(r10 - 48) = r2;"
|
||||
/* sk = bpf_sk_lookup_tcp(ctx, &tuple, sizeof(tuple), 0, 0) */
|
||||
"r1 = r6;"
|
||||
"r2 = r10;"
|
||||
"r2 += -48;"
|
||||
"r3 = %[sizeof_bpf_sock_tuple];"
|
||||
"r4 = 0;"
|
||||
"r5 = 0;"
|
||||
"call %[bpf_sk_lookup_tcp];"
|
||||
/* r0 = sk (PTR_TO_SOCK_OR_NULL) */
|
||||
"if r0 == 0 goto l0_%=;"
|
||||
/* Store sock ref to outgoing arg6 slot */
|
||||
"*(u64 *)(r11 - 8) = r0;"
|
||||
/* Release the reference — invalidates the stack arg slot */
|
||||
"r1 = r0;"
|
||||
"call %[bpf_sk_release];"
|
||||
/* Call subprog that dereferences arg6 — should fail */
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call subprog_deref_arg6;"
|
||||
"l0_%=:"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(bpf_sk_lookup_tcp),
|
||||
__imm(bpf_sk_release),
|
||||
__imm_const(sizeof_bpf_sock_tuple, sizeof(struct bpf_sock_tuple))
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: pkt pointer in stack arg slot invalidated after pull_data")
|
||||
__failure
|
||||
__msg("R{{[0-9]}} !read_ok")
|
||||
__naked void stack_arg_stale_pkt_ptr(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r6 = r1;"
|
||||
"r7 = *(u32 *)(r6 + %[__sk_buff_data]);"
|
||||
"r8 = *(u32 *)(r6 + %[__sk_buff_data_end]);"
|
||||
/* check pkt has at least 1 byte */
|
||||
"r0 = r7;"
|
||||
"r0 += 8;"
|
||||
"if r0 > r8 goto l0_%=;"
|
||||
/* Store valid pkt pointer to outgoing arg6 slot */
|
||||
"*(u64 *)(r11 - 8) = r7;"
|
||||
/* bpf_skb_pull_data invalidates all pkt pointers */
|
||||
"r1 = r6;"
|
||||
"r2 = 0;"
|
||||
"call %[bpf_skb_pull_data];"
|
||||
/* Call subprog that dereferences arg6 — should fail */
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call subprog_deref_arg6;"
|
||||
"l0_%=:"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(bpf_skb_pull_data),
|
||||
__imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
|
||||
__imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: null propagation rejects deref on null branch")
|
||||
__failure
|
||||
__msg("R{{[0-9]}} invalid mem access 'scalar'")
|
||||
__naked void stack_arg_null_propagation_fail(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 0;"
|
||||
"*(u64 *)(r10 - 8) = r1;"
|
||||
/* r0 = bpf_map_lookup_elem(&map_hash_8b, &key) */
|
||||
"r2 = r10;"
|
||||
"r2 += -8;"
|
||||
"r1 = %[map_hash_8b] ll;"
|
||||
"call %[bpf_map_lookup_elem];"
|
||||
/* Store PTR_TO_MAP_VALUE_OR_NULL to outgoing arg6 slot */
|
||||
"*(u64 *)(r11 - 8) = r0;"
|
||||
/* null check on r0 */
|
||||
"if r0 != 0 goto l0_%=;"
|
||||
/*
|
||||
* On null branch, outgoing slot is SCALAR(0).
|
||||
* Call subprog that dereferences arg6 — should fail.
|
||||
*/
|
||||
"r1 = 0;"
|
||||
"r2 = 0;"
|
||||
"r3 = 0;"
|
||||
"r4 = 0;"
|
||||
"r5 = 0;"
|
||||
"call subprog_deref_arg6;"
|
||||
"l0_%=:"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(bpf_map_lookup_elem),
|
||||
__imm_addr(map_hash_8b)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: missing store on one branch")
|
||||
__failure
|
||||
__msg("callee expects 7 args, stack arg1 is not initialized")
|
||||
__naked void stack_arg_missing_store_one_branch(void)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
/* Write arg7 (r11-16) before branch */
|
||||
"*(u64 *)(r11 - 16) = 20;"
|
||||
"if r0 > 0 goto l0_%=;"
|
||||
/* Path 1: write arg6 and call */
|
||||
"*(u64 *)(r11 - 8) = 10;"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call subprog_7args;"
|
||||
"goto l1_%=;"
|
||||
"l0_%=:"
|
||||
/* Path 2: missing arg6 store, call should fail */
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call subprog_7args;"
|
||||
"l1_%=:"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: share a store for both branches")
|
||||
__success __retval(0)
|
||||
__naked void stack_arg_shared_store(void)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
/* Write arg7 (r11-16) before branch */
|
||||
"*(u64 *)(r11 - 16) = 20;"
|
||||
"if r0 > 0 goto l0_%=;"
|
||||
/* Path 1: write arg6 and call */
|
||||
"*(u64 *)(r11 - 8) = 10;"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call subprog_7args;"
|
||||
"goto l1_%=;"
|
||||
"l0_%=:"
|
||||
/* Path 2: also write arg6 and call */
|
||||
"*(u64 *)(r11 - 8) = 30;"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call subprog_7args;"
|
||||
"l1_%=:"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: write beyond max outgoing depth")
|
||||
__failure
|
||||
__msg("stack arg write offset -80 exceeds max 7 stack args")
|
||||
__naked void stack_arg_write_beyond_max(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
/* Write to offset -80, way beyond any callee's needs */
|
||||
"*(u64 *)(r11 - 80) = 99;"
|
||||
"*(u64 *)(r11 - 16) = 20;"
|
||||
"*(u64 *)(r11 - 8) = 10;"
|
||||
"call subprog_7args;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: write unused stack arg slot")
|
||||
__failure
|
||||
__msg("func#0 writes 5 stack arg slots, but calls only require 2")
|
||||
__naked void stack_arg_write_unused_slot(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
/* Write to offset -40, unused for the callee */
|
||||
"*(u64 *)(r11 - 40) = 99;"
|
||||
"*(u64 *)(r11 - 16) = 20;"
|
||||
"*(u64 *)(r11 - 8) = 10;"
|
||||
"call subprog_7args;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: sequential calls reuse slots")
|
||||
__failure
|
||||
__msg("callee expects 7 args, stack arg1 is not initialized")
|
||||
__naked void stack_arg_sequential_calls(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 8) = 6;"
|
||||
"*(u64 *)(r11 - 16) = 7;"
|
||||
"call subprog_7args;"
|
||||
"r6 = r0;"
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"call subprog_7args;"
|
||||
"r0 += r6;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
SEC("socket")
|
||||
__description("stack_arg is not supported by compiler or jit, use a dummy test")
|
||||
__success
|
||||
int dummy_test(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
127
tools/testing/selftests/bpf/progs/verifier_stack_arg_order.c
Normal file
127
tools/testing/selftests/bpf/progs/verifier_stack_arg_order.c
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_misc.h"
|
||||
|
||||
#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
|
||||
defined(__BPF_FEATURE_STACK_ARGUMENT)
|
||||
|
||||
__noinline __used __naked
|
||||
static int subprog_bad_order_6args(int a, int b, int c, int d, int e, int f)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u64 *)(r11 - 8) = r1;"
|
||||
"r0 = *(u64 *)(r11 + 8);"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: r11 load after r11 store")
|
||||
__failure
|
||||
__msg("r11 load must be before any r11 store or call insn")
|
||||
__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
|
||||
__naked void stack_arg_load_after_store(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 8) = 6;"
|
||||
"call subprog_bad_order_6args;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
__noinline __used __naked
|
||||
static int subprog_call_before_load_6args(int a, int b, int c, int d, int e,
|
||||
int f)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"r0 = *(u64 *)(r11 + 8);"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: r11 load after a call")
|
||||
__failure
|
||||
__msg("r11 load must be before any r11 store or call insn")
|
||||
__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
|
||||
__naked void stack_arg_load_after_call(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 1;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 8) = 6;"
|
||||
"call subprog_call_before_load_6args;"
|
||||
"exit;"
|
||||
::: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
__noinline __used __naked
|
||||
static int subprog_pruning_call_before_load_6args(int a, int b, int c, int d,
|
||||
int e, int f)
|
||||
{
|
||||
asm volatile (
|
||||
"if r1 s> 0 goto l0_%=;"
|
||||
"goto l1_%=;"
|
||||
"l0_%=:"
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"l1_%=:"
|
||||
"r0 = *(u64 *)(r11 + 8);"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: pruning keeps r11 load ordering")
|
||||
__failure
|
||||
__flag(BPF_F_TEST_STATE_FREQ)
|
||||
__msg("r11 load must be before any r11 store or call insn")
|
||||
__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
|
||||
__naked void stack_arg_pruning_load_after_call(void)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"r1 = r0;"
|
||||
"r2 = 2;"
|
||||
"r3 = 3;"
|
||||
"r4 = 4;"
|
||||
"r5 = 5;"
|
||||
"*(u64 *)(r11 - 8) = 6;"
|
||||
"call subprog_pruning_call_before_load_6args;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
SEC("socket")
|
||||
__description("stack_arg order is not supported by compiler or jit, use a dummy test")
|
||||
__success
|
||||
int dummy_test(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
|
@ -825,6 +825,69 @@ __bpf_kfunc int bpf_kfunc_call_test5(u8 a, u16 b, u32 c)
|
|||
return 0;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg(u64 a, u64 b, u64 c, u64 d,
|
||||
u64 e, u64 f, u64 g, u64 h)
|
||||
{
|
||||
return a + b + c + d + e + f + g + h;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_ptr(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
struct prog_test_pass1 *p)
|
||||
{
|
||||
return a + b + c + d + e + p->x0 + p->x1;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_mix(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
struct prog_test_pass1 *p, u64 f,
|
||||
struct prog_test_pass1 *q)
|
||||
{
|
||||
return a + b + c + d + e + p->x0 + f + q->x1;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_dynptr(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
struct bpf_dynptr *ptr)
|
||||
{
|
||||
const struct bpf_dynptr_kern *kern_ptr = (void *)ptr;
|
||||
|
||||
return a + b + c + d + e + (kern_ptr->size & 0xFFFFFF);
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_mem(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
void *mem, int mem__sz)
|
||||
{
|
||||
const unsigned char *p = mem;
|
||||
u64 sum = a + b + c + d + e;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < mem__sz; i++)
|
||||
sum += p[i];
|
||||
return sum;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_iter(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
struct bpf_iter_testmod_seq *it__iter)
|
||||
{
|
||||
return a + b + c + d + e + it__iter->value;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_const_str(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
const char *str__str)
|
||||
{
|
||||
return a + b + c + d + e;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_timer(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
struct bpf_timer *timer)
|
||||
{
|
||||
return a + b + c + d + e;
|
||||
}
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_stack_arg_big(u64 a, u64 b, u64 c, u64 d, u64 e,
|
||||
struct prog_test_big_arg s)
|
||||
{
|
||||
return a + b + c + d + e + s.a + s.b;
|
||||
}
|
||||
|
||||
static struct prog_test_ref_kfunc prog_test_struct = {
|
||||
.a = 42,
|
||||
.b = 108,
|
||||
|
|
@ -1288,6 +1351,15 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test2)
|
|||
BTF_ID_FLAGS(func, bpf_kfunc_call_test3)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_test4)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_test5)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_ptr)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_mix)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_dynptr)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_mem)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_iter)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_const_str)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_timer)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_stack_arg_big)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail1)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail2)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_test_acquire, KF_ACQUIRE | KF_RET_NULL)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ struct prog_test_ref_kfunc {
|
|||
};
|
||||
#endif
|
||||
|
||||
struct bpf_iter_testmod_seq;
|
||||
|
||||
struct prog_test_pass1 {
|
||||
int x0;
|
||||
struct {
|
||||
|
|
@ -48,6 +50,11 @@ struct prog_test_pass2 {
|
|||
} x;
|
||||
};
|
||||
|
||||
struct prog_test_big_arg {
|
||||
__u64 a;
|
||||
__u64 b;
|
||||
};
|
||||
|
||||
struct prog_test_fail1 {
|
||||
void *p;
|
||||
int x;
|
||||
|
|
@ -111,6 +118,25 @@ int bpf_kfunc_call_test2(struct sock *sk, __u32 a, __u32 b) __ksym;
|
|||
struct sock *bpf_kfunc_call_test3(struct sock *sk) __ksym;
|
||||
long bpf_kfunc_call_test4(signed char a, short b, int c, long d) __ksym;
|
||||
int bpf_kfunc_call_test5(__u8 a, __u16 b, __u32 c) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg(__u64 a, __u64 b, __u64 c, __u64 d,
|
||||
__u64 e, __u64 f, __u64 g, __u64 h) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_ptr(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
struct prog_test_pass1 *p) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_mix(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
struct prog_test_pass1 *p, __u64 f,
|
||||
struct prog_test_pass1 *q) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_dynptr(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
struct bpf_dynptr *ptr) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_mem(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
void *mem, int mem__sz) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_iter(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
struct bpf_iter_testmod_seq *it__iter) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_const_str(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
const char *str__str) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_timer(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
struct bpf_timer *timer) __ksym;
|
||||
__u64 bpf_kfunc_call_stack_arg_big(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
|
||||
struct prog_test_big_arg s) __ksym;
|
||||
|
||||
void bpf_kfunc_call_test_pass_ctx(struct __sk_buff *skb) __ksym;
|
||||
void bpf_kfunc_call_test_pass1(struct prog_test_pass1 *p) __ksym;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ struct test_spec {
|
|||
struct test_subspec priv;
|
||||
struct test_subspec unpriv;
|
||||
const char *btf_custom_path;
|
||||
const char *btf_custom_func_path;
|
||||
int log_level;
|
||||
int prog_flags;
|
||||
int mode_mask;
|
||||
|
|
@ -590,6 +591,8 @@ static int parse_test_spec(struct test_loader *tester,
|
|||
jit_on_next_line = true;
|
||||
} else if ((val = str_has_pfx(s, "test_btf_path="))) {
|
||||
spec->btf_custom_path = val;
|
||||
} else if ((val = str_has_pfx(s, "test_btf_func_path="))) {
|
||||
spec->btf_custom_func_path = val;
|
||||
} else if ((val = str_has_pfx(s, "test_caps_unpriv="))) {
|
||||
err = parse_caps(val, &spec->unpriv.caps, "test caps");
|
||||
if (err)
|
||||
|
|
@ -1175,6 +1178,123 @@ static int get_stream(int stream_id, int prog_fd, char *text, size_t text_sz)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fix up the program's BTF using BTF from a separate file.
|
||||
*
|
||||
* For __naked subprogs, clang drops parameter names from BTF. Find FUNC
|
||||
* entries with anonymous parameters and replace their FUNC_PROTO with the
|
||||
* properly-named version from the custom file.
|
||||
*/
|
||||
static int fixup_btf_from_path(struct bpf_object *obj, const char *path)
|
||||
{
|
||||
struct btf *prog_btf, *custom_btf;
|
||||
__u32 i, j, cnt, custom_cnt;
|
||||
int err = 0;
|
||||
|
||||
prog_btf = bpf_object__btf(obj);
|
||||
if (!prog_btf)
|
||||
return 0;
|
||||
|
||||
custom_btf = btf__parse(path, NULL);
|
||||
if (!ASSERT_OK_PTR(custom_btf, "parse_custom_btf"))
|
||||
return -EINVAL;
|
||||
|
||||
cnt = btf__type_cnt(prog_btf);
|
||||
custom_cnt = btf__type_cnt(custom_btf);
|
||||
|
||||
/* Fix up FUNC entries with anonymous params.
|
||||
* Save all data from prog_btf BEFORE calling btf__add_*,
|
||||
* since those calls may reallocate the BTF data buffer
|
||||
* and invalidate any pointers obtained from btf__type_by_id.
|
||||
*/
|
||||
for (i = 1; i < cnt; i++) {
|
||||
const struct btf_type *t = btf__type_by_id(prog_btf, i);
|
||||
const struct btf_type *fp, *custom_t, *custom_fp;
|
||||
const struct btf_param *params, *custom_params;
|
||||
__u32 ret_type_id, vlen;
|
||||
__u32 *prog_param_types = NULL;
|
||||
const char *name;
|
||||
int new_proto_id;
|
||||
|
||||
if (!btf_is_func(t))
|
||||
continue;
|
||||
|
||||
fp = btf__type_by_id(prog_btf, t->type);
|
||||
if (!fp || !btf_is_func_proto(fp) || btf_vlen(fp) == 0)
|
||||
continue;
|
||||
|
||||
/* Check if any param is anonymous */
|
||||
params = btf_params(fp);
|
||||
if (params[0].name_off != 0)
|
||||
continue;
|
||||
|
||||
/* Find matching FUNC by name in custom BTF */
|
||||
name = btf__name_by_offset(prog_btf, t->name_off);
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
for (j = 1; j < custom_cnt; j++) {
|
||||
const char *cname;
|
||||
|
||||
custom_t = btf__type_by_id(custom_btf, j);
|
||||
if (!btf_is_func(custom_t))
|
||||
continue;
|
||||
cname = btf__name_by_offset(custom_btf, custom_t->name_off);
|
||||
if (cname && strcmp(name, cname) == 0)
|
||||
break;
|
||||
}
|
||||
if (j >= custom_cnt)
|
||||
continue;
|
||||
|
||||
custom_fp = btf__type_by_id(custom_btf, custom_t->type);
|
||||
if (!custom_fp || !btf_is_func_proto(custom_fp))
|
||||
continue;
|
||||
|
||||
vlen = btf_vlen(fp);
|
||||
if (vlen != btf_vlen(custom_fp))
|
||||
continue;
|
||||
|
||||
/* Save data before btf__add_* calls invalidate pointers */
|
||||
ret_type_id = fp->type;
|
||||
prog_param_types = malloc(vlen * sizeof(*prog_param_types));
|
||||
if (!prog_param_types) {
|
||||
err = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
for (j = 0; j < vlen; j++)
|
||||
prog_param_types[j] = params[j].type;
|
||||
|
||||
/* Add a new FUNC_PROTO: param names from custom, types from prog */
|
||||
new_proto_id = btf__add_func_proto(prog_btf, ret_type_id);
|
||||
if (new_proto_id < 0) {
|
||||
err = new_proto_id;
|
||||
free(prog_param_types);
|
||||
break;
|
||||
}
|
||||
|
||||
custom_params = btf_params(custom_fp);
|
||||
for (j = 0; j < vlen; j++) {
|
||||
const char *pname;
|
||||
|
||||
pname = btf__name_by_offset(custom_btf, custom_params[j].name_off);
|
||||
err = btf__add_func_param(prog_btf, pname ?: "", prog_param_types[j]);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
free(prog_param_types);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
/* Update the FUNC to point to the new FUNC_PROTO (re-fetch
|
||||
* since btf__add_* may have reallocated the data buffer).
|
||||
*/
|
||||
((struct btf_type *)btf__type_by_id(prog_btf, i))->type = new_proto_id;
|
||||
}
|
||||
|
||||
btf__free(custom_btf);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* this function is forced noinline and has short generic name to look better
|
||||
* in test_progs output (in case of a failure)
|
||||
*/
|
||||
|
|
@ -1231,13 +1351,27 @@ void run_subtest(struct test_loader *tester,
|
|||
}
|
||||
}
|
||||
|
||||
/* Implicitly reset to NULL if next test case doesn't specify */
|
||||
/* Implicitly reset to NULL if next test case doesn't specify.
|
||||
* btf_custom_func_path also serves as btf_custom_path for kfunc resolution.
|
||||
*/
|
||||
open_opts->btf_custom_path = spec->btf_custom_path;
|
||||
if (!open_opts->btf_custom_path)
|
||||
open_opts->btf_custom_path = spec->btf_custom_func_path;
|
||||
|
||||
tobj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, open_opts);
|
||||
if (!ASSERT_OK_PTR(tobj, "obj_open_mem")) /* shouldn't happen */
|
||||
goto subtest_cleanup;
|
||||
|
||||
/* Fix up __naked subprog BTF using a separate file with named params */
|
||||
if (spec->btf_custom_func_path) {
|
||||
err = fixup_btf_from_path(tobj, spec->btf_custom_func_path);
|
||||
if (err) {
|
||||
PRINT_FAIL("failed to fixup BTF from %s: %d\n",
|
||||
spec->btf_custom_func_path, err);
|
||||
goto tobj_cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
bpf_object__for_each_program(tprog_iter, tobj) {
|
||||
spec_iter = &specs[i++];
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user