AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/17 03:20 flow patch-triage
4m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT",
    "DEBUG_INFO_BTF",
    "DYNAMIC_FTRACE"
  ],
  "FocusSymbols": [
    "arch_prepare_bpf_trampoline",
    "bpf_check_attach_target",
    "arch_bpf_trampoline_size",
    "bpf_trampoline_update"
  ],
  "KMSANReasoning": "The patch modifies the x86-64 BPF JIT compiler and the BPF verifier to correctly handle argument placement (including alignment holes and stack/register splits) for 16-byte aligned arguments (such as __int128 or structs containing them) in BPF trampolines.\n\n1. Analysis of memory initialization:\n- In `kernel/bpf/verifier.c`, `flatten_bpf_func_model()` operates on an already initialized `struct btf_func_model` and explicitly sets `arg_size` and `arg_flags` for all slots. In `check_unreliable_func_args()`, `struct btf_func_model m` is passed to `btf_distill_func_proto()` which initializes its fields upon success; on error, `m` is never read.\n- In `arch/x86/net/bpf_jit_comp.c`, `pos_of_slot` is sized to `MAX_BPF_FUNC_ARG_SLOTS` and populated up to `nr_regs` elements by `bpf_jit_place_args()`. All subsequent reads in `get_nr_stack_slots()`, `save_args()`, and `restore_regs()` operate strictly within the initialized `nr_regs` slots.\n- No uninitialized variables or struct paddings are read or leaked to userspace.\n\n2. KMSAN vs. KASAN applicability:\n- The changes do not expose any uninitialized memory reads, info-leaks to user space, or branching on uninitialized data.\n- KMSAN does not instrument runtime JIT-generated machine instructions.\n- Potential defects in this patch would relate to JIT assembly generation (incorrect stack offsets, improper register save/restore, or invalid RSP manipulation), which are caught by standard testing, functional execution, or KASAN if memory corruption or OOB accesses occur.\n\nConsequently, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies the x86-64 BPF JIT trampoline generator (arch/x86/net/bpf_jit_comp.c) and the BPF verifier attach target handling (kernel/bpf/verifier.c). Specifically, it restructures how argument slots are calculated and placed on the stack for tracing trampolines (handling alignment holes, 16-byte aligned arguments like int128/structs, tail call contexts, and \u003e6 arguments), and adds new verification checks (flatten_bpf_func_model and check_unreliable_func_args). These changes touch reachable core BPF logic on amd64 that can be directly exercised via standard bpf() syscalls when attaching tracing/trampoline programs (such as fentry, fexit, and fmod_ret). Fuzzing is warranted to detect potential stack misalignment, off-by-one offsets in generated machine code, or unexpected verifier edge cases.",
  "WorthFuzzing": true
}

1/1 2026/10/17 03:20 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit af5698b3e790e13eb13b56eccd3ea7a12961729d\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Oct 17 03:20:58 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c\nindex 3975a94ac812d..e316387700030 100644\n--- a/arch/x86/net/bpf_jit_comp.c\n+++ b/arch/x86/net/bpf_jit_comp.c\n@@ -2222,6 +2222,16 @@ static const u8 x86_arg_reg[] = {\n \tBPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5, X86_REG_R9,\n };\n \n+static bool x86_arg_on_stack(u8 pos)\n+{\n+\treturn pos \u003e= x86_arg_abi.nr_arg_regs;\n+}\n+\n+static s32 x86_arg_stack_off(u8 pos)\n+{\n+\treturn (pos - x86_arg_abi.nr_arg_regs) * 8;\n+}\n+\n /*\n  * Move the arguments the x86-64 ABI places somewhere other than the argument\n  * slot the BPF calling convention gave them. @stack_base addresses the\n@@ -3752,21 +3762,17 @@ static void clean_stack_garbage(const struct btf_func_model *m,\n \t}\n }\n \n-/* get the count of the regs that are used to pass arguments */\n-static int get_nr_used_regs(const struct btf_func_model *m)\n+/* the eightbytes taken by the on-stack arguments, alignment holes included */\n+static int get_nr_stack_slots(const u8 *pos_of_slot, int nr_slots)\n {\n-\tint i, arg_regs, nr_used_regs = 0;\n+\tint slot;\n \n-\tfor (i = 0; i \u003c min_t(int, m-\u003enr_args, MAX_BPF_FUNC_ARGS); i++) {\n-\t\targ_regs = (m-\u003earg_size[i] + 7) / 8;\n-\t\tif (nr_used_regs + arg_regs \u003c= 6)\n-\t\t\tnr_used_regs += arg_regs;\n-\n-\t\tif (nr_used_regs \u003e= 6)\n-\t\t\tbreak;\n+\tfor (slot = nr_slots - 1; slot \u003e= 0; slot--) {\n+\t\tif (x86_arg_on_stack(pos_of_slot[slot]))\n+\t\t\treturn x86_arg_stack_off(pos_of_slot[slot]) / 8 + 1;\n \t}\n \n-\treturn nr_used_regs;\n+\treturn 0;\n }\n \n /*\n@@ -3796,20 +3802,21 @@ static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base\n }\n \n static void save_args(const struct btf_func_model *m, u8 **prog,\n-\t\t      int stack_size, bool for_call_origin, u32 flags,\n-\t\t      u64 arena_base)\n+\t\t      const u8 *pos_of_slot, int stack_size,\n+\t\t      bool for_call_origin, u32 flags, u64 arena_base)\n {\n-\tint arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;\n+\tint arg_regs, first_off = 0, nr_stack_slots = 0;\n \tbool use_jmp = bpf_trampoline_use_jmp(flags);\n \tint stack_args_off = (use_jmp || (flags \u0026 BPF_TRAMP_F_INDIRECT)) ? 16 : 24;\n-\tint i, j;\n+\tint bargs_off = stack_size;\n+\tint i, j, slot, off, doff;\n \n \t/* Store function arguments to stack.\n \t * For a function that accepts two pointers the sequence will be:\n \t * mov QWORD PTR [rbp-0x10],rdi\n \t * mov QWORD PTR [rbp-0x8],rsi\n \t */\n-\tfor (i = 0; i \u003c min_t(int, m-\u003enr_args, MAX_BPF_FUNC_ARGS); i++) {\n+\tfor (i = 0, slot = 0; i \u003c m-\u003enr_args; i++) {\n \t\tbool arena_arg = arena_base \u0026\u0026 (m-\u003earg_flags[i] \u0026 BTF_FMODEL_ARENA_ARG);\n \t\tbool nullable = m-\u003earg_flags[i] \u0026 BTF_FMODEL_NULLABLE_ARG;\n \n@@ -3831,8 +3838,36 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\n \t\t *\n \t\t * the arg1-5,arg7 will be passed by regs, and arg6 will\n \t\t * by stack.\n+\t\t *\n+\t\t * An argument aligned to 16 bytes, an __int128 or a struct\n+\t\t * holding one, also starts at an even stack slot, which can\n+\t\t * leave a hole before it. bpf_jit_place_args() worked out\n+\t\t * where each eightbyte is.\n \t\t */\n-\t\tif (nr_regs + arg_regs \u003e 6) {\n+\t\tfor (j = 0; j \u003c arg_regs; j++, slot++, bargs_off -= 8) {\n+\t\t\tu8 pos = pos_of_slot[slot];\n+\t\t\tu32 src;\n+\n+\t\t\tif (!x86_arg_on_stack(pos)) {\n+\t\t\t\t/*\n+\t\t\t\t * Only copy the arguments on-stack to\n+\t\t\t\t * current 'stack_size' and ignore the regs,\n+\t\t\t\t * used to prepare the arguments on-stack for\n+\t\t\t\t * origin call.\n+\t\t\t\t */\n+\t\t\t\tif (for_call_origin)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\t/* copy the arguments from regs into stack */\n+\t\t\t\tsrc = x86_arg_reg[pos];\n+\t\t\t\tif (arena_arg) {\n+\t\t\t\t\temit_arena_arg_conv(prog, src, nullable, (u32)arena_base);\n+\t\t\t\t\tsrc = BPF_REG_0;\n+\t\t\t\t}\n+\t\t\t\temit_stx(prog, BPF_DW, BPF_REG_FP, src, -bargs_off);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n \t\t\t/* copy function arguments from origin stack frame\n \t\t\t * into current stack frame.\n \t\t\t *\n@@ -3842,53 +3877,33 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\n \t\t\t * entered through the fentry call, so rbp + 24, and\n \t\t\t * a single one when it is entered with a jmp or\n \t\t\t * called indirectly, so rbp + 16.\n+\t\t\t *\n+\t\t\t * The area for the origin call mirrors the incoming\n+\t\t\t * one, hole and all; only the bpf program takes the\n+\t\t\t * arguments packed.\n \t\t\t */\n-\t\t\tfor (j = 0; j \u003c arg_regs; j++) {\n-\t\t\t\temit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,\n-\t\t\t\t\t nr_stack_slots * 8 + stack_args_off);\n-\t\t\t\tif (arena_arg)\n-\t\t\t\t\temit_arena_arg_conv(prog, BPF_REG_0, nullable,\n-\t\t\t\t\t\t\t    (u32)arena_base);\n-\t\t\t\temit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,\n-\t\t\t\t\t -stack_size);\n-\n-\t\t\t\tif (!nr_stack_slots)\n-\t\t\t\t\tfirst_off = stack_size;\n-\t\t\t\tstack_size -= 8;\n-\t\t\t\tnr_stack_slots++;\n-\t\t\t}\n-\t\t} else {\n-\t\t\t/* Only copy the arguments on-stack to current\n-\t\t\t * 'stack_size' and ignore the regs, used to\n-\t\t\t * prepare the arguments on-stack for origin call.\n-\t\t\t */\n-\t\t\tif (for_call_origin) {\n-\t\t\t\tnr_regs += arg_regs;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\n-\t\t\t/* copy the arguments from regs into stack */\n-\t\t\tfor (j = 0; j \u003c arg_regs; j++) {\n-\t\t\t\tu32 src = nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs;\n-\n-\t\t\t\tif (arena_arg) {\n-\t\t\t\t\temit_arena_arg_conv(prog, src, nullable, (u32)arena_base);\n-\t\t\t\t\tsrc = BPF_REG_0;\n-\t\t\t\t}\n-\t\t\t\temit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);\n-\t\t\t\tstack_size -= 8;\n-\t\t\t\tnr_regs++;\n-\t\t\t}\n+\t\t\toff = x86_arg_stack_off(pos);\n+\t\t\tdoff = for_call_origin ? stack_size - off : bargs_off;\n+\t\t\temit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,\n+\t\t\t\t stack_args_off + off);\n+\t\t\tif (arena_arg)\n+\t\t\t\temit_arena_arg_conv(prog, BPF_REG_0, nullable,\n+\t\t\t\t\t\t    (u32)arena_base);\n+\t\t\temit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -doff);\n+\n+\t\t\tif (!nr_stack_slots)\n+\t\t\t\tfirst_off = doff;\n+\t\t\tnr_stack_slots++;\n \t\t}\n \t}\n \n \tclean_stack_garbage(m, prog, nr_stack_slots, first_off);\n }\n \n-static void restore_regs(const struct btf_func_model *m, u8 **prog,\n+static void restore_regs(u8 **prog, const u8 *pos_of_slot, int nr_slots,\n \t\t\t int stack_size)\n {\n-\tint i, j, arg_regs, nr_regs = 0;\n+\tint slot;\n \n \t/* Restore function arguments from stack.\n \t * For a function that accepts two pointers the sequence will be:\n@@ -3897,23 +3912,10 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog,\n \t *\n \t * The logic here is similar to what we do in save_args()\n \t */\n-\tfor (i = 0; i \u003c min_t(int, m-\u003enr_args, MAX_BPF_FUNC_ARGS); i++) {\n-\t\targ_regs = (m-\u003earg_size[i] + 7) / 8;\n-\t\tif (nr_regs + arg_regs \u003c= 6) {\n-\t\t\tfor (j = 0; j \u003c arg_regs; j++) {\n-\t\t\t\temit_ldx(prog, BPF_DW,\n-\t\t\t\t\t nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,\n-\t\t\t\t\t BPF_REG_FP,\n-\t\t\t\t\t -stack_size);\n-\t\t\t\tstack_size -= 8;\n-\t\t\t\tnr_regs++;\n-\t\t\t}\n-\t\t} else {\n-\t\t\tstack_size -= 8 * arg_regs;\n-\t\t}\n-\n-\t\tif (nr_regs \u003e= 6)\n-\t\t\tbreak;\n+\tfor (slot = 0; slot \u003c nr_slots; slot++, stack_size -= 8) {\n+\t\tif (!x86_arg_on_stack(pos_of_slot[slot]))\n+\t\t\temit_ldx(prog, BPF_DW, x86_arg_reg[pos_of_slot[slot]],\n+\t\t\t\t BPF_REG_FP, -stack_size);\n \t}\n }\n \n@@ -4102,9 +4104,9 @@ static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,\n \treturn 0;\n }\n \n-/* mov rax, qword ptr [rbp - rounded_stack_depth - 8] */\n-#define LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack)\t\\\n-\t__LOAD_TCC_PTR(-round_up(stack, 8) - 8)\n+/* mov rax, qword ptr [rbp - off] */\n+#define LOAD_TRAMP_TAIL_CALL_CNT_PTR(off)\t\\\n+\t__LOAD_TCC_PTR(-(off))\n \n /* Example:\n  * __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev);\n@@ -4172,8 +4174,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t\t\t\t\t struct bpf_tramp_nodes *tnodes,\n \t\t\t\t\t void *func_addr)\n {\n-\tint i, ret, nr_regs = m-\u003enr_args, stack_size = 0;\n+\tint i, ret, nr_regs, stack_size = 0;\n+\tu8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];\n \tint regs_off, func_meta_off, ip_off, run_ctx_off, arg_stack_off, rbx_off;\n+\tint tcc_off = 0;\n \tstruct bpf_tramp_nodes *fentry = \u0026tnodes[BPF_TRAMP_FENTRY];\n \tstruct bpf_tramp_nodes *fexit = \u0026tnodes[BPF_TRAMP_FEXIT];\n \tstruct bpf_tramp_nodes *fmod_ret = \u0026tnodes[BPF_TRAMP_MODIFY_RETURN];\n@@ -4195,12 +4199,12 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \n \tarena_base = bpf_tramp_arena_base(m, tnodes, flags);\n \n-\tfor (i = 0; i \u003c m-\u003enr_args; i++)\n-\t\tnr_regs += (m-\u003earg_size[i] + 7) / 8 - 1;\n-\n-\t/* x86-64 supports up to MAX_BPF_FUNC_ARGS arguments. 1-6\n-\t * are passed through regs, the remains are through stack.\n+\t/*\n+\t * x86-64 supports up to MAX_BPF_FUNC_ARGS argument slots, six in\n+\t * registers and the rest on the stack, where bpf_jit_place_args()\n+\t * puts them.\n \t */\n+\tnr_regs = bpf_jit_place_args(\u0026x86_arg_abi, m, pos_of_slot);\n \tif (nr_regs \u003e MAX_BPF_FUNC_ARGS)\n \t\treturn -ENOTSUPP;\n \n@@ -4212,11 +4216,11 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t * RBP - 8         [ return value    ]  BPF_TRAMP_F_CALL_ORIG or\n \t *                                      BPF_TRAMP_F_RET_FENTRY_RET flags\n \t *\n-\t *                 [ reg_argN        ]  always\n-\t *                 [ ...             ]\n-\t * RBP - regs_off  [ reg_arg1        ]  program's ctx pointer\n+\t *                 [ arg slotN       ]  always, packed, whether\n+\t *                 [ ...             ]  in a register or on the\n+\t * RBP - regs_off  [ arg slot1       ]  stack; program's ctx pointer\n \t *\n-\t * RBP - func_meta_off [ regs count, etc ]  always\n+\t * RBP - func_meta_off [ slot count, etc ]  always\n \t *\n \t * RBP - ip_off    [ traced function ]  BPF_TRAMP_F_IP_ARG flag\n \t *\n@@ -4224,11 +4228,12 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t *\n \t * RBP - run_ctx_off [ bpf_tramp_run_ctx ]\n \t *\n-\t *                     [ stack_argN ]  BPF_TRAMP_F_CALL_ORIG\n-\t *                     [ ...        ]\n-\t *                     [ stack_arg2 ]\n-\t * RBP - arg_stack_off [ stack_arg1 ]\n-\t * RSP                 [ tail_call_cnt_ptr ] BPF_TRAMP_F_TAIL_CALL_CTX\n+\t * RBP - tcc_off   [ tail_call_cnt_ptr ]  BPF_TRAMP_F_TAIL_CALL_CTX\n+\t *\n+\t *                     [ stack slotN ]  BPF_TRAMP_F_CALL_ORIG,\n+\t *                     [ ...         ]  the incoming stack\n+\t *                     [ stack slot2 ]  arguments, alignment\n+\t * RBP - arg_stack_off [ stack slot1 ]  holes included\n \t */\n \n \t/* room for return value of orig_call or fentry prog */\n@@ -4239,7 +4244,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \tstack_size += nr_regs * 8;\n \tregs_off = stack_size;\n \n-\t/* function matedata, such as regs count  */\n+\t/* function matedata, such as the argument slot count */\n \tstack_size += 8;\n \tfunc_meta_off = stack_size;\n \n@@ -4259,9 +4264,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \tstack_size += (sizeof(struct bpf_tramp_run_ctx) + 7) \u0026 ~0x7;\n \trun_ctx_off = stack_size;\n \n+\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX) {\n+\t\tstack_size += 8;\n+\t\ttcc_off = stack_size;\n+\t}\n+\n \tif (nr_regs \u003e 6 \u0026\u0026 (flags \u0026 BPF_TRAMP_F_CALL_ORIG)) {\n \t\t/* the space that used to pass arguments on-stack */\n-\t\tstack_size += (nr_regs - get_nr_used_regs(m)) * 8;\n+\t\tstack_size += get_nr_stack_slots(pos_of_slot, nr_regs) * 8;\n \t\t/* make sure the stack pointer is 16-byte aligned if we\n \t\t * need pass arguments on stack, which means\n \t\t *  [stack_size + 8(rbp) + 8(rip) + 8(origin rip)]\n@@ -4311,12 +4321,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t/* sub rsp, stack_size */\n \temit_sub_rsp(\u0026prog, stack_size);\n \tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX)\n-\t\tEMIT1(0x50);\t\t/* push rax */\n+\t\t/* mov QWORD PTR [rbp - tcc_off], rax */\n+\t\temit_stx(\u0026prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -tcc_off);\n \t/* mov QWORD PTR [rbp - rbx_off], rbx */\n \temit_stx(\u0026prog, BPF_DW, BPF_REG_FP, BPF_REG_6, -rbx_off);\n \n \tfunc_meta = nr_regs;\n-\t/* Store number of argument registers of the traced function */\n+\t/* Store number of argument slots of the traced function */\n \temit_store_stack_imm64(\u0026prog, BPF_REG_0, -func_meta_off, func_meta);\n \n \tif (flags \u0026 BPF_TRAMP_F_IP_ARG) {\n@@ -4324,7 +4335,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t\temit_store_stack_imm64(\u0026prog, BPF_REG_0, -ip_off, (long)func_addr);\n \t}\n \n-\tsave_args(m, \u0026prog, regs_off, false, flags, arena_base);\n+\tsave_args(m, \u0026prog, pos_of_slot, regs_off, false, flags, arena_base);\n \n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n \t\t/* arg1: mov rdi, im */\n@@ -4365,14 +4376,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t}\n \n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n-\t\trestore_regs(m, \u0026prog, regs_off);\n-\t\tsave_args(m, \u0026prog, arg_stack_off, true, flags, 0);\n+\t\trestore_regs(\u0026prog, pos_of_slot, nr_regs, regs_off);\n+\t\tsave_args(m, \u0026prog, pos_of_slot, arg_stack_off, true, flags, 0);\n \n \t\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX) {\n \t\t\t/* Before calling the original function, load the\n \t\t\t * tail_call_cnt_ptr from stack to rax.\n \t\t\t */\n-\t\t\tLOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);\n+\t\t\tLOAD_TRAMP_TAIL_CALL_CNT_PTR(tcc_off);\n \t\t}\n \n \t\tif (flags \u0026 BPF_TRAMP_F_ORIG_STACK) {\n@@ -4419,7 +4430,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t}\n \n \tif (flags \u0026 BPF_TRAMP_F_RESTORE_REGS)\n-\t\trestore_regs(m, \u0026prog, regs_off);\n+\t\trestore_regs(\u0026prog, pos_of_slot, nr_regs, regs_off);\n \n \t/* This needs to be done regardless. If there were fmod_ret programs,\n \t * the return value is only updated on the stack and still needs to be\n@@ -4436,7 +4447,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t\t/* Before running the original function, load the\n \t\t * tail_call_cnt_ptr from stack to rax.\n \t\t */\n-\t\tLOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);\n+\t\tLOAD_TRAMP_TAIL_CALL_CNT_PTR(tcc_off);\n \t}\n \n \t/* restore return value of orig_call or fentry prog back into RAX */\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex ec136cf377c13..6edddb56451d7 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -22006,6 +22006,59 @@ static bool attach_uses_trampoline_retval(enum bpf_attach_type type)\n \t}\n }\n \n+/*\n+ * BPF code passes argument slot N where the kernel convention passes its Nth\n+ * u64 argument, so describe a BPF function to its trampoline as one u64 per\n+ * slot, and every arch places its arguments right.\n+ */\n+static int flatten_bpf_func_model(struct bpf_verifier_log *log, const char *tname,\n+\t\t\t\t  struct btf_func_model *m)\n+{\n+\tu32 i, nslots = 0;\n+\n+\tfor (i = 0; i \u003c m-\u003enr_args; i++)\n+\t\tnslots += btf_func_model_arg_slots(m, i);\n+\tif (nslots \u003e MAX_BPF_FUNC_ARGS) {\n+\t\tbpf_log(log, \"The function %s has %u argument slots. Too many.\\n\",\n+\t\t\ttname, nslots);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tfor (i = 0; i \u003c nslots; i++) {\n+\t\tm-\u003earg_size[i] = sizeof(u64);\n+\t\tm-\u003earg_flags[i] = 0;\n+\t}\n+\tm-\u003enr_args = nslots;\n+\treturn 0;\n+}\n+\n+/*\n+ * The verifier lets a static function use stack arguments counted from its\n+ * BTF, even if that BTF turns out unreliable. A trampoline on the function is\n+ * then given only MAX_BPF_FUNC_REG_ARGS u64 arguments and would lose the\n+ * stack arguments, so such a function cannot be attached to.\n+ */\n+static int check_unreliable_func_args(struct bpf_verifier_log *log, struct btf *btf,\n+\t\t\t\t      const struct btf_type *func_proto, const char *tname)\n+{\n+\tstruct btf_func_model m;\n+\tu32 i, nslots = 0;\n+\n+\tif (btf_distill_func_proto(NULL, btf, func_proto, tname, \u0026m)) {\n+\t\t/* at least a slot for each argument */\n+\t\tnslots = btf_type_vlen(func_proto);\n+\t} else {\n+\t\tfor (i = 0; i \u003c m.nr_args; i++)\n+\t\t\tnslots += btf_func_model_arg_slots(\u0026m, i);\n+\t}\n+\tif (nslots \u003c= MAX_BPF_FUNC_REG_ARGS)\n+\t\treturn 0;\n+\n+\tbpf_log(log, \"Cannot attach to static function %s: unreliable BTF and stack arguments\\n\",\n+\t\ttname);\n+\treturn -EINVAL;\n+}\n+\n int bpf_check_attach_target(struct bpf_verifier_log *log,\n \t\t\t    const struct bpf_prog *prog,\n \t\t\t    const struct bpf_prog *tgt_prog,\n@@ -22273,12 +22326,23 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,\n \t\t     prog-\u003eaux-\u003esaved_dst_attach_type != tgt_prog-\u003eexpected_attach_type))\n \t\t\treturn -EINVAL;\n \n-\t\tif (tgt_prog \u0026\u0026 conservative)\n+\t\tif (tgt_prog \u0026\u0026 conservative) {\n+\t\t\tif (subprog \u003e 0) {\n+\t\t\t\tret = check_unreliable_func_args(log, btf, t, tname);\n+\t\t\t\tif (ret)\n+\t\t\t\t\treturn ret;\n+\t\t\t}\n \t\t\tt = NULL;\n+\t\t}\n \n \t\tret = btf_distill_func_proto(log, btf, t, tname, \u0026tgt_info-\u003efmodel);\n \t\tif (ret \u003c 0)\n \t\t\treturn ret;\n+\t\tif (tgt_prog) {\n+\t\t\tret = flatten_bpf_func_model(log, tname, \u0026tgt_info-\u003efmodel);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\treturn ret;\n+\t\t}\n \n \t\tif (tgt_info-\u003efmodel.ret_size \u003e 8 \u0026\u0026\n \t\t    attach_uses_trampoline_retval(prog-\u003eexpected_attach_type)) {\ndiff --git a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c\nindex 15b95d0235b52..de358f335d98b 100644\n--- a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c\n+++ b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c\n@@ -5,6 +5,10 @@\n #include \"tracing_struct.skel.h\"\n #include \"tracing_struct_many_args.skel.h\"\n #include \"tracing_struct_int128.skel.h\"\n+#include \"tracing_struct_int128_tgt.skel.h\"\n+#include \"tracing_struct_int128_tracer.skel.h\"\n+#include \"tracing_stack_arg_tailcall_tgt.skel.h\"\n+#include \"tracing_stack_arg_tailcall_tracer.skel.h\"\n \n static void test_struct_args(void)\n {\n@@ -117,9 +121,11 @@ static void test_int128_args(void)\n {\n \t/*\n \t * __int128 arguments are passed in a register pair on x86_64 and\n-\t * arm64, which the trampoline packs into two context slots. Other\n-\t * architectures pass a __int128 differently (e.g. s390x passes larger\n-\t * arguments by reference), so only exercise this on x86_64 and arm64.\n+\t * arm64, which the trampoline packs into two context slots. Past the\n+\t * registers, both start an argument aligned to 16 bytes at an even\n+\t * stack slot, which the trampoline packs too. Other architectures\n+\t * pass a __int128 differently (e.g. s390x passes larger arguments by\n+\t * reference), so only exercise this on x86_64 and arm64.\n \t */\n #if defined(__x86_64__) || defined(__aarch64__)\n \tstruct tracing_struct_int128 *skel;\n@@ -139,6 +145,20 @@ static void test_int128_args(void)\n \tASSERT_EQ(skel-\u003ebss-\u003et_c, 3, \"t:c\");\n \tASSERT_EQ(skel-\u003ebss-\u003et_ret, 6, \"t ret\");\n \n+\tASSERT_EQ(skel-\u003ebss-\u003es_g, 7, \"s:g\");\n+\tASSERT_EQ(skel-\u003ebss-\u003es_h_lo, 8, \"s:h.lo\");\n+\tASSERT_EQ(skel-\u003ebss-\u003es_h_hi, 9, \"s:h.hi\");\n+\tASSERT_EQ(skel-\u003ebss-\u003es_i, 10, \"s:i\");\n+\tASSERT_EQ(skel-\u003ebss-\u003es_ret, 385, \"s ret\");\n+\n+\tASSERT_EQ(skel-\u003ebss-\u003eb_f_a, 6, \"b:f.a\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eb_f_b, 7, \"b:f.b\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eb_g, 8, \"b:g\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eb_h, 9, \"b:h\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eb_i_lo, 10, \"b:i.lo\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eb_i_hi, 11, \"b:i.hi\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eb_ret, 506, \"b ret\");\n+\n destroy_skel:\n \ttracing_struct_int128__destroy(skel);\n #else\n@@ -146,6 +166,191 @@ static void test_int128_args(void)\n #endif\n }\n \n+static void test_int128_tgt_prog(void)\n+{\n+#if defined(__x86_64__) || defined(__aarch64__) || defined(__s390x__)\n+\tstruct tracing_struct_int128_tracer *tracer = NULL;\n+\tstruct tracing_struct_int128_tgt *tgt;\n+\tLIBBPF_OPTS(bpf_test_run_opts, topts);\n+\tint err, fd;\n+\n+\ttgt = tracing_struct_int128_tgt__open_and_load();\n+\tif (!ASSERT_OK_PTR(tgt, \"tgt__open_and_load\"))\n+\t\treturn;\n+\tif (!tgt-\u003erodata-\u003ehas_struct_arg) {\n+\t\ttest__skip();\n+\t\tgoto out;\n+\t}\n+\tfd = bpf_program__fd(tgt-\u003eprogs.run);\n+\n+\ttracer = tracing_struct_int128_tracer__open();\n+\tif (!ASSERT_OK_PTR(tracer, \"tracer__open\"))\n+\t\tgoto out;\n+\n+\terr = bpf_program__set_attach_target(tracer-\u003eprogs.odd_entry, fd, \"callee_odd\");\n+\terr = err ?: bpf_program__set_attach_target(tracer-\u003eprogs.odd_exit, fd, \"callee_odd\");\n+\tif (tgt-\u003erodata-\u003ehas_stack_arg) {\n+\t\terr = err ?: bpf_program__set_attach_target(tracer-\u003eprogs.stack_entry, fd,\n+\t\t\t\t\t\t\t     \"callee_stack\");\n+\t\terr = err ?: bpf_program__set_attach_target(tracer-\u003eprogs.stack_exit, fd,\n+\t\t\t\t\t\t\t     \"callee_stack\");\n+\t\terr = err ?: bpf_program__set_attach_target(tracer-\u003eprogs.split_entry, fd,\n+\t\t\t\t\t\t\t     \"callee_split\");\n+\t\terr = err ?: bpf_program__set_attach_target(tracer-\u003eprogs.split_exit, fd,\n+\t\t\t\t\t\t\t     \"callee_split\");\n+\t} else {\n+\t\tbpf_program__set_autoload(tracer-\u003eprogs.stack_entry, false);\n+\t\tbpf_program__set_autoload(tracer-\u003eprogs.stack_exit, false);\n+\t\tbpf_program__set_autoload(tracer-\u003eprogs.split_entry, false);\n+\t\tbpf_program__set_autoload(tracer-\u003eprogs.split_exit, false);\n+\t}\n+\tif (!ASSERT_OK(err, \"set_attach_target\"))\n+\t\tgoto out;\n+\n+\terr = tracing_struct_int128_tracer__load(tracer);\n+\tif (!ASSERT_OK(err, \"tracer__load\"))\n+\t\tgoto out;\n+\terr = tracing_struct_int128_tracer__attach(tracer);\n+\tif (!ASSERT_OK(err, \"tracer__attach\"))\n+\t\tgoto out;\n+\n+\terr = bpf_prog_test_run_opts(fd, \u0026topts);\n+\tif (!ASSERT_OK(err, \"test_run\") || !ASSERT_EQ(topts.retval, 0, \"retval\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(tracer-\u003ebss-\u003eodd_a, 1, \"odd:a\");\n+\tASSERT_EQ(tracer-\u003ebss-\u003eodd_lo, 8, \"odd:v.lo\");\n+\tASSERT_EQ(tracer-\u003ebss-\u003eodd_hi, 9, \"odd:v.hi\");\n+\tASSERT_EQ(tracer-\u003ebss-\u003eodd_b, 2, \"odd:b\");\n+\tASSERT_EQ(tgt-\u003ebss-\u003eret_odd, 52, \"odd ret\");\n+\tASSERT_EQ(tracer-\u003ebss-\u003eodd_ret, 52, \"odd fexit ret\");\n+\n+\tif (tgt-\u003erodata-\u003ehas_stack_arg) {\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003estack_g, 7, \"stack:g\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003estack_lo, 8, \"stack:h.lo\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003estack_hi, 9, \"stack:h.hi\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003estack_i, 10, \"stack:i\");\n+\t\tASSERT_EQ(tgt-\u003ebss-\u003eret_stack, 385, \"stack ret\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003estack_ret, 385, \"stack fexit ret\");\n+\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003esplit_e, 5, \"split:e\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003esplit_lo, 8, \"split:h.lo\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003esplit_hi, 9, \"split:h.hi\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003esplit_i, 10, \"split:i\");\n+\t\tASSERT_EQ(tgt-\u003ebss-\u003eret_split, 246, \"split ret\");\n+\t\tASSERT_EQ(tracer-\u003ebss-\u003esplit_ret, 246, \"split fexit ret\");\n+\t}\n+\n+out:\n+\ttracing_struct_int128_tracer__destroy(tracer);\n+\ttracing_struct_int128_tgt__destroy(tgt);\n+#else\n+\ttest__skip();\n+#endif\n+}\n+\n+/*\n+ * A static function whose BTF is unreliable gets a trampoline that only knows\n+ * of the register arguments, so one taking stack arguments must be refused.\n+ */\n+static void test_tgt_prog_unreliable(void)\n+{\n+#if defined(__x86_64__) || defined(__aarch64__)\n+\tstruct tracing_struct_int128_tracer *tracer = NULL;\n+\tstruct tracing_struct_int128_tgt *tgt;\n+\tchar log_buf[4096] = {};\n+\tstruct bpf_program *prog;\n+\tint err, fd;\n+\n+\ttgt = tracing_struct_int128_tgt__open_and_load();\n+\tif (!ASSERT_OK_PTR(tgt, \"tgt__open_and_load\"))\n+\t\treturn;\n+\tif (!tgt-\u003erodata-\u003ehas_stack_arg) {\n+\t\ttest__skip();\n+\t\tgoto out;\n+\t}\n+\tfd = bpf_program__fd(tgt-\u003eprogs.run);\n+\n+\ttracer = tracing_struct_int128_tracer__open();\n+\tif (!ASSERT_OK_PTR(tracer, \"tracer__open\"))\n+\t\tgoto out;\n+\n+\tbpf_object__for_each_program(prog, tracer-\u003eobj)\n+\t\tbpf_program__set_autoload(prog, false);\n+\tbpf_program__set_autoload(tracer-\u003eprogs.unreliable_entry, true);\n+\tbpf_program__set_log_buf(tracer-\u003eprogs.unreliable_entry, log_buf, sizeof(log_buf));\n+\terr = bpf_program__set_attach_target(tracer-\u003eprogs.unreliable_entry, fd,\n+\t\t\t\t\t     \"callee_unreliable\");\n+\tif (!ASSERT_OK(err, \"set_attach_target\"))\n+\t\tgoto out;\n+\n+\terr = tracing_struct_int128_tracer__load(tracer);\n+\tASSERT_ERR(err, \"tracer__load\");\n+\tASSERT_HAS_SUBSTR(log_buf, \"unreliable BTF and stack arguments\", \"log\");\n+\n+out:\n+\ttracing_struct_int128_tracer__destroy(tracer);\n+\ttracing_struct_int128_tgt__destroy(tgt);\n+#else\n+\ttest__skip();\n+#endif\n+}\n+\n+/*\n+ * A trampoline on a function of a tail_call_reachable program keeps the tail\n+ * call counter pointer on its stack, and has to hand the function its stack\n+ * arguments past it.\n+ */\n+static void test_stack_arg_tailcall(void)\n+{\n+#if defined(__x86_64__) || defined(__aarch64__)\n+\tstruct tracing_stack_arg_tailcall_tracer *tracer = NULL;\n+\tstruct tracing_stack_arg_tailcall_tgt *tgt;\n+\tLIBBPF_OPTS(bpf_test_run_opts, topts);\n+\tint err, fd;\n+\n+\ttgt = tracing_stack_arg_tailcall_tgt__open_and_load();\n+\tif (!ASSERT_OK_PTR(tgt, \"tgt__open_and_load\"))\n+\t\treturn;\n+\tif (!tgt-\u003erodata-\u003ehas_stack_arg) {\n+\t\ttest__skip();\n+\t\tgoto out;\n+\t}\n+\tfd = bpf_program__fd(tgt-\u003eprogs.run);\n+\n+\ttracer = tracing_stack_arg_tailcall_tracer__open();\n+\tif (!ASSERT_OK_PTR(tracer, \"tracer__open\"))\n+\t\tgoto out;\n+\n+\terr = bpf_program__set_attach_target(tracer-\u003eprogs.stack_entry, fd, \"callee_stack\");\n+\terr = err ?: bpf_program__set_attach_target(tracer-\u003eprogs.stack_exit, fd, \"callee_stack\");\n+\tif (!ASSERT_OK(err, \"set_attach_target\"))\n+\t\tgoto out;\n+\n+\terr = tracing_stack_arg_tailcall_tracer__load(tracer);\n+\tif (!ASSERT_OK(err, \"tracer__load\"))\n+\t\tgoto out;\n+\terr = tracing_stack_arg_tailcall_tracer__attach(tracer);\n+\tif (!ASSERT_OK(err, \"tracer__attach\"))\n+\t\tgoto out;\n+\n+\terr = bpf_prog_test_run_opts(fd, \u0026topts);\n+\tif (!ASSERT_OK(err, \"test_run\") || !ASSERT_EQ(topts.retval, 0, \"retval\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(tracer-\u003ebss-\u003estack_g, 7, \"stack:g\");\n+\tASSERT_EQ(tracer-\u003ebss-\u003estack_h, 8, \"stack:h\");\n+\tASSERT_EQ(tgt-\u003ebss-\u003eret_stack, 204, \"stack ret\");\n+\tASSERT_EQ(tracer-\u003ebss-\u003estack_ret, 204, \"stack fexit ret\");\n+\n+out:\n+\ttracing_stack_arg_tailcall_tracer__destroy(tracer);\n+\ttracing_stack_arg_tailcall_tgt__destroy(tgt);\n+#else\n+\ttest__skip();\n+#endif\n+}\n+\n static void test_union_args(void)\n {\n \tstruct tracing_struct *skel;\n@@ -181,6 +386,12 @@ void test_tracing_struct(void)\n \t\ttest_struct_many_args();\n \tif (test__start_subtest(\"int128_args\"))\n \t\ttest_int128_args();\n+\tif (test__start_subtest(\"int128_tgt_prog\"))\n+\t\ttest_int128_tgt_prog();\n+\tif (test__start_subtest(\"tgt_prog_unreliable\"))\n+\t\ttest_tgt_prog_unreliable();\n+\tif (test__start_subtest(\"stack_arg_tailcall\"))\n+\t\ttest_stack_arg_tailcall();\n \tif (test__start_subtest(\"union_args\"))\n \t\ttest_union_args();\n }\ndiff --git a/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c b/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c\nindex 52d3d5d539465..c87e886423f4f 100644\n--- a/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c\n+++ b/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c\n@@ -50,6 +50,27 @@ int aggregate_arg_kfunc_int128(struct __sk_buff *skb)\n \treturn 0;\n }\n \n+/*\n+ * arm64 rounds the register number up to an even one for an argument\n+ * aligned to 16 bytes, so it wants this __int128 in x2 and x3, leaving x1\n+ * empty, and the last argument in x4. The x86-64 ABI has no such rule.\n+ */\n+SEC(\"tc\")\n+__arch_x86_64 __arch_arm64\n+__load_if_JITed()\n+__success __retval(0)\n+int aggregate_arg_kfunc_int128_odd(struct __sk_buff *skb)\n+{\n+\t__u64 a = skb-\u003elen ^ MIX_A;\n+\t__u64 b = skb-\u003elen ^ MIX_B;\n+\tu128 v = ((u128)a \u003c\u003c 64) | b;\n+\n+\tif (bpf_kfunc_call_test_i128_arg_odd(1, v, 2) != 1 + 2 * b + 3 * a + 4 * 2)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n #endif /* __SIZEOF_INT128__ */\n \n #if defined(__clang__) \u0026\u0026 defined(__BPF_FEATURE_STACK_ARGUMENT)\n@@ -131,6 +152,56 @@ int aggregate_arg_kfunc_split8(struct __sk_buff *skb)\n \n #ifdef __SIZEOF_INT128__\n \n+/*\n+ * The hole of aggregate_arg_kfunc_int128_odd, with arguments after it that\n+ * arm64 shifts through x5 to x7, where its JIT keeps the BPF stack slots,\n+ * and onto its stack. The second __int128 then leaves the second arm64\n+ * stack slot empty. x86-64 packs the eightbytes like the BPF convention,\n+ * so nothing moves there.\n+ */\n+SEC(\"tc\")\n+__arch_x86_64 __arch_arm64\n+__load_if_JITed()\n+__success __retval(0)\n+int aggregate_arg_kfunc_int128_odd_many(struct __sk_buff *skb)\n+{\n+\t__u64 a = skb-\u003elen ^ MIX_A;\n+\t__u64 b = skb-\u003elen ^ MIX_B;\n+\tu128 v = ((u128)a \u003c\u003c 64) | b;\n+\tu128 w = ((u128)~a \u003c\u003c 64) | ~b;\n+\n+\tif (bpf_kfunc_call_test_i128_arg_odd_many(1, v, 2, 3, 4, 5, 6, w) !=\n+\t    1 + 2 * b + 3 * a + 4 * 2 + 5 * 3 + 6 * 4 + 7 * 5 + 8 * 6 +\n+\t    9 * ~b + 10 * ~a)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * x86-64 moves s to the stack and gives R9 back to the seventh argument.\n+ * w, aligned to 16 bytes by the __int128 it holds, then leaves a stack slot\n+ * empty before it: the fourth on x86-64, the second on arm64.\n+ */\n+SEC(\"tc\")\n+__arch_x86_64 __arch_arm64\n+__load_if_JITed()\n+__success __retval(0)\n+int aggregate_arg_kfunc_int128_stack_hole(struct __sk_buff *skb)\n+{\n+\t__u64 a = skb-\u003elen ^ MIX_A;\n+\t__u64 b = skb-\u003elen ^ MIX_B;\n+\tstruct prog_test_pair_arg s = { .lo = a, .hi = b };\n+\tstruct prog_test_i128_arg w = { .v = ((u128)~a \u003c\u003c 64) | ~b };\n+\n+\tif (bpf_kfunc_call_test_i128_arg_stack_hole(1, 2, 3, 4, 5, s, 6, 7, w) !=\n+\t    1 + 2 * 2 + 3 * 3 + 4 * 4 + 5 * 5 + 6 * a + 7 * b + 8 * 6 + 9 * 7 +\n+\t    10 * ~b + 11 * ~a)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n /*\n  * Both conventions pad the stack to align this __int128, and the BPF\n  * convention pads for neither, so both JITs move it up an eightbyte.\ndiff --git a/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c\nnew file mode 100644\nindex 0000000000000..5fde710c4e370\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c\n@@ -0,0 +1,59 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_PROG_ARRAY);\n+\t__uint(max_entries, 1);\n+\t__uint(key_size, sizeof(__u32));\n+\t__uint(value_size, sizeof(__u32));\n+} jmp_table SEC(\".maps\");\n+\n+__u64 base;\n+__u64 ret_stack;\n+\n+#if defined(__clang__) \u0026\u0026 defined(__BPF_FEATURE_STACK_ARGUMENT)\n+\n+const volatile bool has_stack_arg = true;\n+\n+/* g and h are on the stack, for the x86-64 kernel convention too. */\n+static __noinline __u64 callee_stack(__u64 a, __u64 b, __u64 c, __u64 d,\n+\t\t\t\t     __u64 e, __u64 f, __u64 g, __u64 h)\n+{\n+\treturn a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 + h * 8;\n+}\n+\n+/* No stack args of its own, so it can share a program with a tail call. */\n+static __noinline __u64 call_stack(__u64 x)\n+{\n+\treturn callee_stack(x + 1, x + 2, x + 3, x + 4, x + 5, x + 6, x + 7,\n+\t\t\t    x + 8);\n+}\n+\n+#else\n+\n+const volatile bool has_stack_arg = false;\n+\n+#endif\n+\n+/* Makes the program tail_call_reachable, though callee_stack() is not. */\n+static __noinline int subprog_tail(void *ctx)\n+{\n+\tint ret = 0;\n+\n+\tbpf_tail_call_static(ctx, \u0026jmp_table, 0);\n+\tbarrier_var(ret);\n+\treturn ret;\n+}\n+\n+SEC(\"syscall\")\n+int run(void *ctx)\n+{\n+#if defined(__clang__) \u0026\u0026 defined(__BPF_FEATURE_STACK_ARGUMENT)\n+\tret_stack = call_stack(base);\n+#endif\n+\treturn subprog_tail(ctx);\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c\nnew file mode 100644\nindex 0000000000000..1800e7b7afe2d\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c\n@@ -0,0 +1,23 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+\n+long stack_g, stack_h, stack_ret;\n+\n+SEC(\"fentry/callee_stack\")\n+int stack_entry(unsigned long long *ctx)\n+{\n+\tstack_g = ctx[6];\n+\tstack_h = ctx[7];\n+\treturn 0;\n+}\n+\n+SEC(\"fexit/callee_stack\")\n+int stack_exit(unsigned long long *ctx)\n+{\n+\tstack_ret = ctx[8];\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c\nindex 4638dfec1f38e..5520ee500c01d 100644\n--- a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c\n+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c\n@@ -15,4 +15,49 @@ int test_int128_arg_fexit(unsigned long long *ctx)\n \treturn 0;\n }\n \n+/*\n+ * The arguments come packed in the BPF convention whatever holes the\n+ * kernel convention left between them: an eightbyte each, and two for the\n+ * struct holding an __int128.\n+ */\n+long s_g, s_h_lo, s_h_hi, s_i, s_ret;\n+\n+SEC(\"fentry/bpf_testmod_test_int128_stack\")\n+int test_int128_stack_fentry(unsigned long long *ctx)\n+{\n+\ts_g = ctx[6];\n+\ts_h_lo = ctx[7];\n+\ts_h_hi = ctx[8];\n+\ts_i = ctx[9];\n+\treturn 0;\n+}\n+\n+SEC(\"fexit/bpf_testmod_test_int128_stack\")\n+int test_int128_stack_fexit(unsigned long long *ctx)\n+{\n+\ts_ret = ctx[10];\n+\treturn 0;\n+}\n+\n+long b_f_a, b_f_b, b_g, b_h, b_i_lo, b_i_hi, b_ret;\n+\n+SEC(\"fentry/bpf_testmod_test_int128_backfill\")\n+int test_int128_backfill_fentry(unsigned long long *ctx)\n+{\n+\tb_f_a = ctx[5];\n+\tb_f_b = ctx[6];\n+\tb_g = ctx[7];\n+\tb_h = ctx[8];\n+\tb_i_lo = ctx[9];\n+\tb_i_hi = ctx[10];\n+\treturn 0;\n+}\n+\n+SEC(\"fexit/bpf_testmod_test_int128_backfill\")\n+int test_int128_backfill_fexit(unsigned long long *ctx)\n+{\n+\tb_ret = ctx[11];\n+\treturn 0;\n+}\n+\n char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c\nnew file mode 100644\nindex 0000000000000..2561422c25dd7\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c\n@@ -0,0 +1,101 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+\n+struct int128_arg {\n+\t__int128 v;\n+};\n+\n+__u64 base;\n+__u64 ret_odd, ret_stack, ret_split, ret_unreliable;\n+\n+#if defined(__clang__)\n+\n+const volatile bool has_struct_arg = true;\n+\n+/* arm64 would want v in x2 and x3, and b in x4. */\n+__noinline __u64 callee_odd(__u64 a, struct int128_arg v, __u64 b)\n+{\n+\treturn a + (__u64)v.v * 2 + (__u64)((unsigned __int128)v.v \u003e\u003e 64) * 3 +\n+\t       b * 4;\n+}\n+\n+#else\n+\n+const volatile bool has_struct_arg = false;\n+\n+#endif\n+\n+#if defined(__clang__) \u0026\u0026 \\\n+\t(defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) \u0026\u0026 \\\n+\tdefined(__BPF_FEATURE_STACK_ARGUMENT)\n+\n+const volatile bool has_stack_arg = true;\n+\n+/*\n+ * x86-64 would want h in its third and fourth stack slots, and arm64 in\n+ * its first and second, leaving x7 empty.\n+ */\n+static __noinline __u64 callee_stack(__u64 a, __u64 b, __u64 c, __u64 d,\n+\t\t\t\t     __u64 e, __u64 f, __u64 g,\n+\t\t\t\t     struct int128_arg h, __u64 i)\n+{\n+\treturn a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 +\n+\t       (__u64)h.v * 8 + (__u64)((unsigned __int128)h.v \u003e\u003e 64) * 9 +\n+\t       i * 10;\n+}\n+\n+/*\n+ * x86-64 BPF code passes h split between r9 and its first stack slot;\n+ * arm64 passes it in x5 and x6, so nothing is split there.\n+ */\n+static __noinline __u64 callee_split(__u64 a, __u64 b, __u64 c, __u64 d,\n+\t\t\t\t     __u64 e, struct int128_arg h, __u64 i)\n+{\n+\treturn a + b * 2 + c * 3 + d * 4 + e * 5 + (__u64)h.v * 6 +\n+\t       (__u64)((unsigned __int128)h.v \u003e\u003e 64) * 7 + i * 8;\n+}\n+\n+/*\n+ * A plain pointer makes the BTF of a static function unreliable, but f is\n+ * still passed on the stack. The store keeps p a pointer.\n+ */\n+static __noinline __u64 callee_unreliable(__u64 *p, __u64 b, __u64 c, __u64 d,\n+\t\t\t\t\t  __u64 e, __u64 f)\n+{\n+\t*p += 1;\n+\treturn *p + b * 2 + c * 3 + d * 4 + e * 5 + f * 6;\n+}\n+\n+#else\n+\n+const volatile bool has_stack_arg = false;\n+\n+#endif\n+\n+SEC(\"syscall\")\n+int run(void *ctx)\n+{\n+#if defined(__clang__)\n+\tstruct int128_arg v = { .v = ((__int128)(base + 9) \u003c\u003c 64) | (base + 8) };\n+\n+\tret_odd = callee_odd(base + 1, v, base + 2);\n+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) \u0026\u0026 \\\n+\tdefined(__BPF_FEATURE_STACK_ARGUMENT)\n+\tret_stack = callee_stack(base + 1, base + 2, base + 3, base + 4,\n+\t\t\t\t base + 5, base + 6, base + 7, v, base + 10);\n+\tret_split = callee_split(base + 1, base + 2, base + 3, base + 4,\n+\t\t\t\t base + 5, v, base + 10);\n+\t{\n+\t\t__u64 x = base;\n+\n+\t\tret_unreliable = callee_unreliable(\u0026x, base + 2, base + 3,\n+\t\t\t\t\t\t   base + 4, base + 5, base + 6);\n+\t}\n+#endif\n+#endif\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c\nnew file mode 100644\nindex 0000000000000..fc81c2eea4a8d\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c\n@@ -0,0 +1,77 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+struct int128_arg {\n+\t__int128 v;\n+};\n+\n+long odd_a, odd_lo, odd_hi, odd_b, odd_ret;\n+\n+SEC(\"fentry/callee_odd\")\n+int BPF_PROG2(odd_entry, __u64, a, struct int128_arg, v, __u64, b)\n+{\n+\todd_a = a;\n+\todd_lo = (__u64)v.v;\n+\todd_hi = (unsigned __int128)v.v \u003e\u003e 64;\n+\todd_b = b;\n+\treturn 0;\n+}\n+\n+SEC(\"fexit/callee_odd\")\n+int odd_exit(unsigned long long *ctx)\n+{\n+\todd_ret = ctx[4];\n+\treturn 0;\n+}\n+\n+long stack_g, stack_lo, stack_hi, stack_i, stack_ret;\n+\n+SEC(\"fentry/callee_stack\")\n+int BPF_PROG2(stack_entry, __u64, a, __u64, b, __u64, c, __u64, d, __u64, e,\n+\t      __u64, f, __u64, g, struct int128_arg, h, __u64, i)\n+{\n+\tstack_g = g;\n+\tstack_lo = (__u64)h.v;\n+\tstack_hi = (unsigned __int128)h.v \u003e\u003e 64;\n+\tstack_i = i;\n+\treturn 0;\n+}\n+\n+SEC(\"fexit/callee_stack\")\n+int stack_exit(unsigned long long *ctx)\n+{\n+\tstack_ret = ctx[10];\n+\treturn 0;\n+}\n+\n+long split_e, split_lo, split_hi, split_i, split_ret;\n+\n+SEC(\"fentry/callee_split\")\n+int BPF_PROG2(split_entry, __u64, a, __u64, b, __u64, c, __u64, d, __u64, e,\n+\t      struct int128_arg, h, __u64, i)\n+{\n+\tsplit_e = e;\n+\tsplit_lo = (__u64)h.v;\n+\tsplit_hi = (unsigned __int128)h.v \u003e\u003e 64;\n+\tsplit_i = i;\n+\treturn 0;\n+}\n+\n+SEC(\"fexit/callee_split\")\n+int split_exit(unsigned long long *ctx)\n+{\n+\tsplit_ret = ctx[8];\n+\treturn 0;\n+}\n+\n+/* Refused: the trampoline would lose the stack argument. */\n+SEC(\"?fentry/callee_unreliable\")\n+int unreliable_entry(unsigned long long *ctx)\n+{\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\nindex b4f0af2aeca45..38edb4addc5b2 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n@@ -197,6 +197,43 @@ bpf_testmod_test_int128_arg(__int128 a, int b, long c)\n \tbpf_testmod_test_struct_arg_result = (long)a + b + c;\n \treturn bpf_testmod_test_struct_arg_result;\n }\n+\n+struct bpf_testmod_struct_int128 {\n+\t__int128 a;\n+};\n+\n+/*\n+ * x86-64 puts g in the first stack slot and h, aligned to 16 bytes, in\n+ * the third and fourth, leaving a hole. arm64 leaves x7 empty and puts h\n+ * in the first two stack slots. Either way h and i are not where the BPF\n+ * convention packs them, right after g.\n+ */\n+noinline long\n+bpf_testmod_test_int128_stack(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,\n+\t\t\t      u64 g, struct bpf_testmod_struct_int128 h, u64 i)\n+{\n+\tbpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +\n+\t\tf * 6 + g * 7 + (u64)h.a * 8 +\n+\t\t(u64)((unsigned __int128)h.a \u003e\u003e 64) * 9 + i * 10;\n+\treturn bpf_testmod_test_struct_arg_result;\n+}\n+\n+/*\n+ * x86-64 has no register left for f after e, so f goes to the stack and g\n+ * takes R9 back; h goes to the third stack slot and i, aligned to 16\n+ * bytes, to the fifth and sixth. arm64 has registers up to g, puts h in\n+ * the first stack slot and i in the third and fourth.\n+ */\n+noinline long\n+bpf_testmod_test_int128_backfill(u64 a, u64 b, u64 c, u64 d, u64 e,\n+\t\t\t\t struct bpf_testmod_struct_arg_2 f, u64 g, u64 h,\n+\t\t\t\t struct bpf_testmod_struct_int128 i)\n+{\n+\tbpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +\n+\t\tf.a * 6 + f.b * 7 + g * 8 + h * 9 + (u64)i.a * 10 +\n+\t\t(u64)((unsigned __int128)i.a \u003e\u003e 64) * 11;\n+\treturn bpf_testmod_test_struct_arg_result;\n+}\n #endif\n \n __weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)\n@@ -683,6 +720,12 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,\n #ifdef __SIZEOF_INT128__\n \t(void)bpf_testmod_test_int128_ret(i);\n \t(void)bpf_testmod_test_int128_arg((__int128)1, 2, 3);\n+\t(void)bpf_testmod_test_int128_stack(1, 2, 3, 4, 5, 6, 7,\n+\t\t(struct bpf_testmod_struct_int128){ .a = ((__int128)9 \u003c\u003c 64) | 8 },\n+\t\t10);\n+\t(void)bpf_testmod_test_int128_backfill(1, 2, 3, 4, 5,\n+\t\t(struct bpf_testmod_struct_arg_2){ .a = 6, .b = 7 }, 8, 9,\n+\t\t(struct bpf_testmod_struct_int128){ .a = ((__int128)11 \u003c\u003c 64) | 10 });\n #endif\n \n \t(void)trace_bpf_testmod_test_raw_tp_null_tp(NULL);\n@@ -1047,6 +1090,29 @@ __bpf_kfunc u64 bpf_kfunc_call_test_i128_arg(u64 a, u64 b, __int128 v)\n \treturn a + b * 2 + (u64)v * 3 + (u64)((unsigned __int128)v \u003e\u003e 64) * 4;\n }\n \n+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_odd(u64 a, __int128 v, u64 b)\n+{\n+\treturn a + (u64)v * 2 + (u64)((unsigned __int128)v \u003e\u003e 64) * 3 + b * 4;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_odd_many(u64 a, __int128 v, u64 b, u64 c,\n+\t\t\t\t\t\t      u64 d, u64 e, u64 f, __int128 w)\n+{\n+\treturn a + (u64)v * 2 + (u64)((unsigned __int128)v \u003e\u003e 64) * 3 +\n+\t       b * 4 + c * 5 + d * 6 + e * 7 + f * 8 +\n+\t       (u64)w * 9 + (u64)((unsigned __int128)w \u003e\u003e 64) * 10;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_stack_hole(u64 a, u64 b, u64 c, u64 d, u64 e,\n+\t\t\t\t\t\t\tstruct prog_test_pair_arg s,\n+\t\t\t\t\t\t\tu64 f, u64 g,\n+\t\t\t\t\t\t\tstruct prog_test_i128_arg w)\n+{\n+\treturn a + b * 2 + c * 3 + d * 4 + e * 5 + s.lo * 6 + s.hi * 7 +\n+\t       f * 8 + g * 9 + (u64)w.v * 10 +\n+\t       (u64)((unsigned __int128)w.v \u003e\u003e 64) * 11;\n+}\n+\n __bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_pad(u64 a, u64 b, u64 c, u64 d, u64 e,\n \t\t\t\t\t\t u64 f, u64 g, __int128 v)\n {\n@@ -1770,6 +1836,9 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_deep)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ii)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg)\n+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_odd)\n+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_odd_many)\n+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_stack_hole)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_pad)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg_nofit)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg_tail)\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h\nindex 67c02a421d133..a6a93c9014161 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h\n@@ -66,6 +66,12 @@ struct prog_test_pair_arg {\t/* 16 bytes: two argument registers */\n \t__u64 hi;\n };\n \n+#ifdef __SIZEOF_INT128__\n+struct prog_test_i128_arg {\t/* 16 bytes, aligned to 16 like its member */\n+\t__int128 v;\n+};\n+#endif\n+\n struct prog_test_ptr_arg {\t/* 16 bytes, but holds a pointer */\n \tvoid *p;\n \t__u64 x;\n@@ -234,6 +240,13 @@ struct prog_test_ret_ii bpf_kfunc_call_test_ret_ii(int a, int b) __ksym;\n __u64 bpf_kfunc_call_test_pair_arg(__u64 a, struct prog_test_pair_arg s, __u64 b) __ksym;\n #ifdef __SIZEOF_INT128__\n __u64 bpf_kfunc_call_test_i128_arg(__u64 a, __u64 b, __int128 v) __ksym;\n+__u64 bpf_kfunc_call_test_i128_arg_odd(__u64 a, __int128 v, __u64 b) __ksym;\n+__u64 bpf_kfunc_call_test_i128_arg_odd_many(__u64 a, __int128 v, __u64 b, __u64 c,\n+\t\t\t\t\t    __u64 d, __u64 e, __u64 f, __int128 w) __ksym;\n+__u64 bpf_kfunc_call_test_i128_arg_stack_hole(__u64 a, __u64 b, __u64 c, __u64 d,\n+\t\t\t\t\t      __u64 e, struct prog_test_pair_arg s,\n+\t\t\t\t\t      __u64 f, __u64 g,\n+\t\t\t\t\t      struct prog_test_i128_arg w) __ksym;\n __u64 bpf_kfunc_call_test_i128_arg_pad(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,\n \t\t\t\t       __u64 f, __u64 g, __int128 v) __ksym;\n #endif\n"
}

2/1 2026/10/17 03:20 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT",
    "DEBUG_INFO_BTF",
    "DYNAMIC_FTRACE"
  ],
  "FocusSymbols": [
    "arch_prepare_bpf_trampoline",
    "bpf_check_attach_target",
    "arch_bpf_trampoline_size",
    "bpf_trampoline_update"
  ],
  "Reasoning": "The patch modifies the x86-64 BPF JIT trampoline generator (arch/x86/net/bpf_jit_comp.c) and the BPF verifier attach target handling (kernel/bpf/verifier.c). Specifically, it restructures how argument slots are calculated and placed on the stack for tracing trampolines (handling alignment holes, 16-byte aligned arguments like int128/structs, tail call contexts, and \u003e6 arguments), and adds new verification checks (flatten_bpf_func_model and check_unreliable_func_args). These changes touch reachable core BPF logic on amd64 that can be directly exercised via standard bpf() syscalls when attaching tracing/trampoline programs (such as fentry, fexit, and fmod_ret). Fuzzing is warranted to detect potential stack misalignment, off-by-one offsets in generated machine code, or unexpected verifier edge cases.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit af5698b3e790e13eb13b56eccd3ea7a12961729d
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Oct 17 03:20:58 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 3975a94ac812d..e316387700030 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -2222,6 +2222,16 @@ static const u8 x86_arg_reg[] = {
 	BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5, X86_REG_R9,
 };
 
+static bool x86_arg_on_stack(u8 pos)
+{
+	return pos >= x86_arg_abi.nr_arg_regs;
+}
+
+static s32 x86_arg_stack_off(u8 pos)
+{
+	return (pos - x86_arg_abi.nr_arg_regs) * 8;
+}
+
 /*
  * Move the arguments the x86-64 ABI places somewhere other than the argument
  * slot the BPF calling convention gave them. @stack_base addresses the
@@ -3752,21 +3762,17 @@ static void clean_stack_garbage(const struct btf_func_model *m,
 	}
 }
 
-/* get the count of the regs that are used to pass arguments */
-static int get_nr_used_regs(const struct btf_func_model *m)
+/* the eightbytes taken by the on-stack arguments, alignment holes included */
+static int get_nr_stack_slots(const u8 *pos_of_slot, int nr_slots)
 {
-	int i, arg_regs, nr_used_regs = 0;
+	int slot;
 
-	for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
-		arg_regs = (m->arg_size[i] + 7) / 8;
-		if (nr_used_regs + arg_regs <= 6)
-			nr_used_regs += arg_regs;
-
-		if (nr_used_regs >= 6)
-			break;
+	for (slot = nr_slots - 1; slot >= 0; slot--) {
+		if (x86_arg_on_stack(pos_of_slot[slot]))
+			return x86_arg_stack_off(pos_of_slot[slot]) / 8 + 1;
 	}
 
-	return nr_used_regs;
+	return 0;
 }
 
 /*
@@ -3796,20 +3802,21 @@ static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base
 }
 
 static void save_args(const struct btf_func_model *m, u8 **prog,
-		      int stack_size, bool for_call_origin, u32 flags,
-		      u64 arena_base)
+		      const u8 *pos_of_slot, int stack_size,
+		      bool for_call_origin, u32 flags, u64 arena_base)
 {
-	int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
+	int arg_regs, first_off = 0, nr_stack_slots = 0;
 	bool use_jmp = bpf_trampoline_use_jmp(flags);
 	int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
-	int i, j;
+	int bargs_off = stack_size;
+	int i, j, slot, off, doff;
 
 	/* Store function arguments to stack.
 	 * For a function that accepts two pointers the sequence will be:
 	 * mov QWORD PTR [rbp-0x10],rdi
 	 * mov QWORD PTR [rbp-0x8],rsi
 	 */
-	for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
+	for (i = 0, slot = 0; i < m->nr_args; i++) {
 		bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
 		bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
 
@@ -3831,8 +3838,36 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 		 *
 		 * the arg1-5,arg7 will be passed by regs, and arg6 will
 		 * by stack.
+		 *
+		 * An argument aligned to 16 bytes, an __int128 or a struct
+		 * holding one, also starts at an even stack slot, which can
+		 * leave a hole before it. bpf_jit_place_args() worked out
+		 * where each eightbyte is.
 		 */
-		if (nr_regs + arg_regs > 6) {
+		for (j = 0; j < arg_regs; j++, slot++, bargs_off -= 8) {
+			u8 pos = pos_of_slot[slot];
+			u32 src;
+
+			if (!x86_arg_on_stack(pos)) {
+				/*
+				 * Only copy the arguments on-stack to
+				 * current 'stack_size' and ignore the regs,
+				 * used to prepare the arguments on-stack for
+				 * origin call.
+				 */
+				if (for_call_origin)
+					continue;
+
+				/* copy the arguments from regs into stack */
+				src = x86_arg_reg[pos];
+				if (arena_arg) {
+					emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
+					src = BPF_REG_0;
+				}
+				emit_stx(prog, BPF_DW, BPF_REG_FP, src, -bargs_off);
+				continue;
+			}
+
 			/* copy function arguments from origin stack frame
 			 * into current stack frame.
 			 *
@@ -3842,53 +3877,33 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 			 * entered through the fentry call, so rbp + 24, and
 			 * a single one when it is entered with a jmp or
 			 * called indirectly, so rbp + 16.
+			 *
+			 * The area for the origin call mirrors the incoming
+			 * one, hole and all; only the bpf program takes the
+			 * arguments packed.
 			 */
-			for (j = 0; j < arg_regs; j++) {
-				emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
-					 nr_stack_slots * 8 + stack_args_off);
-				if (arena_arg)
-					emit_arena_arg_conv(prog, BPF_REG_0, nullable,
-							    (u32)arena_base);
-				emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,
-					 -stack_size);
-
-				if (!nr_stack_slots)
-					first_off = stack_size;
-				stack_size -= 8;
-				nr_stack_slots++;
-			}
-		} else {
-			/* Only copy the arguments on-stack to current
-			 * 'stack_size' and ignore the regs, used to
-			 * prepare the arguments on-stack for origin call.
-			 */
-			if (for_call_origin) {
-				nr_regs += arg_regs;
-				continue;
-			}
-
-			/* copy the arguments from regs into stack */
-			for (j = 0; j < arg_regs; j++) {
-				u32 src = nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs;
-
-				if (arena_arg) {
-					emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
-					src = BPF_REG_0;
-				}
-				emit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);
-				stack_size -= 8;
-				nr_regs++;
-			}
+			off = x86_arg_stack_off(pos);
+			doff = for_call_origin ? stack_size - off : bargs_off;
+			emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
+				 stack_args_off + off);
+			if (arena_arg)
+				emit_arena_arg_conv(prog, BPF_REG_0, nullable,
+						    (u32)arena_base);
+			emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -doff);
+
+			if (!nr_stack_slots)
+				first_off = doff;
+			nr_stack_slots++;
 		}
 	}
 
 	clean_stack_garbage(m, prog, nr_stack_slots, first_off);
 }
 
-static void restore_regs(const struct btf_func_model *m, u8 **prog,
+static void restore_regs(u8 **prog, const u8 *pos_of_slot, int nr_slots,
 			 int stack_size)
 {
-	int i, j, arg_regs, nr_regs = 0;
+	int slot;
 
 	/* Restore function arguments from stack.
 	 * For a function that accepts two pointers the sequence will be:
@@ -3897,23 +3912,10 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog,
 	 *
 	 * The logic here is similar to what we do in save_args()
 	 */
-	for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
-		arg_regs = (m->arg_size[i] + 7) / 8;
-		if (nr_regs + arg_regs <= 6) {
-			for (j = 0; j < arg_regs; j++) {
-				emit_ldx(prog, BPF_DW,
-					 nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,
-					 BPF_REG_FP,
-					 -stack_size);
-				stack_size -= 8;
-				nr_regs++;
-			}
-		} else {
-			stack_size -= 8 * arg_regs;
-		}
-
-		if (nr_regs >= 6)
-			break;
+	for (slot = 0; slot < nr_slots; slot++, stack_size -= 8) {
+		if (!x86_arg_on_stack(pos_of_slot[slot]))
+			emit_ldx(prog, BPF_DW, x86_arg_reg[pos_of_slot[slot]],
+				 BPF_REG_FP, -stack_size);
 	}
 }
 
@@ -4102,9 +4104,9 @@ static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,
 	return 0;
 }
 
-/* mov rax, qword ptr [rbp - rounded_stack_depth - 8] */
-#define LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack)	\
-	__LOAD_TCC_PTR(-round_up(stack, 8) - 8)
+/* mov rax, qword ptr [rbp - off] */
+#define LOAD_TRAMP_TAIL_CALL_CNT_PTR(off)	\
+	__LOAD_TCC_PTR(-(off))
 
 /* Example:
  * __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev);
@@ -4172,8 +4174,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 					 struct bpf_tramp_nodes *tnodes,
 					 void *func_addr)
 {
-	int i, ret, nr_regs = m->nr_args, stack_size = 0;
+	int i, ret, nr_regs, stack_size = 0;
+	u8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];
 	int regs_off, func_meta_off, ip_off, run_ctx_off, arg_stack_off, rbx_off;
+	int tcc_off = 0;
 	struct bpf_tramp_nodes *fentry = &tnodes[BPF_TRAMP_FENTRY];
 	struct bpf_tramp_nodes *fexit = &tnodes[BPF_TRAMP_FEXIT];
 	struct bpf_tramp_nodes *fmod_ret = &tnodes[BPF_TRAMP_MODIFY_RETURN];
@@ -4195,12 +4199,12 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 
 	arena_base = bpf_tramp_arena_base(m, tnodes, flags);
 
-	for (i = 0; i < m->nr_args; i++)
-		nr_regs += (m->arg_size[i] + 7) / 8 - 1;
-
-	/* x86-64 supports up to MAX_BPF_FUNC_ARGS arguments. 1-6
-	 * are passed through regs, the remains are through stack.
+	/*
+	 * x86-64 supports up to MAX_BPF_FUNC_ARGS argument slots, six in
+	 * registers and the rest on the stack, where bpf_jit_place_args()
+	 * puts them.
 	 */
+	nr_regs = bpf_jit_place_args(&x86_arg_abi, m, pos_of_slot);
 	if (nr_regs > MAX_BPF_FUNC_ARGS)
 		return -ENOTSUPP;
 
@@ -4212,11 +4216,11 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	 * RBP - 8         [ return value    ]  BPF_TRAMP_F_CALL_ORIG or
 	 *                                      BPF_TRAMP_F_RET_FENTRY_RET flags
 	 *
-	 *                 [ reg_argN        ]  always
-	 *                 [ ...             ]
-	 * RBP - regs_off  [ reg_arg1        ]  program's ctx pointer
+	 *                 [ arg slotN       ]  always, packed, whether
+	 *                 [ ...             ]  in a register or on the
+	 * RBP - regs_off  [ arg slot1       ]  stack; program's ctx pointer
 	 *
-	 * RBP - func_meta_off [ regs count, etc ]  always
+	 * RBP - func_meta_off [ slot count, etc ]  always
 	 *
 	 * RBP - ip_off    [ traced function ]  BPF_TRAMP_F_IP_ARG flag
 	 *
@@ -4224,11 +4228,12 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	 *
 	 * RBP - run_ctx_off [ bpf_tramp_run_ctx ]
 	 *
-	 *                     [ stack_argN ]  BPF_TRAMP_F_CALL_ORIG
-	 *                     [ ...        ]
-	 *                     [ stack_arg2 ]
-	 * RBP - arg_stack_off [ stack_arg1 ]
-	 * RSP                 [ tail_call_cnt_ptr ] BPF_TRAMP_F_TAIL_CALL_CTX
+	 * RBP - tcc_off   [ tail_call_cnt_ptr ]  BPF_TRAMP_F_TAIL_CALL_CTX
+	 *
+	 *                     [ stack slotN ]  BPF_TRAMP_F_CALL_ORIG,
+	 *                     [ ...         ]  the incoming stack
+	 *                     [ stack slot2 ]  arguments, alignment
+	 * RBP - arg_stack_off [ stack slot1 ]  holes included
 	 */
 
 	/* room for return value of orig_call or fentry prog */
@@ -4239,7 +4244,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	stack_size += nr_regs * 8;
 	regs_off = stack_size;
 
-	/* function matedata, such as regs count  */
+	/* function matedata, such as the argument slot count */
 	stack_size += 8;
 	func_meta_off = stack_size;
 
@@ -4259,9 +4264,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	stack_size += (sizeof(struct bpf_tramp_run_ctx) + 7) & ~0x7;
 	run_ctx_off = stack_size;
 
+	if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
+		stack_size += 8;
+		tcc_off = stack_size;
+	}
+
 	if (nr_regs > 6 && (flags & BPF_TRAMP_F_CALL_ORIG)) {
 		/* the space that used to pass arguments on-stack */
-		stack_size += (nr_regs - get_nr_used_regs(m)) * 8;
+		stack_size += get_nr_stack_slots(pos_of_slot, nr_regs) * 8;
 		/* make sure the stack pointer is 16-byte aligned if we
 		 * need pass arguments on stack, which means
 		 *  [stack_size + 8(rbp) + 8(rip) + 8(origin rip)]
@@ -4311,12 +4321,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	/* sub rsp, stack_size */
 	emit_sub_rsp(&prog, stack_size);
 	if (flags & BPF_TRAMP_F_TAIL_CALL_CTX)
-		EMIT1(0x50);		/* push rax */
+		/* mov QWORD PTR [rbp - tcc_off], rax */
+		emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -tcc_off);
 	/* mov QWORD PTR [rbp - rbx_off], rbx */
 	emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_6, -rbx_off);
 
 	func_meta = nr_regs;
-	/* Store number of argument registers of the traced function */
+	/* Store number of argument slots of the traced function */
 	emit_store_stack_imm64(&prog, BPF_REG_0, -func_meta_off, func_meta);
 
 	if (flags & BPF_TRAMP_F_IP_ARG) {
@@ -4324,7 +4335,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 		emit_store_stack_imm64(&prog, BPF_REG_0, -ip_off, (long)func_addr);
 	}
 
-	save_args(m, &prog, regs_off, false, flags, arena_base);
+	save_args(m, &prog, pos_of_slot, regs_off, false, flags, arena_base);
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
 		/* arg1: mov rdi, im */
@@ -4365,14 +4376,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	}
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
-		restore_regs(m, &prog, regs_off);
-		save_args(m, &prog, arg_stack_off, true, flags, 0);
+		restore_regs(&prog, pos_of_slot, nr_regs, regs_off);
+		save_args(m, &prog, pos_of_slot, arg_stack_off, true, flags, 0);
 
 		if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
 			/* Before calling the original function, load the
 			 * tail_call_cnt_ptr from stack to rax.
 			 */
-			LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);
+			LOAD_TRAMP_TAIL_CALL_CNT_PTR(tcc_off);
 		}
 
 		if (flags & BPF_TRAMP_F_ORIG_STACK) {
@@ -4419,7 +4430,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	}
 
 	if (flags & BPF_TRAMP_F_RESTORE_REGS)
-		restore_regs(m, &prog, regs_off);
+		restore_regs(&prog, pos_of_slot, nr_regs, regs_off);
 
 	/* This needs to be done regardless. If there were fmod_ret programs,
 	 * the return value is only updated on the stack and still needs to be
@@ -4436,7 +4447,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 		/* Before running the original function, load the
 		 * tail_call_cnt_ptr from stack to rax.
 		 */
-		LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);
+		LOAD_TRAMP_TAIL_CALL_CNT_PTR(tcc_off);
 	}
 
 	/* restore return value of orig_call or fentry prog back into RAX */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ec136cf377c13..6edddb56451d7 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -22006,6 +22006,59 @@ static bool attach_uses_trampoline_retval(enum bpf_attach_type type)
 	}
 }
 
+/*
+ * BPF code passes argument slot N where the kernel convention passes its Nth
+ * u64 argument, so describe a BPF function to its trampoline as one u64 per
+ * slot, and every arch places its arguments right.
+ */
+static int flatten_bpf_func_model(struct bpf_verifier_log *log, const char *tname,
+				  struct btf_func_model *m)
+{
+	u32 i, nslots = 0;
+
+	for (i = 0; i < m->nr_args; i++)
+		nslots += btf_func_model_arg_slots(m, i);
+	if (nslots > MAX_BPF_FUNC_ARGS) {
+		bpf_log(log, "The function %s has %u argument slots. Too many.\n",
+			tname, nslots);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < nslots; i++) {
+		m->arg_size[i] = sizeof(u64);
+		m->arg_flags[i] = 0;
+	}
+	m->nr_args = nslots;
+	return 0;
+}
+
+/*
+ * The verifier lets a static function use stack arguments counted from its
+ * BTF, even if that BTF turns out unreliable. A trampoline on the function is
+ * then given only MAX_BPF_FUNC_REG_ARGS u64 arguments and would lose the
+ * stack arguments, so such a function cannot be attached to.
+ */
+static int check_unreliable_func_args(struct bpf_verifier_log *log, struct btf *btf,
+				      const struct btf_type *func_proto, const char *tname)
+{
+	struct btf_func_model m;
+	u32 i, nslots = 0;
+
+	if (btf_distill_func_proto(NULL, btf, func_proto, tname, &m)) {
+		/* at least a slot for each argument */
+		nslots = btf_type_vlen(func_proto);
+	} else {
+		for (i = 0; i < m.nr_args; i++)
+			nslots += btf_func_model_arg_slots(&m, i);
+	}
+	if (nslots <= MAX_BPF_FUNC_REG_ARGS)
+		return 0;
+
+	bpf_log(log, "Cannot attach to static function %s: unreliable BTF and stack arguments\n",
+		tname);
+	return -EINVAL;
+}
+
 int bpf_check_attach_target(struct bpf_verifier_log *log,
 			    const struct bpf_prog *prog,
 			    const struct bpf_prog *tgt_prog,
@@ -22273,12 +22326,23 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
 		     prog->aux->saved_dst_attach_type != tgt_prog->expected_attach_type))
 			return -EINVAL;
 
-		if (tgt_prog && conservative)
+		if (tgt_prog && conservative) {
+			if (subprog > 0) {
+				ret = check_unreliable_func_args(log, btf, t, tname);
+				if (ret)
+					return ret;
+			}
 			t = NULL;
+		}
 
 		ret = btf_distill_func_proto(log, btf, t, tname, &tgt_info->fmodel);
 		if (ret < 0)
 			return ret;
+		if (tgt_prog) {
+			ret = flatten_bpf_func_model(log, tname, &tgt_info->fmodel);
+			if (ret < 0)
+				return ret;
+		}
 
 		if (tgt_info->fmodel.ret_size > 8 &&
 		    attach_uses_trampoline_retval(prog->expected_attach_type)) {
diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
index 15b95d0235b52..de358f335d98b 100644
--- a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
+++ b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
@@ -5,6 +5,10 @@
 #include "tracing_struct.skel.h"
 #include "tracing_struct_many_args.skel.h"
 #include "tracing_struct_int128.skel.h"
+#include "tracing_struct_int128_tgt.skel.h"
+#include "tracing_struct_int128_tracer.skel.h"
+#include "tracing_stack_arg_tailcall_tgt.skel.h"
+#include "tracing_stack_arg_tailcall_tracer.skel.h"
 
 static void test_struct_args(void)
 {
@@ -117,9 +121,11 @@ static void test_int128_args(void)
 {
 	/*
 	 * __int128 arguments are passed in a register pair on x86_64 and
-	 * arm64, which the trampoline packs into two context slots. Other
-	 * architectures pass a __int128 differently (e.g. s390x passes larger
-	 * arguments by reference), so only exercise this on x86_64 and arm64.
+	 * arm64, which the trampoline packs into two context slots. Past the
+	 * registers, both start an argument aligned to 16 bytes at an even
+	 * stack slot, which the trampoline packs too. Other architectures
+	 * pass a __int128 differently (e.g. s390x passes larger arguments by
+	 * reference), so only exercise this on x86_64 and arm64.
 	 */
 #if defined(__x86_64__) || defined(__aarch64__)
 	struct tracing_struct_int128 *skel;
@@ -139,6 +145,20 @@ static void test_int128_args(void)
 	ASSERT_EQ(skel->bss->t_c, 3, "t:c");
 	ASSERT_EQ(skel->bss->t_ret, 6, "t ret");
 
+	ASSERT_EQ(skel->bss->s_g, 7, "s:g");
+	ASSERT_EQ(skel->bss->s_h_lo, 8, "s:h.lo");
+	ASSERT_EQ(skel->bss->s_h_hi, 9, "s:h.hi");
+	ASSERT_EQ(skel->bss->s_i, 10, "s:i");
+	ASSERT_EQ(skel->bss->s_ret, 385, "s ret");
+
+	ASSERT_EQ(skel->bss->b_f_a, 6, "b:f.a");
+	ASSERT_EQ(skel->bss->b_f_b, 7, "b:f.b");
+	ASSERT_EQ(skel->bss->b_g, 8, "b:g");
+	ASSERT_EQ(skel->bss->b_h, 9, "b:h");
+	ASSERT_EQ(skel->bss->b_i_lo, 10, "b:i.lo");
+	ASSERT_EQ(skel->bss->b_i_hi, 11, "b:i.hi");
+	ASSERT_EQ(skel->bss->b_ret, 506, "b ret");
+
 destroy_skel:
 	tracing_struct_int128__destroy(skel);
 #else
@@ -146,6 +166,191 @@ static void test_int128_args(void)
 #endif
 }
 
+static void test_int128_tgt_prog(void)
+{
+#if defined(__x86_64__) || defined(__aarch64__) || defined(__s390x__)
+	struct tracing_struct_int128_tracer *tracer = NULL;
+	struct tracing_struct_int128_tgt *tgt;
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	int err, fd;
+
+	tgt = tracing_struct_int128_tgt__open_and_load();
+	if (!ASSERT_OK_PTR(tgt, "tgt__open_and_load"))
+		return;
+	if (!tgt->rodata->has_struct_arg) {
+		test__skip();
+		goto out;
+	}
+	fd = bpf_program__fd(tgt->progs.run);
+
+	tracer = tracing_struct_int128_tracer__open();
+	if (!ASSERT_OK_PTR(tracer, "tracer__open"))
+		goto out;
+
+	err = bpf_program__set_attach_target(tracer->progs.odd_entry, fd, "callee_odd");
+	err = err ?: bpf_program__set_attach_target(tracer->progs.odd_exit, fd, "callee_odd");
+	if (tgt->rodata->has_stack_arg) {
+		err = err ?: bpf_program__set_attach_target(tracer->progs.stack_entry, fd,
+							     "callee_stack");
+		err = err ?: bpf_program__set_attach_target(tracer->progs.stack_exit, fd,
+							     "callee_stack");
+		err = err ?: bpf_program__set_attach_target(tracer->progs.split_entry, fd,
+							     "callee_split");
+		err = err ?: bpf_program__set_attach_target(tracer->progs.split_exit, fd,
+							     "callee_split");
+	} else {
+		bpf_program__set_autoload(tracer->progs.stack_entry, false);
+		bpf_program__set_autoload(tracer->progs.stack_exit, false);
+		bpf_program__set_autoload(tracer->progs.split_entry, false);
+		bpf_program__set_autoload(tracer->progs.split_exit, false);
+	}
+	if (!ASSERT_OK(err, "set_attach_target"))
+		goto out;
+
+	err = tracing_struct_int128_tracer__load(tracer);
+	if (!ASSERT_OK(err, "tracer__load"))
+		goto out;
+	err = tracing_struct_int128_tracer__attach(tracer);
+	if (!ASSERT_OK(err, "tracer__attach"))
+		goto out;
+
+	err = bpf_prog_test_run_opts(fd, &topts);
+	if (!ASSERT_OK(err, "test_run") || !ASSERT_EQ(topts.retval, 0, "retval"))
+		goto out;
+
+	ASSERT_EQ(tracer->bss->odd_a, 1, "odd:a");
+	ASSERT_EQ(tracer->bss->odd_lo, 8, "odd:v.lo");
+	ASSERT_EQ(tracer->bss->odd_hi, 9, "odd:v.hi");
+	ASSERT_EQ(tracer->bss->odd_b, 2, "odd:b");
+	ASSERT_EQ(tgt->bss->ret_odd, 52, "odd ret");
+	ASSERT_EQ(tracer->bss->odd_ret, 52, "odd fexit ret");
+
+	if (tgt->rodata->has_stack_arg) {
+		ASSERT_EQ(tracer->bss->stack_g, 7, "stack:g");
+		ASSERT_EQ(tracer->bss->stack_lo, 8, "stack:h.lo");
+		ASSERT_EQ(tracer->bss->stack_hi, 9, "stack:h.hi");
+		ASSERT_EQ(tracer->bss->stack_i, 10, "stack:i");
+		ASSERT_EQ(tgt->bss->ret_stack, 385, "stack ret");
+		ASSERT_EQ(tracer->bss->stack_ret, 385, "stack fexit ret");
+
+		ASSERT_EQ(tracer->bss->split_e, 5, "split:e");
+		ASSERT_EQ(tracer->bss->split_lo, 8, "split:h.lo");
+		ASSERT_EQ(tracer->bss->split_hi, 9, "split:h.hi");
+		ASSERT_EQ(tracer->bss->split_i, 10, "split:i");
+		ASSERT_EQ(tgt->bss->ret_split, 246, "split ret");
+		ASSERT_EQ(tracer->bss->split_ret, 246, "split fexit ret");
+	}
+
+out:
+	tracing_struct_int128_tracer__destroy(tracer);
+	tracing_struct_int128_tgt__destroy(tgt);
+#else
+	test__skip();
+#endif
+}
+
+/*
+ * A static function whose BTF is unreliable gets a trampoline that only knows
+ * of the register arguments, so one taking stack arguments must be refused.
+ */
+static void test_tgt_prog_unreliable(void)
+{
+#if defined(__x86_64__) || defined(__aarch64__)
+	struct tracing_struct_int128_tracer *tracer = NULL;
+	struct tracing_struct_int128_tgt *tgt;
+	char log_buf[4096] = {};
+	struct bpf_program *prog;
+	int err, fd;
+
+	tgt = tracing_struct_int128_tgt__open_and_load();
+	if (!ASSERT_OK_PTR(tgt, "tgt__open_and_load"))
+		return;
+	if (!tgt->rodata->has_stack_arg) {
+		test__skip();
+		goto out;
+	}
+	fd = bpf_program__fd(tgt->progs.run);
+
+	tracer = tracing_struct_int128_tracer__open();
+	if (!ASSERT_OK_PTR(tracer, "tracer__open"))
+		goto out;
+
+	bpf_object__for_each_program(prog, tracer->obj)
+		bpf_program__set_autoload(prog, false);
+	bpf_program__set_autoload(tracer->progs.unreliable_entry, true);
+	bpf_program__set_log_buf(tracer->progs.unreliable_entry, log_buf, sizeof(log_buf));
+	err = bpf_program__set_attach_target(tracer->progs.unreliable_entry, fd,
+					     "callee_unreliable");
+	if (!ASSERT_OK(err, "set_attach_target"))
+		goto out;
+
+	err = tracing_struct_int128_tracer__load(tracer);
+	ASSERT_ERR(err, "tracer__load");
+	ASSERT_HAS_SUBSTR(log_buf, "unreliable BTF and stack arguments", "log");
+
+out:
+	tracing_struct_int128_tracer__destroy(tracer);
+	tracing_struct_int128_tgt__destroy(tgt);
+#else
+	test__skip();
+#endif
+}
+
+/*
+ * A trampoline on a function of a tail_call_reachable program keeps the tail
+ * call counter pointer on its stack, and has to hand the function its stack
+ * arguments past it.
+ */
+static void test_stack_arg_tailcall(void)
+{
+#if defined(__x86_64__) || defined(__aarch64__)
+	struct tracing_stack_arg_tailcall_tracer *tracer = NULL;
+	struct tracing_stack_arg_tailcall_tgt *tgt;
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	int err, fd;
+
+	tgt = tracing_stack_arg_tailcall_tgt__open_and_load();
+	if (!ASSERT_OK_PTR(tgt, "tgt__open_and_load"))
+		return;
+	if (!tgt->rodata->has_stack_arg) {
+		test__skip();
+		goto out;
+	}
+	fd = bpf_program__fd(tgt->progs.run);
+
+	tracer = tracing_stack_arg_tailcall_tracer__open();
+	if (!ASSERT_OK_PTR(tracer, "tracer__open"))
+		goto out;
+
+	err = bpf_program__set_attach_target(tracer->progs.stack_entry, fd, "callee_stack");
+	err = err ?: bpf_program__set_attach_target(tracer->progs.stack_exit, fd, "callee_stack");
+	if (!ASSERT_OK(err, "set_attach_target"))
+		goto out;
+
+	err = tracing_stack_arg_tailcall_tracer__load(tracer);
+	if (!ASSERT_OK(err, "tracer__load"))
+		goto out;
+	err = tracing_stack_arg_tailcall_tracer__attach(tracer);
+	if (!ASSERT_OK(err, "tracer__attach"))
+		goto out;
+
+	err = bpf_prog_test_run_opts(fd, &topts);
+	if (!ASSERT_OK(err, "test_run") || !ASSERT_EQ(topts.retval, 0, "retval"))
+		goto out;
+
+	ASSERT_EQ(tracer->bss->stack_g, 7, "stack:g");
+	ASSERT_EQ(tracer->bss->stack_h, 8, "stack:h");
+	ASSERT_EQ(tgt->bss->ret_stack, 204, "stack ret");
+	ASSERT_EQ(tracer->bss->stack_ret, 204, "stack fexit ret");
+
+out:
+	tracing_stack_arg_tailcall_tracer__destroy(tracer);
+	tracing_stack_arg_tailcall_tgt__destroy(tgt);
+#else
+	test__skip();
+#endif
+}
+
 static void test_union_args(void)
 {
 	struct tracing_struct *skel;
@@ -181,6 +386,12 @@ void test_tracing_struct(void)
 		test_struct_many_args();
 	if (test__start_subtest("int128_args"))
 		test_int128_args();
+	if (test__start_subtest("int128_tgt_prog"))
+		test_int128_tgt_prog();
+	if (test__start_subtest("tgt_prog_unreliable"))
+		test_tgt_prog_unreliable();
+	if (test__start_subtest("stack_arg_tailcall"))
+		test_stack_arg_tailcall();
 	if (test__start_subtest("union_args"))
 		test_union_args();
 }
diff --git a/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c b/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c
index 52d3d5d539465..c87e886423f4f 100644
--- a/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c
+++ b/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c
@@ -50,6 +50,27 @@ int aggregate_arg_kfunc_int128(struct __sk_buff *skb)
 	return 0;
 }
 
+/*
+ * arm64 rounds the register number up to an even one for an argument
+ * aligned to 16 bytes, so it wants this __int128 in x2 and x3, leaving x1
+ * empty, and the last argument in x4. The x86-64 ABI has no such rule.
+ */
+SEC("tc")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_arg_kfunc_int128_odd(struct __sk_buff *skb)
+{
+	__u64 a = skb->len ^ MIX_A;
+	__u64 b = skb->len ^ MIX_B;
+	u128 v = ((u128)a << 64) | b;
+
+	if (bpf_kfunc_call_test_i128_arg_odd(1, v, 2) != 1 + 2 * b + 3 * a + 4 * 2)
+		return 1;
+
+	return 0;
+}
+
 #endif /* __SIZEOF_INT128__ */
 
 #if defined(__clang__) && defined(__BPF_FEATURE_STACK_ARGUMENT)
@@ -131,6 +152,56 @@ int aggregate_arg_kfunc_split8(struct __sk_buff *skb)
 
 #ifdef __SIZEOF_INT128__
 
+/*
+ * The hole of aggregate_arg_kfunc_int128_odd, with arguments after it that
+ * arm64 shifts through x5 to x7, where its JIT keeps the BPF stack slots,
+ * and onto its stack. The second __int128 then leaves the second arm64
+ * stack slot empty. x86-64 packs the eightbytes like the BPF convention,
+ * so nothing moves there.
+ */
+SEC("tc")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_arg_kfunc_int128_odd_many(struct __sk_buff *skb)
+{
+	__u64 a = skb->len ^ MIX_A;
+	__u64 b = skb->len ^ MIX_B;
+	u128 v = ((u128)a << 64) | b;
+	u128 w = ((u128)~a << 64) | ~b;
+
+	if (bpf_kfunc_call_test_i128_arg_odd_many(1, v, 2, 3, 4, 5, 6, w) !=
+	    1 + 2 * b + 3 * a + 4 * 2 + 5 * 3 + 6 * 4 + 7 * 5 + 8 * 6 +
+	    9 * ~b + 10 * ~a)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * x86-64 moves s to the stack and gives R9 back to the seventh argument.
+ * w, aligned to 16 bytes by the __int128 it holds, then leaves a stack slot
+ * empty before it: the fourth on x86-64, the second on arm64.
+ */
+SEC("tc")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_arg_kfunc_int128_stack_hole(struct __sk_buff *skb)
+{
+	__u64 a = skb->len ^ MIX_A;
+	__u64 b = skb->len ^ MIX_B;
+	struct prog_test_pair_arg s = { .lo = a, .hi = b };
+	struct prog_test_i128_arg w = { .v = ((u128)~a << 64) | ~b };
+
+	if (bpf_kfunc_call_test_i128_arg_stack_hole(1, 2, 3, 4, 5, s, 6, 7, w) !=
+	    1 + 2 * 2 + 3 * 3 + 4 * 4 + 5 * 5 + 6 * a + 7 * b + 8 * 6 + 9 * 7 +
+	    10 * ~b + 11 * ~a)
+		return 1;
+
+	return 0;
+}
+
 /*
  * Both conventions pad the stack to align this __int128, and the BPF
  * convention pads for neither, so both JITs move it up an eightbyte.
diff --git a/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c
new file mode 100644
index 0000000000000..5fde710c4e370
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+	__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+	__uint(max_entries, 1);
+	__uint(key_size, sizeof(__u32));
+	__uint(value_size, sizeof(__u32));
+} jmp_table SEC(".maps");
+
+__u64 base;
+__u64 ret_stack;
+
+#if defined(__clang__) && defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+const volatile bool has_stack_arg = true;
+
+/* g and h are on the stack, for the x86-64 kernel convention too. */
+static __noinline __u64 callee_stack(__u64 a, __u64 b, __u64 c, __u64 d,
+				     __u64 e, __u64 f, __u64 g, __u64 h)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 + h * 8;
+}
+
+/* No stack args of its own, so it can share a program with a tail call. */
+static __noinline __u64 call_stack(__u64 x)
+{
+	return callee_stack(x + 1, x + 2, x + 3, x + 4, x + 5, x + 6, x + 7,
+			    x + 8);
+}
+
+#else
+
+const volatile bool has_stack_arg = false;
+
+#endif
+
+/* Makes the program tail_call_reachable, though callee_stack() is not. */
+static __noinline int subprog_tail(void *ctx)
+{
+	int ret = 0;
+
+	bpf_tail_call_static(ctx, &jmp_table, 0);
+	barrier_var(ret);
+	return ret;
+}
+
+SEC("syscall")
+int run(void *ctx)
+{
+#if defined(__clang__) && defined(__BPF_FEATURE_STACK_ARGUMENT)
+	ret_stack = call_stack(base);
+#endif
+	return subprog_tail(ctx);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c
new file mode 100644
index 0000000000000..1800e7b7afe2d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+long stack_g, stack_h, stack_ret;
+
+SEC("fentry/callee_stack")
+int stack_entry(unsigned long long *ctx)
+{
+	stack_g = ctx[6];
+	stack_h = ctx[7];
+	return 0;
+}
+
+SEC("fexit/callee_stack")
+int stack_exit(unsigned long long *ctx)
+{
+	stack_ret = ctx[8];
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
index 4638dfec1f38e..5520ee500c01d 100644
--- a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
@@ -15,4 +15,49 @@ int test_int128_arg_fexit(unsigned long long *ctx)
 	return 0;
 }
 
+/*
+ * The arguments come packed in the BPF convention whatever holes the
+ * kernel convention left between them: an eightbyte each, and two for the
+ * struct holding an __int128.
+ */
+long s_g, s_h_lo, s_h_hi, s_i, s_ret;
+
+SEC("fentry/bpf_testmod_test_int128_stack")
+int test_int128_stack_fentry(unsigned long long *ctx)
+{
+	s_g = ctx[6];
+	s_h_lo = ctx[7];
+	s_h_hi = ctx[8];
+	s_i = ctx[9];
+	return 0;
+}
+
+SEC("fexit/bpf_testmod_test_int128_stack")
+int test_int128_stack_fexit(unsigned long long *ctx)
+{
+	s_ret = ctx[10];
+	return 0;
+}
+
+long b_f_a, b_f_b, b_g, b_h, b_i_lo, b_i_hi, b_ret;
+
+SEC("fentry/bpf_testmod_test_int128_backfill")
+int test_int128_backfill_fentry(unsigned long long *ctx)
+{
+	b_f_a = ctx[5];
+	b_f_b = ctx[6];
+	b_g = ctx[7];
+	b_h = ctx[8];
+	b_i_lo = ctx[9];
+	b_i_hi = ctx[10];
+	return 0;
+}
+
+SEC("fexit/bpf_testmod_test_int128_backfill")
+int test_int128_backfill_fexit(unsigned long long *ctx)
+{
+	b_ret = ctx[11];
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c
new file mode 100644
index 0000000000000..2561422c25dd7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct int128_arg {
+	__int128 v;
+};
+
+__u64 base;
+__u64 ret_odd, ret_stack, ret_split, ret_unreliable;
+
+#if defined(__clang__)
+
+const volatile bool has_struct_arg = true;
+
+/* arm64 would want v in x2 and x3, and b in x4. */
+__noinline __u64 callee_odd(__u64 a, struct int128_arg v, __u64 b)
+{
+	return a + (__u64)v.v * 2 + (__u64)((unsigned __int128)v.v >> 64) * 3 +
+	       b * 4;
+}
+
+#else
+
+const volatile bool has_struct_arg = false;
+
+#endif
+
+#if defined(__clang__) && \
+	(defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+	defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+const volatile bool has_stack_arg = true;
+
+/*
+ * x86-64 would want h in its third and fourth stack slots, and arm64 in
+ * its first and second, leaving x7 empty.
+ */
+static __noinline __u64 callee_stack(__u64 a, __u64 b, __u64 c, __u64 d,
+				     __u64 e, __u64 f, __u64 g,
+				     struct int128_arg h, __u64 i)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 +
+	       (__u64)h.v * 8 + (__u64)((unsigned __int128)h.v >> 64) * 9 +
+	       i * 10;
+}
+
+/*
+ * x86-64 BPF code passes h split between r9 and its first stack slot;
+ * arm64 passes it in x5 and x6, so nothing is split there.
+ */
+static __noinline __u64 callee_split(__u64 a, __u64 b, __u64 c, __u64 d,
+				     __u64 e, struct int128_arg h, __u64 i)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + (__u64)h.v * 6 +
+	       (__u64)((unsigned __int128)h.v >> 64) * 7 + i * 8;
+}
+
+/*
+ * A plain pointer makes the BTF of a static function unreliable, but f is
+ * still passed on the stack. The store keeps p a pointer.
+ */
+static __noinline __u64 callee_unreliable(__u64 *p, __u64 b, __u64 c, __u64 d,
+					  __u64 e, __u64 f)
+{
+	*p += 1;
+	return *p + b * 2 + c * 3 + d * 4 + e * 5 + f * 6;
+}
+
+#else
+
+const volatile bool has_stack_arg = false;
+
+#endif
+
+SEC("syscall")
+int run(void *ctx)
+{
+#if defined(__clang__)
+	struct int128_arg v = { .v = ((__int128)(base + 9) << 64) | (base + 8) };
+
+	ret_odd = callee_odd(base + 1, v, base + 2);
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+	defined(__BPF_FEATURE_STACK_ARGUMENT)
+	ret_stack = callee_stack(base + 1, base + 2, base + 3, base + 4,
+				 base + 5, base + 6, base + 7, v, base + 10);
+	ret_split = callee_split(base + 1, base + 2, base + 3, base + 4,
+				 base + 5, v, base + 10);
+	{
+		__u64 x = base;
+
+		ret_unreliable = callee_unreliable(&x, base + 2, base + 3,
+						   base + 4, base + 5, base + 6);
+	}
+#endif
+#endif
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c
new file mode 100644
index 0000000000000..fc81c2eea4a8d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+struct int128_arg {
+	__int128 v;
+};
+
+long odd_a, odd_lo, odd_hi, odd_b, odd_ret;
+
+SEC("fentry/callee_odd")
+int BPF_PROG2(odd_entry, __u64, a, struct int128_arg, v, __u64, b)
+{
+	odd_a = a;
+	odd_lo = (__u64)v.v;
+	odd_hi = (unsigned __int128)v.v >> 64;
+	odd_b = b;
+	return 0;
+}
+
+SEC("fexit/callee_odd")
+int odd_exit(unsigned long long *ctx)
+{
+	odd_ret = ctx[4];
+	return 0;
+}
+
+long stack_g, stack_lo, stack_hi, stack_i, stack_ret;
+
+SEC("fentry/callee_stack")
+int BPF_PROG2(stack_entry, __u64, a, __u64, b, __u64, c, __u64, d, __u64, e,
+	      __u64, f, __u64, g, struct int128_arg, h, __u64, i)
+{
+	stack_g = g;
+	stack_lo = (__u64)h.v;
+	stack_hi = (unsigned __int128)h.v >> 64;
+	stack_i = i;
+	return 0;
+}
+
+SEC("fexit/callee_stack")
+int stack_exit(unsigned long long *ctx)
+{
+	stack_ret = ctx[10];
+	return 0;
+}
+
+long split_e, split_lo, split_hi, split_i, split_ret;
+
+SEC("fentry/callee_split")
+int BPF_PROG2(split_entry, __u64, a, __u64, b, __u64, c, __u64, d, __u64, e,
+	      struct int128_arg, h, __u64, i)
+{
+	split_e = e;
+	split_lo = (__u64)h.v;
+	split_hi = (unsigned __int128)h.v >> 64;
+	split_i = i;
+	return 0;
+}
+
+SEC("fexit/callee_split")
+int split_exit(unsigned long long *ctx)
+{
+	split_ret = ctx[8];
+	return 0;
+}
+
+/* Refused: the trampoline would lose the stack argument. */
+SEC("?fentry/callee_unreliable")
+int unreliable_entry(unsigned long long *ctx)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index b4f0af2aeca45..38edb4addc5b2 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -197,6 +197,43 @@ bpf_testmod_test_int128_arg(__int128 a, int b, long c)
 	bpf_testmod_test_struct_arg_result = (long)a + b + c;
 	return bpf_testmod_test_struct_arg_result;
 }
+
+struct bpf_testmod_struct_int128 {
+	__int128 a;
+};
+
+/*
+ * x86-64 puts g in the first stack slot and h, aligned to 16 bytes, in
+ * the third and fourth, leaving a hole. arm64 leaves x7 empty and puts h
+ * in the first two stack slots. Either way h and i are not where the BPF
+ * convention packs them, right after g.
+ */
+noinline long
+bpf_testmod_test_int128_stack(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,
+			      u64 g, struct bpf_testmod_struct_int128 h, u64 i)
+{
+	bpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +
+		f * 6 + g * 7 + (u64)h.a * 8 +
+		(u64)((unsigned __int128)h.a >> 64) * 9 + i * 10;
+	return bpf_testmod_test_struct_arg_result;
+}
+
+/*
+ * x86-64 has no register left for f after e, so f goes to the stack and g
+ * takes R9 back; h goes to the third stack slot and i, aligned to 16
+ * bytes, to the fifth and sixth. arm64 has registers up to g, puts h in
+ * the first stack slot and i in the third and fourth.
+ */
+noinline long
+bpf_testmod_test_int128_backfill(u64 a, u64 b, u64 c, u64 d, u64 e,
+				 struct bpf_testmod_struct_arg_2 f, u64 g, u64 h,
+				 struct bpf_testmod_struct_int128 i)
+{
+	bpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +
+		f.a * 6 + f.b * 7 + g * 8 + h * 9 + (u64)i.a * 10 +
+		(u64)((unsigned __int128)i.a >> 64) * 11;
+	return bpf_testmod_test_struct_arg_result;
+}
 #endif
 
 __weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)
@@ -683,6 +720,12 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
 #ifdef __SIZEOF_INT128__
 	(void)bpf_testmod_test_int128_ret(i);
 	(void)bpf_testmod_test_int128_arg((__int128)1, 2, 3);
+	(void)bpf_testmod_test_int128_stack(1, 2, 3, 4, 5, 6, 7,
+		(struct bpf_testmod_struct_int128){ .a = ((__int128)9 << 64) | 8 },
+		10);
+	(void)bpf_testmod_test_int128_backfill(1, 2, 3, 4, 5,
+		(struct bpf_testmod_struct_arg_2){ .a = 6, .b = 7 }, 8, 9,
+		(struct bpf_testmod_struct_int128){ .a = ((__int128)11 << 64) | 10 });
 #endif
 
 	(void)trace_bpf_testmod_test_raw_tp_null_tp(NULL);
@@ -1047,6 +1090,29 @@ __bpf_kfunc u64 bpf_kfunc_call_test_i128_arg(u64 a, u64 b, __int128 v)
 	return a + b * 2 + (u64)v * 3 + (u64)((unsigned __int128)v >> 64) * 4;
 }
 
+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_odd(u64 a, __int128 v, u64 b)
+{
+	return a + (u64)v * 2 + (u64)((unsigned __int128)v >> 64) * 3 + b * 4;
+}
+
+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_odd_many(u64 a, __int128 v, u64 b, u64 c,
+						      u64 d, u64 e, u64 f, __int128 w)
+{
+	return a + (u64)v * 2 + (u64)((unsigned __int128)v >> 64) * 3 +
+	       b * 4 + c * 5 + d * 6 + e * 7 + f * 8 +
+	       (u64)w * 9 + (u64)((unsigned __int128)w >> 64) * 10;
+}
+
+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_stack_hole(u64 a, u64 b, u64 c, u64 d, u64 e,
+							struct prog_test_pair_arg s,
+							u64 f, u64 g,
+							struct prog_test_i128_arg w)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + s.lo * 6 + s.hi * 7 +
+	       f * 8 + g * 9 + (u64)w.v * 10 +
+	       (u64)((unsigned __int128)w.v >> 64) * 11;
+}
+
 __bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_pad(u64 a, u64 b, u64 c, u64 d, u64 e,
 						 u64 f, u64 g, __int128 v)
 {
@@ -1770,6 +1836,9 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_deep)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ii)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_odd)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_odd_many)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_stack_hole)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_pad)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg_nofit)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg_tail)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index 67c02a421d133..a6a93c9014161 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -66,6 +66,12 @@ struct prog_test_pair_arg {	/* 16 bytes: two argument registers */
 	__u64 hi;
 };
 
+#ifdef __SIZEOF_INT128__
+struct prog_test_i128_arg {	/* 16 bytes, aligned to 16 like its member */
+	__int128 v;
+};
+#endif
+
 struct prog_test_ptr_arg {	/* 16 bytes, but holds a pointer */
 	void *p;
 	__u64 x;
@@ -234,6 +240,13 @@ struct prog_test_ret_ii bpf_kfunc_call_test_ret_ii(int a, int b) __ksym;
 __u64 bpf_kfunc_call_test_pair_arg(__u64 a, struct prog_test_pair_arg s, __u64 b) __ksym;
 #ifdef __SIZEOF_INT128__
 __u64 bpf_kfunc_call_test_i128_arg(__u64 a, __u64 b, __int128 v) __ksym;
+__u64 bpf_kfunc_call_test_i128_arg_odd(__u64 a, __int128 v, __u64 b) __ksym;
+__u64 bpf_kfunc_call_test_i128_arg_odd_many(__u64 a, __int128 v, __u64 b, __u64 c,
+					    __u64 d, __u64 e, __u64 f, __int128 w) __ksym;
+__u64 bpf_kfunc_call_test_i128_arg_stack_hole(__u64 a, __u64 b, __u64 c, __u64 d,
+					      __u64 e, struct prog_test_pair_arg s,
+					      __u64 f, __u64 g,
+					      struct prog_test_i128_arg w) __ksym;
 __u64 bpf_kfunc_call_test_i128_arg_pad(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
 				       __u64 f, __u64 g, __int128 v) __ksym;
 #endif

3/2 2026/10/17 03:20 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22215
							output: 53
							thoughts: 442
						

Thoughts:
**Analyzing a Kernel Patch Series: BPF and x86-64**

Okay, so the task at hand is to evaluate a patch series that's been applied as HEAD in the kernel workspace. My focus, right now, is on the x86-64 architecture, given that's the specified target.

First, I need to get a clear picture of the modifications. The diff reveals changes to `arch/x86/net/bpf_jit_comp.c`, specifically functions like `x86_arg_on_stack`, `x86_arg_stack_off`, and replacements like `get_nr_stack_slots` replacing `get_nr_used_regs`, as well as `save_args`, `restore_regs`, `LOAD_TRAMP_TAIL_CALL_CNT_PTR`, and `__arch_prepare_bpf_trampoline`. The verifier is also touched (`kernel/bpf/verifier.c`), with modifications to `flatten_bpf_func_model`, `check_unreliable_func_args`, and `bpf_check_attach_target`. And of course, there are some changes in the selftests.

This is all pretty clearly about the BPF JIT trampoline on x86-64 and BPF verifier attach target checking. BPF is a fundamental and critical subsystem – this means BPF trampoline generation, attach target verification, handling argument passing (whether on the stack or in registers), fentry/fexit, and all that. Given the architecture is x86-64, that's exactly where I expect to see the modifications!

I can confirm that this code is definitely reachable in QEMU or a standard virtualized environment through the BPF syscall (`bpf()`). I can verify this by checking the ability to load programs and attach fentry/fexit trampolines, so it can be verified.

Alright, so the architecture lines up. Now I need to dive into the code. I'll need to figure out what Kconfig options are relevant, what configurations might be required to enable these changes, and identify the key symbols I need to focus on to start with. Looks like I'll use tools like `grepper` or `read-file` to get a better sense of the surrounding code and the dependencies.



4/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "bpf_jit_place_args",
  "PathPrefixes": [
    "arch/x86/net/bpf_jit_comp.c",
    "kernel/bpf/"
  ]
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=3804=static void save_args(const struct btf_func_model *m, u8 **prog,\n--\narch/x86/net/bpf_jit_comp.c-3843-\t\t * holding one, also starts at an even stack slot, which can\narch/x86/net/bpf_jit_comp.c:3844:\t\t * leave a hole before it. bpf_jit_place_args() worked out\narch/x86/net/bpf_jit_comp.c-3845-\t\t * where each eightbyte is.\n--\narch/x86/net/bpf_jit_comp.c=4171=static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\n--\narch/x86/net/bpf_jit_comp.c-4203-\t * x86-64 supports up to MAX_BPF_FUNC_ARGS argument slots, six in\narch/x86/net/bpf_jit_comp.c:4204:\t * registers and the rest on the stack, where bpf_jit_place_args()\narch/x86/net/bpf_jit_comp.c-4205-\t * puts them.\narch/x86/net/bpf_jit_comp.c-4206-\t */\narch/x86/net/bpf_jit_comp.c:4207:\tnr_regs = bpf_jit_place_args(\u0026x86_arg_abi, m, pos_of_slot);\narch/x86/net/bpf_jit_comp.c-4208-\tif (nr_regs \u003e MAX_BPF_FUNC_ARGS)\n--\nkernel/bpf/core.c=3330=const struct bpf_jit_arg_abi * __weak bpf_jit_arg_abi(void)\n--\nkernel/bpf/core.c-3334-\nkernel/bpf/core.c:3335:u32 bpf_jit_place_args(const struct bpf_jit_arg_abi *abi,\nkernel/bpf/core.c-3336-\t\t       const struct btf_func_model *fm, u8 *pos_of_slot)\n--\nkernel/bpf/core.c=3378=u32 bpf_jit_plan_arg_moves(const struct bpf_jit_arg_abi *abi,\n--\nkernel/bpf/core.c-3384-\nkernel/bpf/core.c:3385:\tnslots = bpf_jit_place_args(abi, fm, pos_of_slot);\nkernel/bpf/core.c-3386-\tback = nslots;\n--\nkernel/bpf/verifier.c=13598=static u32 kfunc_abi_slots(const struct btf_func_model *fm)\n--\nkernel/bpf/verifier.c-13609-\nkernel/bpf/verifier.c:13610:\tnslots = bpf_jit_place_args(abi, fm, pos_of_slot);\nkernel/bpf/verifier.c-13611-\tfor (i = 0; i \u003c nslots; i++)\n"
}

5/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24052
							output: 55
							thoughts: 19
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing the Callers of `__arch_prepare_bpf_trampoline`**

Right, let's dive in. I need to understand where `__arch_prepare_bpf_trampoline` is being used. This function is critical for BPF, specifically dealing with the architectural specifics of setting up the trampoline. Its behavior directly impacts the integrity and security of the BPF execution environment, so understanding its call graph is paramount for impact analysis. I'm thinking I'll need to trace its callers, starting with the immediate ones, but I'll also want to understand the *contexts* those immediate callers are operating in. Are they directly related to BPF JIT compilation? Are they within the core BPF subsystem, or are they potentially integrating it from other kernel modules? Furthermore, I'll need to check if there are any architecture-specific hooks or variations in its usage across different architectures, and what those look like. This information is key to understanding potential vulnerabilities or side effects introduced by changes to this function, or downstream issues from any patches applied. My goal is to paint a complete picture of its lifecycle and dependencies.



6/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "__arch_prepare_bpf_trampoline|arch_prepare_bpf_trampoline",
  "PathPrefixes": [
    "arch/x86/",
    "kernel/bpf/"
  ]
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=3922=static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog,\n--\narch/x86/net/bpf_jit_comp.c-3943-\t * bpf_tramp_run_ctx is already preserved by\narch/x86/net/bpf_jit_comp.c:3944:\t * arch_prepare_bpf_trampoline().\narch/x86/net/bpf_jit_comp.c-3945-\t *\n--\narch/x86/net/bpf_jit_comp.c=4068=static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,\n--\narch/x86/net/bpf_jit_comp.c-4170- */\narch/x86/net/bpf_jit_comp.c:4171:static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\narch/x86/net/bpf_jit_comp.c-4172-\t\t\t\t\t void *rw_image_end, void *image,\n--\narch/x86/net/bpf_jit_comp.c=4490=int arch_protect_bpf_trampoline(void *image, unsigned int size)\n--\narch/x86/net/bpf_jit_comp.c-4494-\narch/x86/net/bpf_jit_comp.c:4495:int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *image_end,\narch/x86/net/bpf_jit_comp.c-4496-\t\t\t\tconst struct btf_func_model *m, u32 flags,\n--\narch/x86/net/bpf_jit_comp.c-4510-\narch/x86/net/bpf_jit_comp.c:4511:\tret = __arch_prepare_bpf_trampoline(im, rw_image, rw_image + size, image, m,\narch/x86/net/bpf_jit_comp.c-4512-\t\t\t\t\t    flags, tnodes, func_addr);\n--\narch/x86/net/bpf_jit_comp.c=4524=int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,\n--\narch/x86/net/bpf_jit_comp.c-4531-\narch/x86/net/bpf_jit_comp.c:4532:\treturn __arch_prepare_bpf_trampoline(\u0026im, trampoline_size_image,\narch/x86/net/bpf_jit_comp.c-4533-\t\t\t\t\t     trampoline_size_image + PAGE_SIZE,\n--\nkernel/bpf/bpf_struct_ops.c=638=int bpf_struct_ops_prepare_trampoline(struct bpf_tramp_nodes *tnodes,\n--\nkernel/bpf/bpf_struct_ops.c-669-\nkernel/bpf/bpf_struct_ops.c:670:\tsize = arch_prepare_bpf_trampoline(NULL, image + image_off,\nkernel/bpf/bpf_struct_ops.c-671-\t\t\t\t\t   image + image_off + size,\n--\nkernel/bpf/trampoline.c=712=static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex,\n--\nkernel/bpf/trampoline.c-782-\nkernel/bpf/trampoline.c:783:\terr = arch_prepare_bpf_trampoline(im, im-\u003eimage, im-\u003eimage + size,\nkernel/bpf/trampoline.c-784-\t\t\t\t\t  \u0026tr-\u003efunc.model, tr-\u003eflags, tnodes,\n--\nkernel/bpf/trampoline.c=1530=int __weak\nkernel/bpf/trampoline.c:1531:arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *image_end,\nkernel/bpf/trampoline.c-1532-\t\t\t    const struct btf_func_model *m, u32 flags,\n"
}

7/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25511
							output: 34
							thoughts: 17
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Investigating `bpf_check_attach_target` Callers**

Alright, time to dive into this. The initial thought, of course, is to understand the context. So, my mind immediately jumps to "Let's check the callers of `bpf_check_attach_target`." This isn't just a casual glance; it's a strategic move. I want to build a mental map of how this function fits into the larger BPF ecosystem.

I'm thinking about tracing the call graph. What's calling it? Is it invoked directly from the kernel's tracepoint mechanism? Are there specific subsystems, like networking or security modules, that are frequently leveraging this function for attachment checks? Understanding the call sites is key to understanding its purpose and potential performance bottlenecks or security implications. I'm essentially trying to identify the "who," "what," and "why" behind the function's usage. Are there interesting edge-cases to be found here?

This initial step is absolutely critical. By examining the callers, I'm hoping to quickly grasp the common usage patterns, potential misuse scenarios, and the overall design considerations that shaped the function's behavior. I want to build a mental model of how this function operates in the grand scheme of things.



8/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "bpf_check_attach_target",
  "PathPrefixes": [
    "kernel/bpf/"
  ]
}

Results:
{
  "Output": "kernel/bpf/btf.c=7420=bool btf_ctx_access(int off, int size, enum bpf_access_type type,\n--\nkernel/bpf/btf.c-7604-\t\t\treturn false;\nkernel/bpf/btf.c:7605:\t\t/* Checked by bpf_check_attach_target */\nkernel/bpf/btf.c-7606-\t\ttname += sizeof(\"btf_trace_\") - 1;\n--\nkernel/bpf/syscall.c=3678=static int bpf_tracing_prog_attach(struct bpf_prog *prog,\n--\nkernel/bpf/syscall.c-3810-\nkernel/bpf/syscall.c:3811:\t\terr = bpf_check_attach_target(NULL, prog, tgt_prog, btf_id,\nkernel/bpf/syscall.c-3812-\t\t\t\t\t      \u0026tgt_info);\n--\nkernel/bpf/trampoline.c=1176=int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,\n--\nkernel/bpf/trampoline.c-1186-\nkernel/bpf/trampoline.c:1187:\terr = bpf_check_attach_target(NULL, prog, NULL,\nkernel/bpf/trampoline.c-1188-\t\t\t\t      prog-\u003eaux-\u003eattach_btf_id,\n--\nkernel/bpf/verifier.c=14541=static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-14552- * LSM hooks and iters (both sleepable and non-sleepable) are safe.\nkernel/bpf/verifier.c:14553: * Any sleepable progs are also safe since bpf_check_attach_target() enforce\nkernel/bpf/verifier.c-14554- * them can only be attached to some specific hook points.\n--\nkernel/bpf/verifier.c=22041=static int check_unreliable_func_args(struct bpf_verifier_log *log, struct btf *btf,\n--\nkernel/bpf/verifier.c-22061-\nkernel/bpf/verifier.c:22062:int bpf_check_attach_target(struct bpf_verifier_log *log,\nkernel/bpf/verifier.c-22063-\t\t\t    const struct bpf_prog *prog,\n--\nkernel/bpf/verifier.c=22496=static int check_attach_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-22526-\nkernel/bpf/verifier.c:22527:\tret = bpf_check_attach_target(\u0026env-\u003elog, prog, tgt_prog, btf_id, \u0026tgt_info);\nkernel/bpf/verifier.c-22528-\tif (ret)\n"
}

9/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26623
							output: 36
							thoughts: 121
						

Thoughts:
**My Analysis of BPF Program Attachment and Trampolines**

Okay, so I'm seeing that `bpf_check_attach_target` gets invoked at two crucial points: first, during the BPF program verification process, specifically within `check_attach_btf_id`, and then again when we're actually attaching tracing programs via `bpf_tracing_prog_attach` in `kernel/bpf/syscall.c`. This makes sense; we need to validate the target *before* we try to attach, and then we need to validate it again when attaching. I knew it, and I am glad that I am not wrong.

Furthermore, `arch_prepare_bpf_trampoline` is called during trampoline preparation, which is done through `bpf_trampoline_update` and `bpf_struct_ops_prepare_trampoline`. That gives me a complete view.

Now, my next step is to nail down any Kconfig dependencies for this code. Let me check the `arch/x86/net/Makefile` and see if anything jumps out.



10/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "bpf_jit_comp",
  "PathPrefixes": [
    "arch/x86/net/Makefile"
  ]
}

Results:
{
  "Output": "arch/x86/net/Makefile=6=ifeq ($(CONFIG_X86_32),y)\narch/x86/net/Makefile:7:        obj-$(CONFIG_BPF_JIT) += bpf_jit_comp32.o\narch/x86/net/Makefile-8-else\narch/x86/net/Makefile:9:        obj-$(CONFIG_BPF_JIT) += bpf_jit_comp.o bpf_timed_may_goto.o\narch/x86/net/Makefile-10-endif\n"
}

11/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27160
							output: 58
							thoughts: 40
						

Thoughts:
Here's my take on the thought process, summarized as you requested:

**Assessing BPF JIT and System Call Configuration**

Okay, so I'm diving into this kernel code and I'm seeing `CONFIG_BPF_JIT` and `CONFIG_BPF_SYSCALL`. My immediate thought is, "Right, let's nail down what's enabled where." To do that effectively, I need to know where these flags are defined and used. I'm going to start by confirming the existence and purpose of `BPF_JIT` using Kconfig. Then, I need to look into the implementation of `bpf_trampoline`, as this is a key component in the BPF architecture that would tie these configurations together. I'm trying to mentally map out how these pieces fit within the larger BPF framework to understand their impact and how they're influencing the system's behavior. Time to delve into the Kconfig files and source code!



12/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "config BPF_JIT|config BPF_SYSCALL",
  "PathPrefixes": [
    "init/Kconfig",
    "kernel/bpf/Kconfig",
    "net/core/Kconfig"
  ]
}

Results:
{
  "Output": "kernel/bpf/Kconfig=29=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-30-\nkernel/bpf/Kconfig:31:config BPF_SYSCALL\nkernel/bpf/Kconfig-32-\tbool \"Enable bpf() system call\"\n--\nkernel/bpf/Kconfig-45-\nkernel/bpf/Kconfig:46:config BPF_JIT\nkernel/bpf/Kconfig-47-\tbool \"Enable BPF Just In Time compiler\"\n--\nkernel/bpf/Kconfig-61-\nkernel/bpf/Kconfig:62:config BPF_JIT_ALWAYS_ON\nkernel/bpf/Kconfig-63-\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n--\nkernel/bpf/Kconfig-72-\nkernel/bpf/Kconfig:73:config BPF_JIT_DEFAULT_ON\nkernel/bpf/Kconfig-74-\tdef_bool ARCH_WANT_DEFAULT_BPF_JIT || BPF_JIT_ALWAYS_ON\n--\nkernel/bpf/Kconfig=105=config BPF_LSM\n--\nkernel/bpf/Kconfig-116-\nkernel/bpf/Kconfig:117:config BPF_JIT_KASAN\nkernel/bpf/Kconfig-118-\tbool\n"
}

13/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27815
							output: 42
							thoughts: 19
						

Thoughts:
Okay, here's my summary, tailored for an expert audience, as if it's *my* train of thought:

**Checking BPF_TRAMPOLINE Implementation**

Alright, let's get down to brass tacks. I need to figure out the status of `BPF_TRAMPOLINE`. My initial hunch is that it's likely managed either as a configuration option, or perhaps it’s conditional, depending on other kernel features or hardware support. Understanding this is crucial before I dive any deeper. Knowing how the feature is enabled (or disabled) is step one. Is it a compile-time flag controlled by the kernel build system? Or does it depend on the presence of specific hardware capabilities, requiring runtime checks? Finding the answer to this will dictate the direction of my next investigation. I need to confirm whether this is a hard requirement, a soft dependency, or something that's optional based on the system's setup. This knowledge will directly impact the feasibility and portability of anything I plan to do with it.



14/2 2026/10/17 03:21 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "BPF_TRAMPOLINE",
  "PathPrefixes": [
    "kernel/bpf/",
    "arch/x86/"
  ]
}

Results:
{
  "error": "no matches"
}

15/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28106
							output: 31
							thoughts: 30
						

Thoughts:
Here's my take on the thought process, from an expert's perspective:

**Pinpointing the bpf_trampoline**

Okay, so I need to understand the control flow around `bpf_trampoline`. First, I want to figure out where the compilation or guarding of `arch_prepare_bpf_trampoline` is taking place, so let's start with a focused search. I need to trace this back and see how this particular function is getting compiled so that I know exactly the build conditions. I need to understand the dependencies and build configuration involved, because that will expose how it's linked into the larger picture of BPF operation. Essentially, I need to know under what circumstances this functionality is available.



16/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "trampoline\\.o",
  "PathPrefixes": [
    "kernel/bpf/Makefile"
  ]
}

Results:
{
  "Output": "kernel/bpf/Makefile=16=obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o\nkernel/bpf/Makefile:17:obj-$(CONFIG_BPF_JIT) += trampoline.o\nkernel/bpf/Makefile-18-obj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o stream.o\n"
}

17/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28429
							output: 43
							thoughts: 87
						

Thoughts:
Okay, here's my breakdown of what I'm looking at, from my perspective as someone deeply familiar with kernel configuration:

**Kernel BPF Configuration Deep Dive**

Right, so I'm focusing on BPF configuration, specifically the core settings. My primary concern, as I'm thinking about this, revolves around the interaction of `CONFIG_BPF_SYSCALL` and `CONFIG_BPF_JIT`. Those are the big ones, the foundational switches. Now, I need to understand the dependencies and implications, so my mind immediately jumps to BTF and how it's handled in relation to targets and trampolines, which gives rise to `bpf_check_attach_target`. I'm going to look more closely at the `DEBUG_INFO_BTF` option to verify this. Finally, to gain a solid understanding of `BPF_SYSCALL`, I know I'll want to review its definitions and context within `kernel/bpf/Kconfig`. I'm starting from the broad strokes and zooming in on the specifics related to how BPF interacts with the system.



18/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "DEBUG_INFO_BTF",
  "PathPrefixes": [
    "kernel/bpf/Kconfig",
    "lib/Kconfig.debug"
  ]
}

Results:
{
  "Output": "lib/Kconfig.debug=378=config DEBUG_INFO_SPLIT\n--\nlib/Kconfig.debug-397-\nlib/Kconfig.debug:398:config DEBUG_INFO_BTF\nlib/Kconfig.debug-399-\tbool \"Generate BTF type information\"\n--\nlib/Kconfig.debug=419=config PAHOLE_HAS_LANG_EXCLUDE\n--\nlib/Kconfig.debug-425-\t  otherwise it would emit malformed kernel and module binaries when\nlib/Kconfig.debug:426:\t  using DEBUG_INFO_BTF_MODULES.\nlib/Kconfig.debug-427-\nlib/Kconfig.debug=428=config PAHOLE_HAS_INLINE\n--\nlib/Kconfig.debug-433-\nlib/Kconfig.debug:434:config DEBUG_INFO_BTF_MODULES\nlib/Kconfig.debug-435-\tbool \"Generate BTF type information for kernel modules\"\nlib/Kconfig.debug-436-\tdefault y\nlib/Kconfig.debug:437:\tdepends on DEBUG_INFO_BTF \u0026\u0026 MODULES\nlib/Kconfig.debug-438-\thelp\n--\nlib/Kconfig.debug-440-\nlib/Kconfig.debug:441:config DEBUG_INFO_BTF_INLINE\nlib/Kconfig.debug-442-\ttristate \"Provide information about inline sites in BTF\"\nlib/Kconfig.debug-443-\tdefault n\nlib/Kconfig.debug:444:\tdepends on DEBUG_INFO_BTF \u0026\u0026 PAHOLE_HAS_INLINE \u0026\u0026 SYSFS\nlib/Kconfig.debug:445:\tselect DEBUG_INFO_BTF_MODULES if MODULES\nlib/Kconfig.debug-446-\thelp\n--\nlib/Kconfig.debug=453=config MODULE_ALLOW_BTF_MISMATCH\nlib/Kconfig.debug-454-\tbool \"Allow loading modules with non-matching BTF type info\"\nlib/Kconfig.debug:455:\tdepends on DEBUG_INFO_BTF_MODULES\nlib/Kconfig.debug-456-\thelp\n"
}

19/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29339
							output: 39
							thoughts: 112
						

Thoughts:
**Analyzing BPF Program Attachment Points**

Okay, let's start by considering where we can hook into the system with BPF. My initial thoughts are to investigate `BPF_EVENTS` or perhaps delve into tracing with kprobes and similar mechanisms. I need to refresh my memory on the program types that leverage trampolines. It looks like we're working with `fentry`, `fexit`, `fmod_ret`, `lsm`, and `struct_ops` specifically. From a practical standpoint, this translates to tracing programs primarily. I'm thinking of `BPF_PROG_TYPE_TRACING`, `BPF_PROG_TYPE_LSM`, `BPF_PROG_TYPE_STRUCT_OPS`, etc. which are all relevant.

To solidify my understanding, I need to look at `kernel/bpf/verifier.c`. Specifically, I'll focus on the area around the `bpf_check_attach_target` function to confirm and clarify my understanding of this part of the process.



20/2 2026/10/17 03:21 tool grepper
0m Args:
{
  "Expression": "int bpf_check_attach_target\\(",
  "PathPrefixes": [
    "kernel/bpf/verifier.c"
  ]
}

Results:
{
  "Output": "kernel/bpf/verifier.c=22041=static int check_unreliable_func_args(struct bpf_verifier_log *log, struct btf *btf,\n--\nkernel/bpf/verifier.c-22061-\nkernel/bpf/verifier.c:22062:int bpf_check_attach_target(struct bpf_verifier_log *log,\nkernel/bpf/verifier.c-22063-\t\t\t    const struct bpf_prog *prog,\n"
}

21/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29847
							output: 38
							thoughts: 23
						

Thoughts:
Okay, here's my interpretation of what's going through my mind as I approach line 22062 in `kernel/bpf/verifier.c`, rendered in the first person, as requested:

**Diving into the BPF Verifier's Depths**

Alright, time to get my hands dirty. Line 22062 in `kernel/bpf/verifier.c`, eh? That usually means we're deep in the weeds of the BPF verifier's logic. Given the size and complexity of that beast, I'm expecting to find some intricate conditional statement, a critical loop, or maybe even a state transition that's giving someone (or a piece of code) a headache. It's probably related to a specific instruction class, register management, or data flow analysis.

I'm mentally preparing for a combination of control flow analysis, data dependency inspection, and type inference. I'll be looking for potential vulnerabilities—anything that might allow an attacker to craft a malicious BPF program that bypasses the verifier's checks. That includes things like out-of-bounds memory accesses, register overflows, or incorrect use of helper functions. I need to be thinking about how the verifier's internal state is being modified, how it interacts with the program's registers and stack, and how any potential issues might be exploited.

Given my experience, I'll need to remember to consider the surrounding context. It is very rare a single line tells the full story. I'll need to quickly understand what the code immediately before and after the line is trying to achieve. I should look at the variables involved, the surrounding functions, and any related structures. What are the inputs to this section of code? What are the outputs? What invariants is the code trying to maintain? I need the big picture to fully grasp the code's impact. Ultimately, I need to understand whether this line is performing its intended function securely, or whether it contains any subtle flaws that could be exploited. Let's see what we've got.



22/2 2026/10/17 03:21 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 22060,
  "LineCount": 50
}

Results:
{
  "Contents": "22060:\t}\n22061:\t\n22062:\tint bpf_check_attach_target(struct bpf_verifier_log *log,\n22063:\t\t\t\t    const struct bpf_prog *prog,\n22064:\t\t\t\t    const struct bpf_prog *tgt_prog,\n22065:\t\t\t\t    u32 btf_id,\n22066:\t\t\t\t    struct bpf_attach_target_info *tgt_info)\n22067:\t{\n22068:\t\tbool prog_extension = prog-\u003etype == BPF_PROG_TYPE_EXT;\n22069:\t\tbool prog_tracing = prog-\u003etype == BPF_PROG_TYPE_TRACING;\n22070:\t\tchar trace_symbol[KSYM_SYMBOL_LEN];\n22071:\t\tconst char prefix[] = \"btf_trace_\";\n22072:\t\tstruct bpf_raw_event_map *btp;\n22073:\t\tint ret = 0, subprog = -1, i;\n22074:\t\tconst struct btf_type *t;\n22075:\t\tbool conservative = true;\n22076:\t\tconst char *tname, *fname;\n22077:\t\tstruct btf *btf;\n22078:\t\tlong addr = 0;\n22079:\t\tstruct module *mod = NULL;\n22080:\t\n22081:\t\tif (!btf_id) {\n22082:\t\t\tbpf_log(log, \"Tracing programs must provide btf_id\\n\");\n22083:\t\t\treturn -EINVAL;\n22084:\t\t}\n22085:\t\tbtf = tgt_prog ? tgt_prog-\u003eaux-\u003ebtf : prog-\u003eaux-\u003eattach_btf;\n22086:\t\tif (!btf) {\n22087:\t\t\tbpf_log(log,\n22088:\t\t\t\t\"Tracing program can only be attached to another program annotated with BTF\\n\");\n22089:\t\t\treturn -EINVAL;\n22090:\t\t}\n22091:\t\tt = btf_type_by_id(btf, btf_id);\n22092:\t\tif (!t) {\n22093:\t\t\tbpf_log(log, \"attach_btf_id %u is invalid\\n\", btf_id);\n22094:\t\t\treturn -EINVAL;\n22095:\t\t}\n22096:\t\ttname = btf_name_by_offset(btf, t-\u003ename_off);\n22097:\t\tif (!tname) {\n22098:\t\t\tbpf_log(log, \"attach_btf_id %u doesn't have a name\\n\", btf_id);\n22099:\t\t\treturn -EINVAL;\n22100:\t\t}\n22101:\t\tif (tgt_prog) {\n22102:\t\t\tstruct bpf_prog_aux *aux = tgt_prog-\u003eaux;\n22103:\t\t\tbool tgt_changes_pkt_data;\n22104:\t\t\tbool tgt_might_sleep;\n22105:\t\n22106:\t\t\tif (bpf_prog_is_dev_bound(prog-\u003eaux) \u0026\u0026\n22107:\t\t\t    !bpf_prog_dev_bound_match(prog, tgt_prog)) {\n22108:\t\t\t\tbpf_log(log, \"Target program bound device mismatch\");\n22109:\t\t\t\treturn -EINVAL;\n"
}

23/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31264
							output: 38
							thoughts: 28
						

Thoughts:
Okay, let's dive into this. Here's my take on those lines:

**Analyzing `kernel/bpf/verifier.c` Lines 22320-22360**

Right, let's zero in on those specific lines: 22320 through 22360 in the BPF verifier. The first thing I'm doing is preparing to get contextually aware of the surrounding code. My initial thought is to recall where this section falls within the overall verification process. Is this related to register state management? Instruction analysis? Maybe dealing with loop unwinding or error handling? I need to understand the larger framework.

I'm expecting this block to deal with, potentially, a specific instruction class or perhaps a particular verification rule. Given the line range, I'm thinking it’s likely a smaller, self-contained aspect, like a check for overflow conditions, dealing with memory access bounds, or perhaps something related to the handling of specific BPF helper functions. Perhaps it's dealing with a tricky edge case.

I'll be looking for clues in the variable names and comments. Are they doing a pointer arithmetic validation? Dealing with constant propagation? The goal is to determine the function of this chunk of code relative to the overarching program verification. The ultimate goal is to ascertain that the program is safe to run and doesn't contain any malicious or erroneous constructs. Let's see what we can find.



24/2 2026/10/17 03:21 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 22320,
  "LineCount": 40
}

Results:
{
  "Contents": "22320:\t\t\tt = btf_attach_func_proto(log, btf, btf_id);\n22321:\t\t\tif (!t || !btf_type_is_func_proto(t))\n22322:\t\t\t\treturn -EINVAL;\n22323:\t\n22324:\t\t\tif ((prog-\u003eaux-\u003esaved_dst_prog_type || prog-\u003eaux-\u003esaved_dst_attach_type) \u0026\u0026\n22325:\t\t\t    (!tgt_prog || prog-\u003eaux-\u003esaved_dst_prog_type != tgt_prog-\u003etype ||\n22326:\t\t\t     prog-\u003eaux-\u003esaved_dst_attach_type != tgt_prog-\u003eexpected_attach_type))\n22327:\t\t\t\treturn -EINVAL;\n22328:\t\n22329:\t\t\tif (tgt_prog \u0026\u0026 conservative) {\n22330:\t\t\t\tif (subprog \u003e 0) {\n22331:\t\t\t\t\tret = check_unreliable_func_args(log, btf, t, tname);\n22332:\t\t\t\t\tif (ret)\n22333:\t\t\t\t\t\treturn ret;\n22334:\t\t\t\t}\n22335:\t\t\t\tt = NULL;\n22336:\t\t\t}\n22337:\t\n22338:\t\t\tret = btf_distill_func_proto(log, btf, t, tname, \u0026tgt_info-\u003efmodel);\n22339:\t\t\tif (ret \u003c 0)\n22340:\t\t\t\treturn ret;\n22341:\t\t\tif (tgt_prog) {\n22342:\t\t\t\tret = flatten_bpf_func_model(log, tname, \u0026tgt_info-\u003efmodel);\n22343:\t\t\t\tif (ret \u003c 0)\n22344:\t\t\t\t\treturn ret;\n22345:\t\t\t}\n22346:\t\n22347:\t\t\tif (tgt_info-\u003efmodel.ret_size \u003e 8 \u0026\u0026\n22348:\t\t\t    attach_uses_trampoline_retval(prog-\u003eexpected_attach_type)) {\n22349:\t\t\t\tbpf_log(log,\n22350:\t\t\t\t\t\"Attach to function %s with a \u003e8 byte return value is not supported for this attach type\\n\",\n22351:\t\t\t\t\ttname);\n22352:\t\t\t\treturn -EOPNOTSUPP;\n22353:\t\t\t}\n22354:\t\n22355:\t\t\t/*\n22356:\t\t\t * *.multi programs don't need an address during program\n22357:\t\t\t * verification, we just take the module ref if needed.\n22358:\t\t\t */\n22359:\t\t\tif (is_tracing_multi_id(prog, btf_id)) {\n"
}

25/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32347
							output: 45
							thoughts: 35
						

Thoughts:
Okay, here's my interpretation of what's going on, thinking through this as I would:

**Diving into the BPF JIT Trampoline Preparation in x86**

Alright, I'm going to take a look at `arch/x86/net/bpf_jit_comp.c`, specifically focusing on the `__arch_prepare_bpf_trampoline` function. My initial thought is to figure out exactly what's happening at this level of the BPF JIT on x86. I know the trampoline is crucial for the efficient execution of BPF programs, and how this function prepares it will directly impact performance and security. I need to understand the details of the x86 architecture and how the JIT compiler leverages it to optimize BPF instructions. I'm expecting this function to be the core of setting up the machine code structure that transitions from the kernel's execution context to the compiled BPF program and then back. I'll be looking for how registers are saved and restored, how the program counter (IP) is managed for this transition, and the likely use of instruction sequences optimized for speed and safety. Understanding this function is going to be vital to understanding x86-specific BPF execution behavior and potential optimization opportunities.



26/2 2026/10/17 03:21 tool read-file
0m Args:
{
  "File": "arch/x86/net/bpf_jit_comp.c",
  "FirstLine": 4190,
  "LineCount": 50
}

Results:
{
  "Contents": "4190:\t\tbool save_ret;\n4191:\t\n4192:\t\t/*\n4193:\t\t * F_INDIRECT is only compatible with F_RET_FENTRY_RET, it is\n4194:\t\t * explicitly incompatible with F_CALL_ORIG | F_SKIP_FRAME | F_IP_ARG\n4195:\t\t * because @func_addr.\n4196:\t\t */\n4197:\t\tWARN_ON_ONCE((flags \u0026 BPF_TRAMP_F_INDIRECT) \u0026\u0026\n4198:\t\t\t     (flags \u0026 ~(BPF_TRAMP_F_INDIRECT | BPF_TRAMP_F_RET_FENTRY_RET)));\n4199:\t\n4200:\t\tarena_base = bpf_tramp_arena_base(m, tnodes, flags);\n4201:\t\n4202:\t\t/*\n4203:\t\t * x86-64 supports up to MAX_BPF_FUNC_ARGS argument slots, six in\n4204:\t\t * registers and the rest on the stack, where bpf_jit_place_args()\n4205:\t\t * puts them.\n4206:\t\t */\n4207:\t\tnr_regs = bpf_jit_place_args(\u0026x86_arg_abi, m, pos_of_slot);\n4208:\t\tif (nr_regs \u003e MAX_BPF_FUNC_ARGS)\n4209:\t\t\treturn -ENOTSUPP;\n4210:\t\n4211:\t\t/* Generated trampoline stack layout:\n4212:\t\t *\n4213:\t\t * RBP + 8         [ return address  ]\n4214:\t\t * RBP + 0         [ RBP             ]\n4215:\t\t *\n4216:\t\t * RBP - 8         [ return value    ]  BPF_TRAMP_F_CALL_ORIG or\n4217:\t\t *                                      BPF_TRAMP_F_RET_FENTRY_RET flags\n4218:\t\t *\n4219:\t\t *                 [ arg slotN       ]  always, packed, whether\n4220:\t\t *                 [ ...             ]  in a register or on the\n4221:\t\t * RBP - regs_off  [ arg slot1       ]  stack; program's ctx pointer\n4222:\t\t *\n4223:\t\t * RBP - func_meta_off [ slot count, etc ]  always\n4224:\t\t *\n4225:\t\t * RBP - ip_off    [ traced function ]  BPF_TRAMP_F_IP_ARG flag\n4226:\t\t *\n4227:\t\t * RBP - rbx_off   [ rbx value       ]  always\n4228:\t\t *\n4229:\t\t * RBP - run_ctx_off [ bpf_tramp_run_ctx ]\n4230:\t\t *\n4231:\t\t * RBP - tcc_off   [ tail_call_cnt_ptr ]  BPF_TRAMP_F_TAIL_CALL_CTX\n4232:\t\t *\n4233:\t\t *                     [ stack slotN ]  BPF_TRAMP_F_CALL_ORIG,\n4234:\t\t *                     [ ...         ]  the incoming stack\n4235:\t\t *                     [ stack slot2 ]  arguments, alignment\n4236:\t\t * RBP - arg_stack_off [ stack slot1 ]  holes included\n4237:\t\t */\n4238:\t\n4239:\t\t/* room for return value of orig_call or fentry prog */\n"
}

27/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33608
							output: 45
							thoughts: 42
						

Thoughts:
Okay, let's dive into this. I need to take a look at `arch_prepare_bpf_trampoline`, specifically at line 4495 within `arch/x86/net/bpf_jit_comp.c`.  I know this is a core piece of the BPF JIT compilation process for x86 architectures, and the trampoline mechanism is crucial for the efficient execution of BPF programs. I need to understand what's happening at this specific line, as it’s likely a critical point in setting up the transition from the kernel to the JIT-compiled BPF code.  Knowing the specific context around line 4495 will illuminate the approach to preparing the execution environment and how any security measures are incorporated. It will be important to confirm the role of this routine in the overall trampoline architecture.



28/2 2026/10/17 03:21 tool read-file
0m Args:
{
  "File": "arch/x86/net/bpf_jit_comp.c",
  "FirstLine": 4495,
  "LineCount": 45
}

Results:
{
  "Contents": "4495:\tint arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *image_end,\n4496:\t\t\t\t\tconst struct btf_func_model *m, u32 flags,\n4497:\t\t\t\t\tstruct bpf_tramp_nodes *tnodes,\n4498:\t\t\t\t\tvoid *func_addr)\n4499:\t{\n4500:\t\tvoid *rw_image, *tmp;\n4501:\t\tint ret;\n4502:\t\tu32 size = image_end - image;\n4503:\t\n4504:\t\t/* rw_image doesn't need to be in module memory range, so we can\n4505:\t\t * use kvmalloc.\n4506:\t\t */\n4507:\t\trw_image = kvmalloc(size, GFP_KERNEL);\n4508:\t\tif (!rw_image)\n4509:\t\t\treturn -ENOMEM;\n4510:\t\n4511:\t\tret = __arch_prepare_bpf_trampoline(im, rw_image, rw_image + size, image, m,\n4512:\t\t\t\t\t\t    flags, tnodes, func_addr);\n4513:\t\tif (ret \u003c 0)\n4514:\t\t\tgoto out;\n4515:\t\n4516:\t\ttmp = bpf_arch_text_copy(image, rw_image, size);\n4517:\t\tif (IS_ERR(tmp))\n4518:\t\t\tret = PTR_ERR(tmp);\n4519:\tout:\n4520:\t\tkvfree(rw_image);\n4521:\t\treturn ret;\n4522:\t}\n4523:\t\n4524:\tint arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,\n4525:\t\t\t\t     struct bpf_tramp_nodes *tnodes, void *func_addr)\n4526:\t{\n4527:\t\tstruct bpf_tramp_image im = {};\n4528:\t\n4529:\t\tif (!trampoline_size_image)\n4530:\t\t\treturn -ENOMEM;\n4531:\t\n4532:\t\treturn __arch_prepare_bpf_trampoline(\u0026im, trampoline_size_image,\n4533:\t\t\t\t\t\t     trampoline_size_image + PAGE_SIZE,\n4534:\t\t\t\t\t\t     trampoline_size_image, m, flags,\n4535:\t\t\t\t\t\t     tnodes, func_addr);\n4536:\t}\n4537:\t\n4538:\tstatic int emit_bpf_dispatcher(u8 **pprog, int a, int b, s64 *progs, u8 *image, u8 *buf)\n4539:\t{\n"
}

29/2 2026/10/17 03:21 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34601
							output: 45
							thoughts: 1538
						

Thoughts:
**Fuzzing Strategy for BPF Trampoline and Verifier Changes**

Okay, let's break this down. This patch is touching some key areas: argument handling in BPF trampolines on x86-64, especially when things get complicated with more than six arguments, alignment issues, or tail call contexts. The verifier also sees some action with `bpf_check_attach_target`, `flatten_bpf_func_model`, and `check_unreliable_func_args`.

My initial thought? This is definitely worth fuzzing. The target architecture is x86-64, so that's a good start. The relevant code is within `arch/x86/net/bpf_jit_comp.c` and `kernel/bpf/verifier.c`. The trigger points are the standard BPF loading and verification pathways (`BPF_PROG_LOAD`), and specifically tracing programs, fentry/fexit, and trampoline attachments ( `BPF_RAW_TRACEPOINT_OPEN`, `BPF_LINK_CREATE`, `BPF_PROG_ATTACH` etc.). We can exercise this from userspace, so no hardware dependencies are needed, and QEMU/GCE can handle it.

This hits a core subsystem: the BPF JIT, verifier, and trampoline generation. The changes themselves look like they involve refactoring argument placement, modifying stack layout calculations, dealing with tail call offsets, and adding new verifier checks. All of these are ripe for fuzzing. We could easily trip up misaligned stacks, corrupted offsets, bad trampoline code, and out-of-bounds reads/writes during either trampoline execution or verification. So, fuzzing is a go.

Now, which functions should be our focus symbols? Let's sift through the non-ubiquitous functions. From `kernel/bpf/verifier.c`, `bpf_check_attach_target`, `flatten_bpf_func_model`, and `check_unreliable_func_args` stand out. From `arch/x86/net/bpf_jit_comp.c`, we have `arch_prepare_bpf_trampoline`, `__arch_prepare_bpf_trampoline`, and `arch_bpf_trampoline_size`. I need to confirm whether the static functions make it into vmlinux, but at first glance, the global ones are the most important.

`arch_prepare_bpf_trampoline` looks like a key entrypoint for the x86 JIT, where the trampoline code is actually constructed. `bpf_check_attach_target` is also critical, being the verifier function that validates the attach targets, and it calls the two functions we want to test! This seems like a perfect place to introduce various malformed attach targets. And `arch_bpf_trampoline_size` is good too, this is the function we should use to check for stack corruption.

Let's quickly confirm we aren't missing any other important functions. The changes don't touch `bpf_jit_place_args`, so that's off the list. The functions we are interested in are definitely non-ubiquitous, tied specifically to the trampoline generation and attachment features. These are the ones we want.

Finally, what about `EnableConfigs`? We'll need `BPF_SYSCALL`, obviously, for the `bpf()` syscall. `BPF_JIT` is critical; it enables both the JIT and trampoline support. `DEBUG_INFO_BTF` is also necessary because it enables BTF, and we need BTF to check attach targets. I'll make sure there are no other unexpected config dependencies for `BPF_JIT`.



30/2 2026/10/17 03:22 tool grepper
0m Args:
{
  "Expression": "config BPF_JIT\\b",
  "PathPrefixes": [
    "kernel/bpf/Kconfig",
    "net/core/Kconfig"
  ]
}

Results:
{
  "Output": "kernel/bpf/Kconfig=31=config BPF_SYSCALL\n--\nkernel/bpf/Kconfig-45-\nkernel/bpf/Kconfig:46:config BPF_JIT\nkernel/bpf/Kconfig-47-\tbool \"Enable BPF Just In Time compiler\"\n"
}

31/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37069
							output: 34
							thoughts: 21
						

Thoughts:
Okay, let's dive into this. I'm going to focus in on lines 30-80 of `kernel/bpf/Kconfig`. I need to get a clear picture of the configuration options related to BPF within the kernel. I already have a pretty good understanding, but I need to make sure I'm up-to-date with any recent changes and the nuances of how these features are exposed to the user. I'm expecting this section to cover the core BPF functionality – the enablers, the essential build flags, potentially some basic feature gating, and maybe even the inclusion of helper functions and tracepoints. My goal is to quickly pinpoint any additions or modifications to the existing structure. I'll be looking for keywords like "BPF," "tracing," "security," "JIT," and any new options I haven't seen before. I am going to pay attention to any conditional dependencies to see which other options are connected. Understanding the dependencies will help me reason about the feature set. Finally, I will verify my own understanding of this area.



32/2 2026/10/17 03:22 tool read-file
0m Args:
{
  "File": "kernel/bpf/Kconfig",
  "FirstLine": 30,
  "LineCount": 50
}

Results:
{
  "Contents": "  30:\t\n  31:\tconfig BPF_SYSCALL\n  32:\t\tbool \"Enable bpf() system call\"\n  33:\t\tselect BPF\n  34:\t\tselect IRQ_WORK\n  35:\t\tselect NEED_TASKS_RCU\n  36:\t\tselect TASKS_TRACE_RCU\n  37:\t\tselect BINARY_PRINTF\n  38:\t\tselect NET_SOCK_MSG if NET\n  39:\t\tselect NET_XGRESS if NET\n  40:\t\tselect PAGE_POOL if NET\n  41:\t\tdefault n\n  42:\t\thelp\n  43:\t\t  Enable the bpf() system call that allows to manipulate BPF programs\n  44:\t\t  and maps via file descriptors.\n  45:\t\n  46:\tconfig BPF_JIT\n  47:\t\tbool \"Enable BPF Just In Time compiler\"\n  48:\t\tdepends on BPF\n  49:\t\tdepends on HAVE_CBPF_JIT || HAVE_EBPF_JIT\n  50:\t\tselect EXECMEM\n  51:\t\thelp\n  52:\t\t  BPF programs are normally handled by a BPF interpreter. This option\n  53:\t\t  allows the kernel to generate native code when a program is loaded\n  54:\t\t  into the kernel. This will significantly speed-up processing of BPF\n  55:\t\t  programs.\n  56:\t\n  57:\t\t  Note, an admin should enable this feature changing:\n  58:\t\t  /proc/sys/net/core/bpf_jit_enable\n  59:\t\t  /proc/sys/net/core/bpf_jit_harden   (optional)\n  60:\t\t  /proc/sys/net/core/bpf_jit_kallsyms (optional)\n  61:\t\n  62:\tconfig BPF_JIT_ALWAYS_ON\n  63:\t\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n  64:\t\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\n  65:\t\thelp\n  66:\t\t  Enables BPF JIT and removes BPF interpreter to avoid speculative\n  67:\t\t  execution of BPF instructions by the interpreter.\n  68:\t\n  69:\t\t  When CONFIG_BPF_JIT_ALWAYS_ON is enabled, /proc/sys/net/core/bpf_jit_enable\n  70:\t\t  is permanently set to 1 and setting any other value than that will\n  71:\t\t  return failure.\n  72:\t\n  73:\tconfig BPF_JIT_DEFAULT_ON\n  74:\t\tdef_bool ARCH_WANT_DEFAULT_BPF_JIT || BPF_JIT_ALWAYS_ON\n  75:\t\tdepends on HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\n  76:\t\n  77:\tconfig BPF_UNPRIV_DEFAULT_OFF\n  78:\t\tbool \"Disable unprivileged BPF by default\"\n  79:\t\tdefault y\n"
}

33/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38110
							output: 32
							thoughts: 10
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:

**Checking `BPF_EVENTS`**

Alright, let's just quickly confirm something fundamental before we dive deeper. Before I start going down any complex paths, I need to make sure I haven't missed any obvious or critical information. My first instinct, whenever I'm dealing with a new investigation or debugging scenario, is to make sure I haven't forgotten the core piece of information. Specifically, I should check the relevant documentation or source code and see if `BPF_EVENTS` are well-documented. It's a key aspect to understanding my current work. Given the nature of this inquiry, I will consider `BPF_EVENTS` as a starting point. This will give me some quick clarity before jumping into other areas of thought.



34/2 2026/10/17 03:22 tool grepper
0m Args:
{
  "Expression": "config BPF_EVENTS",
  "PathPrefixes": [
    "kernel/trace/Kconfig"
  ]
}

Results:
{
  "Output": "kernel/trace/Kconfig=851=config EPROBE_EVENTS\n--\nkernel/trace/Kconfig-864-\nkernel/trace/Kconfig:865:config BPF_EVENTS\nkernel/trace/Kconfig-866-\tdepends on BPF_SYSCALL\n"
}

35/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38410
							output: 34
							thoughts: 20
						

Thoughts:
Here's my thought process, as requested:

**Checking Kernel Trace Configuration**

Okay, so I need to understand this `BPF_EVENTS` thing a bit better. My first instinct is to jump straight into the kernel source and locate its definition. Specifically, I'll head over to `kernel/trace/Kconfig`. That's where configuration options like this are usually declared. It's the central place to figure out what `BPF_EVENTS` actually *means* within the kernel's tracing framework. I'm expecting to find details about whether it's enabled by default, what dependencies it might have, and maybe even a brief description of its purpose. Knowing that will give me a great foundation.



36/2 2026/10/17 03:22 tool read-file
0m Args:
{
  "File": "kernel/trace/Kconfig",
  "FirstLine": 860,
  "LineCount": 20
}

Results:
{
  "Contents": " 860:\t\t  events. It can be used to limit what fields are recorded in\n 861:\t\t  an event or even dereference a field of an event. It can\n 862:\t\t  convert the type of an event field. For example, turn an\n 863:\t\t  address into a string.\n 864:\t\n 865:\tconfig BPF_EVENTS\n 866:\t\tdepends on BPF_SYSCALL\n 867:\t\tdepends on (KPROBE_EVENTS || UPROBE_EVENTS) \u0026\u0026 PERF_EVENTS\n 868:\t\tbool\n 869:\t\tdefault y\n 870:\t\thelp\n 871:\t\t  This allows the user to attach BPF programs to kprobe, uprobe, and\n 872:\t\t  tracepoint events.\n 873:\t\n 874:\tconfig DYNAMIC_EVENTS\n 875:\t\tdef_bool n\n 876:\t\n 877:\tconfig PROBE_EVENTS\n 878:\t\tdef_bool n\n 879:\t\n"
}

37/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38895
							output: 46
							thoughts: 64
						

Thoughts:
Okay, here's my assessment of the situation:

**Investigating Potential Conflicts with eBPF Trampolines**

Alright, I'm thinking, could `FUNCTION_TRACER` or `DYNAMIC_FTRACE_WITH_DIRECT_CALLS` present any compatibility issues with how eBPF trampolines operate? It's always a good practice to proactively look for potential conflicts, especially when we are talking about two very powerful tracing features. I need to understand if they might be stepping on each other's toes.

So, the first thing to investigate is `kernel/bpf/trampoline.c`. This file should contain the core implementation of the eBPF trampoline mechanism. I'll need to check the code there to see what it fundamentally relies on, or if there are any subtle dependencies that might be affected by these other tracing options. Are there assumptions about the underlying function call mechanisms or specific kernel hooks? Knowing this will give me a baseline for analysis.

Next, I need to look for any conditional compilation or guards within the `trampoline.c` code or elsewhere in related modules. Are there `#ifdef` blocks or similar constructs that are active or inactive depending on those other options? Are there any explicit checks for `FUNCTION_TRACER` or `DYNAMIC_FTRACE_WITH_DIRECT_CALLS`? Identifying these guards will be key to understanding the potential interaction, or lack thereof. It may reveal where developers have already thought about these aspects and handled potential conflicts (or haven't). From that, I can determine if any specific configurations need special consideration.



38/2 2026/10/17 03:22 tool grepper
0m Args:
{
  "Expression": "DYNAMIC_FTRACE_WITH_DIRECT_CALLS",
  "PathPrefixes": [
    "kernel/bpf/",
    "arch/x86/"
  ]
}

Results:
{
  "Output": "arch/x86/Kconfig=58=config X86\n--\narch/x86/Kconfig-233-\tselect HAVE_FTRACE_REGS_HAVING_PT_REGS\tif X86_64\narch/x86/Kconfig:234:\tselect HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\narch/x86/Kconfig-235-\tselect HAVE_DYNAMIC_FTRACE_WITH_JMP\tif X86_64\n--\narch/x86/Kconfig-342-\tselect ARCH_SUPPORTS_SYSCALL_USER_DISPATCH\narch/x86/Kconfig:343:\tselect HAVE_SINGLE_FTRACE_DIRECT_OPS\tif X86_64 \u0026\u0026 DYNAMIC_FTRACE_WITH_DIRECT_CALLS\narch/x86/Kconfig-344-\n--\narch/x86/include/asm/ftrace.h=83=void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,\n--\narch/x86/include/asm/ftrace.h-88-\narch/x86/include/asm/ftrace.h:89:#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\narch/x86/include/asm/ftrace.h-90-/*\n--\narch/x86/include/asm/ftrace.h=98=__arch_ftrace_set_direct_caller(struct pt_regs *regs, unsigned long addr)\n--\narch/x86/include/asm/ftrace.h-104-\t__arch_ftrace_set_direct_caller(\u0026arch_ftrace_regs(fregs)-\u003eregs, addr)\narch/x86/include/asm/ftrace.h:105:#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */\narch/x86/include/asm/ftrace.h-106-\n--\nkernel/bpf/trampoline.c=62=struct bpf_trampoline_ops {\n--\nkernel/bpf/trampoline.c-68-\nkernel/bpf/trampoline.c:69:#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\nkernel/bpf/trampoline.c-70-static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex,\n--\nkernel/bpf/trampoline.c=210=void bpf_image_ksym_del(struct bpf_ksym *ksym)\n--\nkernel/bpf/trampoline.c-216-\nkernel/bpf/trampoline.c:217:#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\nkernel/bpf/trampoline.c-218-#ifdef CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS\n--\nkernel/bpf/trampoline.c=367=static int direct_ops_mod(struct bpf_trampoline *tr, void *ptr, bool lock_direct_mutex)\n--\nkernel/bpf/trampoline.c-370-}\nkernel/bpf/trampoline.c:371:#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */\nkernel/bpf/trampoline.c-372-\n--\nkernel/bpf/trampoline.c=712=static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex,\n--\nkernel/bpf/trampoline.c-745-\nkernel/bpf/trampoline.c:746:#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\nkernel/bpf/trampoline.c-747-again:\n--\nkernel/bpf/trampoline.c-799-\nkernel/bpf/trampoline.c:800:#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\nkernel/bpf/trampoline.c-801-\tif (err == -EAGAIN) {\n--\nkernel/bpf/trampoline.c=1566=int __weak arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,\n--\nkernel/bpf/trampoline.c-1571-\nkernel/bpf/trampoline.c:1572:#if defined(CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS) \u0026\u0026 \\\nkernel/bpf/trampoline.c-1573-    defined(CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS) \u0026\u0026 \\\n--\nkernel/bpf/trampoline.c=1826=void bpf_trampoline_multi_detach(struct bpf_prog *prog,\n--\nkernel/bpf/trampoline.c-1861-\nkernel/bpf/trampoline.c:1862:#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS \u0026\u0026\nkernel/bpf/trampoline.c-1863-\t  CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS \u0026\u0026\n"
}

39/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 40586
							output: 40
							thoughts: 48
						

Thoughts:
Here's my take on those thoughts:

**Peering into the Depths of Dynamic Ftrace with Direct Calls**

Okay, so `DYNAMIC_FTRACE_WITH_DIRECT_CALLS` has a gatekeeper, specifically in `kernel/bpf/trampoline.c`. That's interesting, and it suggests a specific interaction point. I need to understand what triggers the *enabling* of `DYNAMIC_FTRACE_WITH_DIRECT_CALLS`. Given that it's likely linked to BPF trampolines, I'd expect it is related to the deployment of eBPF programs, or perhaps some specific BPF feature. I'll need to trace back from this guard to understand the configuration dependencies. Is it a module option, a kernel parameter, or tied to specific hardware capabilities? Time to dig in and figure out the dependencies.



40/2 2026/10/17 03:22 tool grepper
0m Args:
{
  "Expression": "config DYNAMIC_FTRACE_WITH_DIRECT_CALLS",
  "PathPrefixes": [
    "kernel/trace/Kconfig"
  ]
}

Results:
{
  "Output": "kernel/trace/Kconfig=324=config DYNAMIC_FTRACE_WITH_REGS\n--\nkernel/trace/Kconfig-328-\nkernel/trace/Kconfig:329:config DYNAMIC_FTRACE_WITH_DIRECT_CALLS\nkernel/trace/Kconfig-330-\tdef_bool y\n"
}

41/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 40944
							output: 34
							thoughts: 19
						

Thoughts:
Okay, let's dive into this. I'm going to pull up `kernel/trace/Kconfig` and zoom in around line 329. I'm expecting to see something related to tracepoints, most likely, but the specific configuration options will tell the real story. I need to understand what's being enabled or disabled in this section, and how it impacts the overall tracing capabilities. This could be about filtering, instrumentation types, buffer sizes, or the inclusion of specific tracepoint groups. Gotta look for dependencies too – are there prerequisites for these configurations? I need to build a mental map of this part of the tracing subsystem and how it fits into the bigger picture. Alright, let's see what's on line 329...



42/2 2026/10/17 03:22 tool read-file
0m Args:
{
  "File": "kernel/trace/Kconfig",
  "FirstLine": 325,
  "LineCount": 20
}

Results:
{
  "Contents": " 325:\t\tdef_bool y\n 326:\t\tdepends on DYNAMIC_FTRACE\n 327:\t\tdepends on HAVE_DYNAMIC_FTRACE_WITH_REGS\n 328:\t\n 329:\tconfig DYNAMIC_FTRACE_WITH_DIRECT_CALLS\n 330:\t\tdef_bool y\n 331:\t\tdepends on DYNAMIC_FTRACE_WITH_REGS || DYNAMIC_FTRACE_WITH_ARGS\n 332:\t\tdepends on HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\n 333:\t\n 334:\tconfig DYNAMIC_FTRACE_WITH_CALL_OPS\n 335:\t\tdef_bool y\n 336:\t\tdepends on HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS\n 337:\t\n 338:\tconfig DYNAMIC_FTRACE_WITH_ARGS\n 339:\t\tdef_bool y\n 340:\t\tdepends on DYNAMIC_FTRACE\n 341:\t\tdepends on HAVE_DYNAMIC_FTRACE_WITH_ARGS\n 342:\t\n 343:\tconfig DYNAMIC_FTRACE_WITH_JMP\n 344:\t\tdef_bool y\n"
}

43/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41464
							output: 35
							thoughts: 93
						

Thoughts:
Here's my understanding of the dependency chain, as I see it:

**Tracing Dependencies - A Quick Audit**

Okay, so I'm looking at `DYNAMIC_FTRACE_WITH_DIRECT_CALLS`, and it's immediately clear that it's not a standalone feature. I have to verify its prerequisites, since these settings are critical to performance and debug capabilities. I'm noting that it absolutely depends on either `DYNAMIC_FTRACE_WITH_REGS` or `DYNAMIC_FTRACE_WITH_ARGS`. That makes perfect sense given how direct calls are implemented. Furthermore, `HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS` is also a critical dependency. This, in turn, hinges on `DYNAMIC_FTRACE`. Now, `DYNAMIC_FTRACE` itself is predicated on `FUNCTION_TRACER`. That seems like the root of the tracing feature.

I'd better go double-check `DYNAMIC_FTRACE`'s Kconfig definition over in `kernel/trace/Kconfig` to confirm my understanding of this hierarchy. Time to verify those dependencies.



44/2 2026/10/17 03:22 tool grepper
0m Args:
{
  "Expression": "config DYNAMIC_FTRACE\\b",
  "PathPrefixes": [
    "kernel/trace/Kconfig"
  ]
}

Results:
{
  "Output": "kernel/trace/Kconfig=290=config FUNCTION_TRACE_ARGS\n--\nkernel/trace/Kconfig-300-\nkernel/trace/Kconfig:301:config DYNAMIC_FTRACE\nkernel/trace/Kconfig-302-\tbool\n"
}

45/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41901
							output: 34
							thoughts: 19
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into Kernel Trace Configuration**

Okay, so I'm thinking, let's zero in on `kernel/trace/Kconfig`. Specifically, I'm aiming for around line 301. I suspect that area likely deals with a particular trace option or a cluster of related configurations that I need to understand better. Given my experience, I'm anticipating that line to be a critical point, maybe the definition of a specific tracer, feature flag or some aspect of the tracepoint mechanism. I'm keen to quickly grasp the implications of whatever's defined there, assess its impact, and see how it relates to the wider tracing subsystem within the kernel. Let's see what that snippet holds.



46/2 2026/10/17 03:22 tool read-file
0m Args:
{
  "File": "kernel/trace/Kconfig",
  "FirstLine": 300,
  "LineCount": 20
}

Results:
{
  "Contents": " 300:\t\n 301:\tconfig DYNAMIC_FTRACE\n 302:\t\tbool\n 303:\t\tdepends on FUNCTION_TRACER\n 304:\t\tdepends on HAVE_DYNAMIC_FTRACE\n 305:\t\tdefault y\n 306:\t\thelp\n 307:\t\t  This option will modify all the calls to function tracing\n 308:\t\t  dynamically (will patch them out of the binary image and\n 309:\t\t  replace them with a No-Op instruction) on boot up. During\n 310:\t\t  compile time, a table is made of all the locations that ftrace\n 311:\t\t  can function trace, and this table is linked into the kernel\n 312:\t\t  image. When this is enabled, functions can be individually\n 313:\t\t  enabled, and the functions not enabled will not affect\n 314:\t\t  performance of the system.\n 315:\t\n 316:\t\t  See the files in /sys/kernel/tracing:\n 317:\t\t    available_filter_functions\n 318:\t\t    set_ftrace_filter\n 319:\t\t    set_ftrace_notrace\n"
}

47/2 2026/10/17 03:22 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42427
							output: 308
							thoughts: 789
						

Thoughts:
**My Analysis of BPF Trampoline Configuration for Fuzzing**

Okay, let's break this down for fuzzing. I'm focusing on BPF trampolines, specifically how they are configured and utilized.  My initial observation is that `DYNAMIC_FTRACE` is enabled by default when `FUNCTION_TRACER` is on, and that this cascadingly enables `DYNAMIC_FTRACE_WITH_DIRECT_CALLS`.  Furthermore, `DEBUG_INFO_BTF` is crucial as it enables BTF (BTF is always an interesting target), and we'll certainly need `BPF_SYSCALL` and `BPF_JIT`.

I've confirmed that `BPF_SYSCALL` is essential for the BPF syscall itself, `BPF_JIT` enables the JIT compiler for BPF (compiling `arch/x86/net/bpf_jit_comp.c` and `kernel/bpf/trampoline.c`), `DEBUG_INFO_BTF` provides BTF data needed for attaching trampolines and verifying function prototypes, and finally, `DYNAMIC_FTRACE` enables direct calls and trampolines within `kernel/bpf/trampoline.c`.  I'm making a mental note that since `DYNAMIC_FTRACE_WITH_DIRECT_CALLS` is `def_bool y` when `DYNAMIC_FTRACE` is enabled on x86, we only need to specify `DYNAMIC_FTRACE` – it's more concise.

Looking at the compilation process, `arch/x86/net/Makefile` includes `bpf_jit_comp.o` only if `CONFIG_BPF_JIT` is enabled, and `kernel/bpf/Makefile` includes relevant parts only if `CONFIG_BPF_SYSCALL` or `CONFIG_BPF_JIT` are enabled. This confirms my config assumptions.

I've also identified key symbols that are important to track. I want to include: `arch_prepare_bpf_trampoline`, `bpf_check_attach_target`, and `arch_bpf_trampoline_size`. I am also including `bpf_trampoline_update` as it's the critical function that calls `arch_prepare_bpf_trampoline` when an fentry, fexit, or other tracing programs are attached.  `bpf_check_attach_target` is called from `bpf_tracing_prog_attach` and `check_attach_btf_id`.

Therefore, for the fuzzing setup, here's what I'll specify for the tool call:

*   `WorthFuzzing`:  `true`
*   `EnableConfigs`: `["BPF_SYSCALL", "BPF_JIT", "DEBUG_INFO_BTF", "DYNAMIC_FTRACE"]`
*   `FocusSymbols`: `["arch_prepare_bpf_trampoline", "bpf_check_attach_target", "arch_bpf_trampoline_size", "bpf_trampoline_update"]`
*   `Reasoning`:  BPF trampolines are a crucial part of the kernel's tracing mechanism and security, offering dynamic code injection capabilities. Fuzzing this area is worth it as a vulnerability here could have significant consequences. These configs enable the BPF syscall interface, the JIT, BTF metadata, and dynamic tracing infrastructure. They directly influence trampoline creation, attachment, and verification. The focus symbols were chosen as they are the core functions that handle trampoline preparation, attachment target checking and trampoline update. By fuzzing these we can explore BPF's interaction with the tracing, and system call interfaces, potentially identifying vulnerabilities in trampoline construction, security checks, or code execution flows.



48/2 2026/10/17 03:22 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT",
    "DEBUG_INFO_BTF",
    "DYNAMIC_FTRACE"
  ],
  "FocusSymbols": [
    "arch_prepare_bpf_trampoline",
    "bpf_check_attach_target",
    "arch_bpf_trampoline_size",
    "bpf_trampoline_update"
  ],
  "Reasoning": "The patch modifies the x86-64 BPF JIT trampoline generator (arch/x86/net/bpf_jit_comp.c) and the BPF verifier attach target handling (kernel/bpf/verifier.c). Specifically, it restructures how argument slots are calculated and placed on the stack for tracing trampolines (handling alignment holes, 16-byte aligned arguments like int128/structs, tail call contexts, and \u003e6 arguments), and adds new verification checks (flatten_bpf_func_model and check_unreliable_func_args). These changes touch reachable core BPF logic on amd64 that can be directly exercised via standard bpf() syscalls when attaching tracing/trampoline programs (such as fentry, fexit, and fmod_ret). Fuzzing is warranted to detect potential stack misalignment, off-by-one offsets in generated machine code, or unexpected verifier edge cases.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT",
    "DEBUG_INFO_BTF",
    "DYNAMIC_FTRACE"
  ],
  "FocusSymbols": [
    "arch_prepare_bpf_trampoline",
    "bpf_check_attach_target",
    "arch_bpf_trampoline_size",
    "bpf_trampoline_update"
  ],
  "Reasoning": "The patch modifies the x86-64 BPF JIT trampoline generator (arch/x86/net/bpf_jit_comp.c) and the BPF verifier attach target handling (kernel/bpf/verifier.c). Specifically, it restructures how argument slots are calculated and placed on the stack for tracing trampolines (handling alignment holes, 16-byte aligned arguments like int128/structs, tail call contexts, and \u003e6 arguments), and adds new verification checks (flatten_bpf_func_model and check_unreliable_func_args). These changes touch reachable core BPF logic on amd64 that can be directly exercised via standard bpf() syscalls when attaching tracing/trampoline programs (such as fentry, fexit, and fmod_ret). Fuzzing is warranted to detect potential stack misalignment, off-by-one offsets in generated machine code, or unexpected verifier edge cases.",
  "WorthFuzzing": true
}

49/1 2026/10/17 03:22 action If
2m Args:
{
  "WorthFuzzing": true
}

Results:
null

50/2 2026/10/17 03:22 agent kmsan-evaluator
2m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch modifies the x86-64 BPF JIT compiler and the BPF verifier to correctly handle argument placement (including alignment holes and stack/register splits) for 16-byte aligned arguments (such as __int128 or structs containing them) in BPF trampolines.\n\n1. Analysis of memory initialization:\n- In `kernel/bpf/verifier.c`, `flatten_bpf_func_model()` operates on an already initialized `struct btf_func_model` and explicitly sets `arg_size` and `arg_flags` for all slots. In `check_unreliable_func_args()`, `struct btf_func_model m` is passed to `btf_distill_func_proto()` which initializes its fields upon success; on error, `m` is never read.\n- In `arch/x86/net/bpf_jit_comp.c`, `pos_of_slot` is sized to `MAX_BPF_FUNC_ARG_SLOTS` and populated up to `nr_regs` elements by `bpf_jit_place_args()`. All subsequent reads in `get_nr_stack_slots()`, `save_args()`, and `restore_regs()` operate strictly within the initialized `nr_regs` slots.\n- No uninitialized variables or struct paddings are read or leaked to userspace.\n\n2. KMSAN vs. KASAN applicability:\n- The changes do not expose any uninitialized memory reads, info-leaks to user space, or branching on uninitialized data.\n- KMSAN does not instrument runtime JIT-generated machine instructions.\n- Potential defects in this patch would relate to JIT assembly generation (incorrect stack offsets, improper register save/restore, or invalid RSP manipulation), which are caught by standard testing, functional execution, or KASAN if memory corruption or OOB accesses occur.\n\nConsequently, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit af5698b3e790e13eb13b56eccd3ea7a12961729d
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Oct 17 03:20:58 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 3975a94ac812d..e316387700030 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -2222,6 +2222,16 @@ static const u8 x86_arg_reg[] = {
 	BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5, X86_REG_R9,
 };
 
+static bool x86_arg_on_stack(u8 pos)
+{
+	return pos >= x86_arg_abi.nr_arg_regs;
+}
+
+static s32 x86_arg_stack_off(u8 pos)
+{
+	return (pos - x86_arg_abi.nr_arg_regs) * 8;
+}
+
 /*
  * Move the arguments the x86-64 ABI places somewhere other than the argument
  * slot the BPF calling convention gave them. @stack_base addresses the
@@ -3752,21 +3762,17 @@ static void clean_stack_garbage(const struct btf_func_model *m,
 	}
 }
 
-/* get the count of the regs that are used to pass arguments */
-static int get_nr_used_regs(const struct btf_func_model *m)
+/* the eightbytes taken by the on-stack arguments, alignment holes included */
+static int get_nr_stack_slots(const u8 *pos_of_slot, int nr_slots)
 {
-	int i, arg_regs, nr_used_regs = 0;
+	int slot;
 
-	for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
-		arg_regs = (m->arg_size[i] + 7) / 8;
-		if (nr_used_regs + arg_regs <= 6)
-			nr_used_regs += arg_regs;
-
-		if (nr_used_regs >= 6)
-			break;
+	for (slot = nr_slots - 1; slot >= 0; slot--) {
+		if (x86_arg_on_stack(pos_of_slot[slot]))
+			return x86_arg_stack_off(pos_of_slot[slot]) / 8 + 1;
 	}
 
-	return nr_used_regs;
+	return 0;
 }
 
 /*
@@ -3796,20 +3802,21 @@ static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base
 }
 
 static void save_args(const struct btf_func_model *m, u8 **prog,
-		      int stack_size, bool for_call_origin, u32 flags,
-		      u64 arena_base)
+		      const u8 *pos_of_slot, int stack_size,
+		      bool for_call_origin, u32 flags, u64 arena_base)
 {
-	int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
+	int arg_regs, first_off = 0, nr_stack_slots = 0;
 	bool use_jmp = bpf_trampoline_use_jmp(flags);
 	int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
-	int i, j;
+	int bargs_off = stack_size;
+	int i, j, slot, off, doff;
 
 	/* Store function arguments to stack.
 	 * For a function that accepts two pointers the sequence will be:
 	 * mov QWORD PTR [rbp-0x10],rdi
 	 * mov QWORD PTR [rbp-0x8],rsi
 	 */
-	for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
+	for (i = 0, slot = 0; i < m->nr_args; i++) {
 		bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
 		bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
 
@@ -3831,8 +3838,36 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 		 *
 		 * the arg1-5,arg7 will be passed by regs, and arg6 will
 		 * by stack.
+		 *
+		 * An argument aligned to 16 bytes, an __int128 or a struct
+		 * holding one, also starts at an even stack slot, which can
+		 * leave a hole before it. bpf_jit_place_args() worked out
+		 * where each eightbyte is.
 		 */
-		if (nr_regs + arg_regs > 6) {
+		for (j = 0; j < arg_regs; j++, slot++, bargs_off -= 8) {
+			u8 pos = pos_of_slot[slot];
+			u32 src;
+
+			if (!x86_arg_on_stack(pos)) {
+				/*
+				 * Only copy the arguments on-stack to
+				 * current 'stack_size' and ignore the regs,
+				 * used to prepare the arguments on-stack for
+				 * origin call.
+				 */
+				if (for_call_origin)
+					continue;
+
+				/* copy the arguments from regs into stack */
+				src = x86_arg_reg[pos];
+				if (arena_arg) {
+					emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
+					src = BPF_REG_0;
+				}
+				emit_stx(prog, BPF_DW, BPF_REG_FP, src, -bargs_off);
+				continue;
+			}
+
 			/* copy function arguments from origin stack frame
 			 * into current stack frame.
 			 *
@@ -3842,53 +3877,33 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 			 * entered through the fentry call, so rbp + 24, and
 			 * a single one when it is entered with a jmp or
 			 * called indirectly, so rbp + 16.
+			 *
+			 * The area for the origin call mirrors the incoming
+			 * one, hole and all; only the bpf program takes the
+			 * arguments packed.
 			 */
-			for (j = 0; j < arg_regs; j++) {
-				emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
-					 nr_stack_slots * 8 + stack_args_off);
-				if (arena_arg)
-					emit_arena_arg_conv(prog, BPF_REG_0, nullable,
-							    (u32)arena_base);
-				emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,
-					 -stack_size);
-
-				if (!nr_stack_slots)
-					first_off = stack_size;
-				stack_size -= 8;
-				nr_stack_slots++;
-			}
-		} else {
-			/* Only copy the arguments on-stack to current
-			 * 'stack_size' and ignore the regs, used to
-			 * prepare the arguments on-stack for origin call.
-			 */
-			if (for_call_origin) {
-				nr_regs += arg_regs;
-				continue;
-			}
-
-			/* copy the arguments from regs into stack */
-			for (j = 0; j < arg_regs; j++) {
-				u32 src = nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs;
-
-				if (arena_arg) {
-					emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
-					src = BPF_REG_0;
-				}
-				emit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);
-				stack_size -= 8;
-				nr_regs++;
-			}
+			off = x86_arg_stack_off(pos);
+			doff = for_call_origin ? stack_size - off : bargs_off;
+			emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
+				 stack_args_off + off);
+			if (arena_arg)
+				emit_arena_arg_conv(prog, BPF_REG_0, nullable,
+						    (u32)arena_base);
+			emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -doff);
+
+			if (!nr_stack_slots)
+				first_off = doff;
+			nr_stack_slots++;
 		}
 	}
 
 	clean_stack_garbage(m, prog, nr_stack_slots, first_off);
 }
 
-static void restore_regs(const struct btf_func_model *m, u8 **prog,
+static void restore_regs(u8 **prog, const u8 *pos_of_slot, int nr_slots,
 			 int stack_size)
 {
-	int i, j, arg_regs, nr_regs = 0;
+	int slot;
 
 	/* Restore function arguments from stack.
 	 * For a function that accepts two pointers the sequence will be:
@@ -3897,23 +3912,10 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog,
 	 *
 	 * The logic here is similar to what we do in save_args()
 	 */
-	for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
-		arg_regs = (m->arg_size[i] + 7) / 8;
-		if (nr_regs + arg_regs <= 6) {
-			for (j = 0; j < arg_regs; j++) {
-				emit_ldx(prog, BPF_DW,
-					 nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,
-					 BPF_REG_FP,
-					 -stack_size);
-				stack_size -= 8;
-				nr_regs++;
-			}
-		} else {
-			stack_size -= 8 * arg_regs;
-		}
-
-		if (nr_regs >= 6)
-			break;
+	for (slot = 0; slot < nr_slots; slot++, stack_size -= 8) {
+		if (!x86_arg_on_stack(pos_of_slot[slot]))
+			emit_ldx(prog, BPF_DW, x86_arg_reg[pos_of_slot[slot]],
+				 BPF_REG_FP, -stack_size);
 	}
 }
 
@@ -4102,9 +4104,9 @@ static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,
 	return 0;
 }
 
-/* mov rax, qword ptr [rbp - rounded_stack_depth - 8] */
-#define LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack)	\
-	__LOAD_TCC_PTR(-round_up(stack, 8) - 8)
+/* mov rax, qword ptr [rbp - off] */
+#define LOAD_TRAMP_TAIL_CALL_CNT_PTR(off)	\
+	__LOAD_TCC_PTR(-(off))
 
 /* Example:
  * __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev);
@@ -4172,8 +4174,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 					 struct bpf_tramp_nodes *tnodes,
 					 void *func_addr)
 {
-	int i, ret, nr_regs = m->nr_args, stack_size = 0;
+	int i, ret, nr_regs, stack_size = 0;
+	u8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];
 	int regs_off, func_meta_off, ip_off, run_ctx_off, arg_stack_off, rbx_off;
+	int tcc_off = 0;
 	struct bpf_tramp_nodes *fentry = &tnodes[BPF_TRAMP_FENTRY];
 	struct bpf_tramp_nodes *fexit = &tnodes[BPF_TRAMP_FEXIT];
 	struct bpf_tramp_nodes *fmod_ret = &tnodes[BPF_TRAMP_MODIFY_RETURN];
@@ -4195,12 +4199,12 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 
 	arena_base = bpf_tramp_arena_base(m, tnodes, flags);
 
-	for (i = 0; i < m->nr_args; i++)
-		nr_regs += (m->arg_size[i] + 7) / 8 - 1;
-
-	/* x86-64 supports up to MAX_BPF_FUNC_ARGS arguments. 1-6
-	 * are passed through regs, the remains are through stack.
+	/*
+	 * x86-64 supports up to MAX_BPF_FUNC_ARGS argument slots, six in
+	 * registers and the rest on the stack, where bpf_jit_place_args()
+	 * puts them.
 	 */
+	nr_regs = bpf_jit_place_args(&x86_arg_abi, m, pos_of_slot);
 	if (nr_regs > MAX_BPF_FUNC_ARGS)
 		return -ENOTSUPP;
 
@@ -4212,11 +4216,11 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	 * RBP - 8         [ return value    ]  BPF_TRAMP_F_CALL_ORIG or
 	 *                                      BPF_TRAMP_F_RET_FENTRY_RET flags
 	 *
-	 *                 [ reg_argN        ]  always
-	 *                 [ ...             ]
-	 * RBP - regs_off  [ reg_arg1        ]  program's ctx pointer
+	 *                 [ arg slotN       ]  always, packed, whether
+	 *                 [ ...             ]  in a register or on the
+	 * RBP - regs_off  [ arg slot1       ]  stack; program's ctx pointer
 	 *
-	 * RBP - func_meta_off [ regs count, etc ]  always
+	 * RBP - func_meta_off [ slot count, etc ]  always
 	 *
 	 * RBP - ip_off    [ traced function ]  BPF_TRAMP_F_IP_ARG flag
 	 *
@@ -4224,11 +4228,12 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	 *
 	 * RBP - run_ctx_off [ bpf_tramp_run_ctx ]
 	 *
-	 *                     [ stack_argN ]  BPF_TRAMP_F_CALL_ORIG
-	 *                     [ ...        ]
-	 *                     [ stack_arg2 ]
-	 * RBP - arg_stack_off [ stack_arg1 ]
-	 * RSP                 [ tail_call_cnt_ptr ] BPF_TRAMP_F_TAIL_CALL_CTX
+	 * RBP - tcc_off   [ tail_call_cnt_ptr ]  BPF_TRAMP_F_TAIL_CALL_CTX
+	 *
+	 *                     [ stack slotN ]  BPF_TRAMP_F_CALL_ORIG,
+	 *                     [ ...         ]  the incoming stack
+	 *                     [ stack slot2 ]  arguments, alignment
+	 * RBP - arg_stack_off [ stack slot1 ]  holes included
 	 */
 
 	/* room for return value of orig_call or fentry prog */
@@ -4239,7 +4244,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	stack_size += nr_regs * 8;
 	regs_off = stack_size;
 
-	/* function matedata, such as regs count  */
+	/* function matedata, such as the argument slot count */
 	stack_size += 8;
 	func_meta_off = stack_size;
 
@@ -4259,9 +4264,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	stack_size += (sizeof(struct bpf_tramp_run_ctx) + 7) & ~0x7;
 	run_ctx_off = stack_size;
 
+	if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
+		stack_size += 8;
+		tcc_off = stack_size;
+	}
+
 	if (nr_regs > 6 && (flags & BPF_TRAMP_F_CALL_ORIG)) {
 		/* the space that used to pass arguments on-stack */
-		stack_size += (nr_regs - get_nr_used_regs(m)) * 8;
+		stack_size += get_nr_stack_slots(pos_of_slot, nr_regs) * 8;
 		/* make sure the stack pointer is 16-byte aligned if we
 		 * need pass arguments on stack, which means
 		 *  [stack_size + 8(rbp) + 8(rip) + 8(origin rip)]
@@ -4311,12 +4321,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	/* sub rsp, stack_size */
 	emit_sub_rsp(&prog, stack_size);
 	if (flags & BPF_TRAMP_F_TAIL_CALL_CTX)
-		EMIT1(0x50);		/* push rax */
+		/* mov QWORD PTR [rbp - tcc_off], rax */
+		emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -tcc_off);
 	/* mov QWORD PTR [rbp - rbx_off], rbx */
 	emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_6, -rbx_off);
 
 	func_meta = nr_regs;
-	/* Store number of argument registers of the traced function */
+	/* Store number of argument slots of the traced function */
 	emit_store_stack_imm64(&prog, BPF_REG_0, -func_meta_off, func_meta);
 
 	if (flags & BPF_TRAMP_F_IP_ARG) {
@@ -4324,7 +4335,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 		emit_store_stack_imm64(&prog, BPF_REG_0, -ip_off, (long)func_addr);
 	}
 
-	save_args(m, &prog, regs_off, false, flags, arena_base);
+	save_args(m, &prog, pos_of_slot, regs_off, false, flags, arena_base);
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
 		/* arg1: mov rdi, im */
@@ -4365,14 +4376,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	}
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
-		restore_regs(m, &prog, regs_off);
-		save_args(m, &prog, arg_stack_off, true, flags, 0);
+		restore_regs(&prog, pos_of_slot, nr_regs, regs_off);
+		save_args(m, &prog, pos_of_slot, arg_stack_off, true, flags, 0);
 
 		if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
 			/* Before calling the original function, load the
 			 * tail_call_cnt_ptr from stack to rax.
 			 */
-			LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);
+			LOAD_TRAMP_TAIL_CALL_CNT_PTR(tcc_off);
 		}
 
 		if (flags & BPF_TRAMP_F_ORIG_STACK) {
@@ -4419,7 +4430,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	}
 
 	if (flags & BPF_TRAMP_F_RESTORE_REGS)
-		restore_regs(m, &prog, regs_off);
+		restore_regs(&prog, pos_of_slot, nr_regs, regs_off);
 
 	/* This needs to be done regardless. If there were fmod_ret programs,
 	 * the return value is only updated on the stack and still needs to be
@@ -4436,7 +4447,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 		/* Before running the original function, load the
 		 * tail_call_cnt_ptr from stack to rax.
 		 */
-		LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);
+		LOAD_TRAMP_TAIL_CALL_CNT_PTR(tcc_off);
 	}
 
 	/* restore return value of orig_call or fentry prog back into RAX */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ec136cf377c13..6edddb56451d7 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -22006,6 +22006,59 @@ static bool attach_uses_trampoline_retval(enum bpf_attach_type type)
 	}
 }
 
+/*
+ * BPF code passes argument slot N where the kernel convention passes its Nth
+ * u64 argument, so describe a BPF function to its trampoline as one u64 per
+ * slot, and every arch places its arguments right.
+ */
+static int flatten_bpf_func_model(struct bpf_verifier_log *log, const char *tname,
+				  struct btf_func_model *m)
+{
+	u32 i, nslots = 0;
+
+	for (i = 0; i < m->nr_args; i++)
+		nslots += btf_func_model_arg_slots(m, i);
+	if (nslots > MAX_BPF_FUNC_ARGS) {
+		bpf_log(log, "The function %s has %u argument slots. Too many.\n",
+			tname, nslots);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < nslots; i++) {
+		m->arg_size[i] = sizeof(u64);
+		m->arg_flags[i] = 0;
+	}
+	m->nr_args = nslots;
+	return 0;
+}
+
+/*
+ * The verifier lets a static function use stack arguments counted from its
+ * BTF, even if that BTF turns out unreliable. A trampoline on the function is
+ * then given only MAX_BPF_FUNC_REG_ARGS u64 arguments and would lose the
+ * stack arguments, so such a function cannot be attached to.
+ */
+static int check_unreliable_func_args(struct bpf_verifier_log *log, struct btf *btf,
+				      const struct btf_type *func_proto, const char *tname)
+{
+	struct btf_func_model m;
+	u32 i, nslots = 0;
+
+	if (btf_distill_func_proto(NULL, btf, func_proto, tname, &m)) {
+		/* at least a slot for each argument */
+		nslots = btf_type_vlen(func_proto);
+	} else {
+		for (i = 0; i < m.nr_args; i++)
+			nslots += btf_func_model_arg_slots(&m, i);
+	}
+	if (nslots <= MAX_BPF_FUNC_REG_ARGS)
+		return 0;
+
+	bpf_log(log, "Cannot attach to static function %s: unreliable BTF and stack arguments\n",
+		tname);
+	return -EINVAL;
+}
+
 int bpf_check_attach_target(struct bpf_verifier_log *log,
 			    const struct bpf_prog *prog,
 			    const struct bpf_prog *tgt_prog,
@@ -22273,12 +22326,23 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
 		     prog->aux->saved_dst_attach_type != tgt_prog->expected_attach_type))
 			return -EINVAL;
 
-		if (tgt_prog && conservative)
+		if (tgt_prog && conservative) {
+			if (subprog > 0) {
+				ret = check_unreliable_func_args(log, btf, t, tname);
+				if (ret)
+					return ret;
+			}
 			t = NULL;
+		}
 
 		ret = btf_distill_func_proto(log, btf, t, tname, &tgt_info->fmodel);
 		if (ret < 0)
 			return ret;
+		if (tgt_prog) {
+			ret = flatten_bpf_func_model(log, tname, &tgt_info->fmodel);
+			if (ret < 0)
+				return ret;
+		}
 
 		if (tgt_info->fmodel.ret_size > 8 &&
 		    attach_uses_trampoline_retval(prog->expected_attach_type)) {
diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
index 15b95d0235b52..de358f335d98b 100644
--- a/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
+++ b/tools/testing/selftests/bpf/prog_tests/tracing_struct.c
@@ -5,6 +5,10 @@
 #include "tracing_struct.skel.h"
 #include "tracing_struct_many_args.skel.h"
 #include "tracing_struct_int128.skel.h"
+#include "tracing_struct_int128_tgt.skel.h"
+#include "tracing_struct_int128_tracer.skel.h"
+#include "tracing_stack_arg_tailcall_tgt.skel.h"
+#include "tracing_stack_arg_tailcall_tracer.skel.h"
 
 static void test_struct_args(void)
 {
@@ -117,9 +121,11 @@ static void test_int128_args(void)
 {
 	/*
 	 * __int128 arguments are passed in a register pair on x86_64 and
-	 * arm64, which the trampoline packs into two context slots. Other
-	 * architectures pass a __int128 differently (e.g. s390x passes larger
-	 * arguments by reference), so only exercise this on x86_64 and arm64.
+	 * arm64, which the trampoline packs into two context slots. Past the
+	 * registers, both start an argument aligned to 16 bytes at an even
+	 * stack slot, which the trampoline packs too. Other architectures
+	 * pass a __int128 differently (e.g. s390x passes larger arguments by
+	 * reference), so only exercise this on x86_64 and arm64.
 	 */
 #if defined(__x86_64__) || defined(__aarch64__)
 	struct tracing_struct_int128 *skel;
@@ -139,6 +145,20 @@ static void test_int128_args(void)
 	ASSERT_EQ(skel->bss->t_c, 3, "t:c");
 	ASSERT_EQ(skel->bss->t_ret, 6, "t ret");
 
+	ASSERT_EQ(skel->bss->s_g, 7, "s:g");
+	ASSERT_EQ(skel->bss->s_h_lo, 8, "s:h.lo");
+	ASSERT_EQ(skel->bss->s_h_hi, 9, "s:h.hi");
+	ASSERT_EQ(skel->bss->s_i, 10, "s:i");
+	ASSERT_EQ(skel->bss->s_ret, 385, "s ret");
+
+	ASSERT_EQ(skel->bss->b_f_a, 6, "b:f.a");
+	ASSERT_EQ(skel->bss->b_f_b, 7, "b:f.b");
+	ASSERT_EQ(skel->bss->b_g, 8, "b:g");
+	ASSERT_EQ(skel->bss->b_h, 9, "b:h");
+	ASSERT_EQ(skel->bss->b_i_lo, 10, "b:i.lo");
+	ASSERT_EQ(skel->bss->b_i_hi, 11, "b:i.hi");
+	ASSERT_EQ(skel->bss->b_ret, 506, "b ret");
+
 destroy_skel:
 	tracing_struct_int128__destroy(skel);
 #else
@@ -146,6 +166,191 @@ static void test_int128_args(void)
 #endif
 }
 
+static void test_int128_tgt_prog(void)
+{
+#if defined(__x86_64__) || defined(__aarch64__) || defined(__s390x__)
+	struct tracing_struct_int128_tracer *tracer = NULL;
+	struct tracing_struct_int128_tgt *tgt;
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	int err, fd;
+
+	tgt = tracing_struct_int128_tgt__open_and_load();
+	if (!ASSERT_OK_PTR(tgt, "tgt__open_and_load"))
+		return;
+	if (!tgt->rodata->has_struct_arg) {
+		test__skip();
+		goto out;
+	}
+	fd = bpf_program__fd(tgt->progs.run);
+
+	tracer = tracing_struct_int128_tracer__open();
+	if (!ASSERT_OK_PTR(tracer, "tracer__open"))
+		goto out;
+
+	err = bpf_program__set_attach_target(tracer->progs.odd_entry, fd, "callee_odd");
+	err = err ?: bpf_program__set_attach_target(tracer->progs.odd_exit, fd, "callee_odd");
+	if (tgt->rodata->has_stack_arg) {
+		err = err ?: bpf_program__set_attach_target(tracer->progs.stack_entry, fd,
+							     "callee_stack");
+		err = err ?: bpf_program__set_attach_target(tracer->progs.stack_exit, fd,
+							     "callee_stack");
+		err = err ?: bpf_program__set_attach_target(tracer->progs.split_entry, fd,
+							     "callee_split");
+		err = err ?: bpf_program__set_attach_target(tracer->progs.split_exit, fd,
+							     "callee_split");
+	} else {
+		bpf_program__set_autoload(tracer->progs.stack_entry, false);
+		bpf_program__set_autoload(tracer->progs.stack_exit, false);
+		bpf_program__set_autoload(tracer->progs.split_entry, false);
+		bpf_program__set_autoload(tracer->progs.split_exit, false);
+	}
+	if (!ASSERT_OK(err, "set_attach_target"))
+		goto out;
+
+	err = tracing_struct_int128_tracer__load(tracer);
+	if (!ASSERT_OK(err, "tracer__load"))
+		goto out;
+	err = tracing_struct_int128_tracer__attach(tracer);
+	if (!ASSERT_OK(err, "tracer__attach"))
+		goto out;
+
+	err = bpf_prog_test_run_opts(fd, &topts);
+	if (!ASSERT_OK(err, "test_run") || !ASSERT_EQ(topts.retval, 0, "retval"))
+		goto out;
+
+	ASSERT_EQ(tracer->bss->odd_a, 1, "odd:a");
+	ASSERT_EQ(tracer->bss->odd_lo, 8, "odd:v.lo");
+	ASSERT_EQ(tracer->bss->odd_hi, 9, "odd:v.hi");
+	ASSERT_EQ(tracer->bss->odd_b, 2, "odd:b");
+	ASSERT_EQ(tgt->bss->ret_odd, 52, "odd ret");
+	ASSERT_EQ(tracer->bss->odd_ret, 52, "odd fexit ret");
+
+	if (tgt->rodata->has_stack_arg) {
+		ASSERT_EQ(tracer->bss->stack_g, 7, "stack:g");
+		ASSERT_EQ(tracer->bss->stack_lo, 8, "stack:h.lo");
+		ASSERT_EQ(tracer->bss->stack_hi, 9, "stack:h.hi");
+		ASSERT_EQ(tracer->bss->stack_i, 10, "stack:i");
+		ASSERT_EQ(tgt->bss->ret_stack, 385, "stack ret");
+		ASSERT_EQ(tracer->bss->stack_ret, 385, "stack fexit ret");
+
+		ASSERT_EQ(tracer->bss->split_e, 5, "split:e");
+		ASSERT_EQ(tracer->bss->split_lo, 8, "split:h.lo");
+		ASSERT_EQ(tracer->bss->split_hi, 9, "split:h.hi");
+		ASSERT_EQ(tracer->bss->split_i, 10, "split:i");
+		ASSERT_EQ(tgt->bss->ret_split, 246, "split ret");
+		ASSERT_EQ(tracer->bss->split_ret, 246, "split fexit ret");
+	}
+
+out:
+	tracing_struct_int128_tracer__destroy(tracer);
+	tracing_struct_int128_tgt__destroy(tgt);
+#else
+	test__skip();
+#endif
+}
+
+/*
+ * A static function whose BTF is unreliable gets a trampoline that only knows
+ * of the register arguments, so one taking stack arguments must be refused.
+ */
+static void test_tgt_prog_unreliable(void)
+{
+#if defined(__x86_64__) || defined(__aarch64__)
+	struct tracing_struct_int128_tracer *tracer = NULL;
+	struct tracing_struct_int128_tgt *tgt;
+	char log_buf[4096] = {};
+	struct bpf_program *prog;
+	int err, fd;
+
+	tgt = tracing_struct_int128_tgt__open_and_load();
+	if (!ASSERT_OK_PTR(tgt, "tgt__open_and_load"))
+		return;
+	if (!tgt->rodata->has_stack_arg) {
+		test__skip();
+		goto out;
+	}
+	fd = bpf_program__fd(tgt->progs.run);
+
+	tracer = tracing_struct_int128_tracer__open();
+	if (!ASSERT_OK_PTR(tracer, "tracer__open"))
+		goto out;
+
+	bpf_object__for_each_program(prog, tracer->obj)
+		bpf_program__set_autoload(prog, false);
+	bpf_program__set_autoload(tracer->progs.unreliable_entry, true);
+	bpf_program__set_log_buf(tracer->progs.unreliable_entry, log_buf, sizeof(log_buf));
+	err = bpf_program__set_attach_target(tracer->progs.unreliable_entry, fd,
+					     "callee_unreliable");
+	if (!ASSERT_OK(err, "set_attach_target"))
+		goto out;
+
+	err = tracing_struct_int128_tracer__load(tracer);
+	ASSERT_ERR(err, "tracer__load");
+	ASSERT_HAS_SUBSTR(log_buf, "unreliable BTF and stack arguments", "log");
+
+out:
+	tracing_struct_int128_tracer__destroy(tracer);
+	tracing_struct_int128_tgt__destroy(tgt);
+#else
+	test__skip();
+#endif
+}
+
+/*
+ * A trampoline on a function of a tail_call_reachable program keeps the tail
+ * call counter pointer on its stack, and has to hand the function its stack
+ * arguments past it.
+ */
+static void test_stack_arg_tailcall(void)
+{
+#if defined(__x86_64__) || defined(__aarch64__)
+	struct tracing_stack_arg_tailcall_tracer *tracer = NULL;
+	struct tracing_stack_arg_tailcall_tgt *tgt;
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	int err, fd;
+
+	tgt = tracing_stack_arg_tailcall_tgt__open_and_load();
+	if (!ASSERT_OK_PTR(tgt, "tgt__open_and_load"))
+		return;
+	if (!tgt->rodata->has_stack_arg) {
+		test__skip();
+		goto out;
+	}
+	fd = bpf_program__fd(tgt->progs.run);
+
+	tracer = tracing_stack_arg_tailcall_tracer__open();
+	if (!ASSERT_OK_PTR(tracer, "tracer__open"))
+		goto out;
+
+	err = bpf_program__set_attach_target(tracer->progs.stack_entry, fd, "callee_stack");
+	err = err ?: bpf_program__set_attach_target(tracer->progs.stack_exit, fd, "callee_stack");
+	if (!ASSERT_OK(err, "set_attach_target"))
+		goto out;
+
+	err = tracing_stack_arg_tailcall_tracer__load(tracer);
+	if (!ASSERT_OK(err, "tracer__load"))
+		goto out;
+	err = tracing_stack_arg_tailcall_tracer__attach(tracer);
+	if (!ASSERT_OK(err, "tracer__attach"))
+		goto out;
+
+	err = bpf_prog_test_run_opts(fd, &topts);
+	if (!ASSERT_OK(err, "test_run") || !ASSERT_EQ(topts.retval, 0, "retval"))
+		goto out;
+
+	ASSERT_EQ(tracer->bss->stack_g, 7, "stack:g");
+	ASSERT_EQ(tracer->bss->stack_h, 8, "stack:h");
+	ASSERT_EQ(tgt->bss->ret_stack, 204, "stack ret");
+	ASSERT_EQ(tracer->bss->stack_ret, 204, "stack fexit ret");
+
+out:
+	tracing_stack_arg_tailcall_tracer__destroy(tracer);
+	tracing_stack_arg_tailcall_tgt__destroy(tgt);
+#else
+	test__skip();
+#endif
+}
+
 static void test_union_args(void)
 {
 	struct tracing_struct *skel;
@@ -181,6 +386,12 @@ void test_tracing_struct(void)
 		test_struct_many_args();
 	if (test__start_subtest("int128_args"))
 		test_int128_args();
+	if (test__start_subtest("int128_tgt_prog"))
+		test_int128_tgt_prog();
+	if (test__start_subtest("tgt_prog_unreliable"))
+		test_tgt_prog_unreliable();
+	if (test__start_subtest("stack_arg_tailcall"))
+		test_stack_arg_tailcall();
 	if (test__start_subtest("union_args"))
 		test_union_args();
 }
diff --git a/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c b/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c
index 52d3d5d539465..c87e886423f4f 100644
--- a/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c
+++ b/tools/testing/selftests/bpf/progs/aggregate_arg_kfunc.c
@@ -50,6 +50,27 @@ int aggregate_arg_kfunc_int128(struct __sk_buff *skb)
 	return 0;
 }
 
+/*
+ * arm64 rounds the register number up to an even one for an argument
+ * aligned to 16 bytes, so it wants this __int128 in x2 and x3, leaving x1
+ * empty, and the last argument in x4. The x86-64 ABI has no such rule.
+ */
+SEC("tc")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_arg_kfunc_int128_odd(struct __sk_buff *skb)
+{
+	__u64 a = skb->len ^ MIX_A;
+	__u64 b = skb->len ^ MIX_B;
+	u128 v = ((u128)a << 64) | b;
+
+	if (bpf_kfunc_call_test_i128_arg_odd(1, v, 2) != 1 + 2 * b + 3 * a + 4 * 2)
+		return 1;
+
+	return 0;
+}
+
 #endif /* __SIZEOF_INT128__ */
 
 #if defined(__clang__) && defined(__BPF_FEATURE_STACK_ARGUMENT)
@@ -131,6 +152,56 @@ int aggregate_arg_kfunc_split8(struct __sk_buff *skb)
 
 #ifdef __SIZEOF_INT128__
 
+/*
+ * The hole of aggregate_arg_kfunc_int128_odd, with arguments after it that
+ * arm64 shifts through x5 to x7, where its JIT keeps the BPF stack slots,
+ * and onto its stack. The second __int128 then leaves the second arm64
+ * stack slot empty. x86-64 packs the eightbytes like the BPF convention,
+ * so nothing moves there.
+ */
+SEC("tc")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_arg_kfunc_int128_odd_many(struct __sk_buff *skb)
+{
+	__u64 a = skb->len ^ MIX_A;
+	__u64 b = skb->len ^ MIX_B;
+	u128 v = ((u128)a << 64) | b;
+	u128 w = ((u128)~a << 64) | ~b;
+
+	if (bpf_kfunc_call_test_i128_arg_odd_many(1, v, 2, 3, 4, 5, 6, w) !=
+	    1 + 2 * b + 3 * a + 4 * 2 + 5 * 3 + 6 * 4 + 7 * 5 + 8 * 6 +
+	    9 * ~b + 10 * ~a)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * x86-64 moves s to the stack and gives R9 back to the seventh argument.
+ * w, aligned to 16 bytes by the __int128 it holds, then leaves a stack slot
+ * empty before it: the fourth on x86-64, the second on arm64.
+ */
+SEC("tc")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_arg_kfunc_int128_stack_hole(struct __sk_buff *skb)
+{
+	__u64 a = skb->len ^ MIX_A;
+	__u64 b = skb->len ^ MIX_B;
+	struct prog_test_pair_arg s = { .lo = a, .hi = b };
+	struct prog_test_i128_arg w = { .v = ((u128)~a << 64) | ~b };
+
+	if (bpf_kfunc_call_test_i128_arg_stack_hole(1, 2, 3, 4, 5, s, 6, 7, w) !=
+	    1 + 2 * 2 + 3 * 3 + 4 * 4 + 5 * 5 + 6 * a + 7 * b + 8 * 6 + 9 * 7 +
+	    10 * ~b + 11 * ~a)
+		return 1;
+
+	return 0;
+}
+
 /*
  * Both conventions pad the stack to align this __int128, and the BPF
  * convention pads for neither, so both JITs move it up an eightbyte.
diff --git a/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c
new file mode 100644
index 0000000000000..5fde710c4e370
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tgt.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+	__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+	__uint(max_entries, 1);
+	__uint(key_size, sizeof(__u32));
+	__uint(value_size, sizeof(__u32));
+} jmp_table SEC(".maps");
+
+__u64 base;
+__u64 ret_stack;
+
+#if defined(__clang__) && defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+const volatile bool has_stack_arg = true;
+
+/* g and h are on the stack, for the x86-64 kernel convention too. */
+static __noinline __u64 callee_stack(__u64 a, __u64 b, __u64 c, __u64 d,
+				     __u64 e, __u64 f, __u64 g, __u64 h)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 + h * 8;
+}
+
+/* No stack args of its own, so it can share a program with a tail call. */
+static __noinline __u64 call_stack(__u64 x)
+{
+	return callee_stack(x + 1, x + 2, x + 3, x + 4, x + 5, x + 6, x + 7,
+			    x + 8);
+}
+
+#else
+
+const volatile bool has_stack_arg = false;
+
+#endif
+
+/* Makes the program tail_call_reachable, though callee_stack() is not. */
+static __noinline int subprog_tail(void *ctx)
+{
+	int ret = 0;
+
+	bpf_tail_call_static(ctx, &jmp_table, 0);
+	barrier_var(ret);
+	return ret;
+}
+
+SEC("syscall")
+int run(void *ctx)
+{
+#if defined(__clang__) && defined(__BPF_FEATURE_STACK_ARGUMENT)
+	ret_stack = call_stack(base);
+#endif
+	return subprog_tail(ctx);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c
new file mode 100644
index 0000000000000..1800e7b7afe2d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_stack_arg_tailcall_tracer.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+long stack_g, stack_h, stack_ret;
+
+SEC("fentry/callee_stack")
+int stack_entry(unsigned long long *ctx)
+{
+	stack_g = ctx[6];
+	stack_h = ctx[7];
+	return 0;
+}
+
+SEC("fexit/callee_stack")
+int stack_exit(unsigned long long *ctx)
+{
+	stack_ret = ctx[8];
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
index 4638dfec1f38e..5520ee500c01d 100644
--- a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
@@ -15,4 +15,49 @@ int test_int128_arg_fexit(unsigned long long *ctx)
 	return 0;
 }
 
+/*
+ * The arguments come packed in the BPF convention whatever holes the
+ * kernel convention left between them: an eightbyte each, and two for the
+ * struct holding an __int128.
+ */
+long s_g, s_h_lo, s_h_hi, s_i, s_ret;
+
+SEC("fentry/bpf_testmod_test_int128_stack")
+int test_int128_stack_fentry(unsigned long long *ctx)
+{
+	s_g = ctx[6];
+	s_h_lo = ctx[7];
+	s_h_hi = ctx[8];
+	s_i = ctx[9];
+	return 0;
+}
+
+SEC("fexit/bpf_testmod_test_int128_stack")
+int test_int128_stack_fexit(unsigned long long *ctx)
+{
+	s_ret = ctx[10];
+	return 0;
+}
+
+long b_f_a, b_f_b, b_g, b_h, b_i_lo, b_i_hi, b_ret;
+
+SEC("fentry/bpf_testmod_test_int128_backfill")
+int test_int128_backfill_fentry(unsigned long long *ctx)
+{
+	b_f_a = ctx[5];
+	b_f_b = ctx[6];
+	b_g = ctx[7];
+	b_h = ctx[8];
+	b_i_lo = ctx[9];
+	b_i_hi = ctx[10];
+	return 0;
+}
+
+SEC("fexit/bpf_testmod_test_int128_backfill")
+int test_int128_backfill_fexit(unsigned long long *ctx)
+{
+	b_ret = ctx[11];
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c
new file mode 100644
index 0000000000000..2561422c25dd7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tgt.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct int128_arg {
+	__int128 v;
+};
+
+__u64 base;
+__u64 ret_odd, ret_stack, ret_split, ret_unreliable;
+
+#if defined(__clang__)
+
+const volatile bool has_struct_arg = true;
+
+/* arm64 would want v in x2 and x3, and b in x4. */
+__noinline __u64 callee_odd(__u64 a, struct int128_arg v, __u64 b)
+{
+	return a + (__u64)v.v * 2 + (__u64)((unsigned __int128)v.v >> 64) * 3 +
+	       b * 4;
+}
+
+#else
+
+const volatile bool has_struct_arg = false;
+
+#endif
+
+#if defined(__clang__) && \
+	(defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+	defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+const volatile bool has_stack_arg = true;
+
+/*
+ * x86-64 would want h in its third and fourth stack slots, and arm64 in
+ * its first and second, leaving x7 empty.
+ */
+static __noinline __u64 callee_stack(__u64 a, __u64 b, __u64 c, __u64 d,
+				     __u64 e, __u64 f, __u64 g,
+				     struct int128_arg h, __u64 i)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 +
+	       (__u64)h.v * 8 + (__u64)((unsigned __int128)h.v >> 64) * 9 +
+	       i * 10;
+}
+
+/*
+ * x86-64 BPF code passes h split between r9 and its first stack slot;
+ * arm64 passes it in x5 and x6, so nothing is split there.
+ */
+static __noinline __u64 callee_split(__u64 a, __u64 b, __u64 c, __u64 d,
+				     __u64 e, struct int128_arg h, __u64 i)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + (__u64)h.v * 6 +
+	       (__u64)((unsigned __int128)h.v >> 64) * 7 + i * 8;
+}
+
+/*
+ * A plain pointer makes the BTF of a static function unreliable, but f is
+ * still passed on the stack. The store keeps p a pointer.
+ */
+static __noinline __u64 callee_unreliable(__u64 *p, __u64 b, __u64 c, __u64 d,
+					  __u64 e, __u64 f)
+{
+	*p += 1;
+	return *p + b * 2 + c * 3 + d * 4 + e * 5 + f * 6;
+}
+
+#else
+
+const volatile bool has_stack_arg = false;
+
+#endif
+
+SEC("syscall")
+int run(void *ctx)
+{
+#if defined(__clang__)
+	struct int128_arg v = { .v = ((__int128)(base + 9) << 64) | (base + 8) };
+
+	ret_odd = callee_odd(base + 1, v, base + 2);
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+	defined(__BPF_FEATURE_STACK_ARGUMENT)
+	ret_stack = callee_stack(base + 1, base + 2, base + 3, base + 4,
+				 base + 5, base + 6, base + 7, v, base + 10);
+	ret_split = callee_split(base + 1, base + 2, base + 3, base + 4,
+				 base + 5, v, base + 10);
+	{
+		__u64 x = base;
+
+		ret_unreliable = callee_unreliable(&x, base + 2, base + 3,
+						   base + 4, base + 5, base + 6);
+	}
+#endif
+#endif
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c
new file mode 100644
index 0000000000000..fc81c2eea4a8d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128_tracer.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+struct int128_arg {
+	__int128 v;
+};
+
+long odd_a, odd_lo, odd_hi, odd_b, odd_ret;
+
+SEC("fentry/callee_odd")
+int BPF_PROG2(odd_entry, __u64, a, struct int128_arg, v, __u64, b)
+{
+	odd_a = a;
+	odd_lo = (__u64)v.v;
+	odd_hi = (unsigned __int128)v.v >> 64;
+	odd_b = b;
+	return 0;
+}
+
+SEC("fexit/callee_odd")
+int odd_exit(unsigned long long *ctx)
+{
+	odd_ret = ctx[4];
+	return 0;
+}
+
+long stack_g, stack_lo, stack_hi, stack_i, stack_ret;
+
+SEC("fentry/callee_stack")
+int BPF_PROG2(stack_entry, __u64, a, __u64, b, __u64, c, __u64, d, __u64, e,
+	      __u64, f, __u64, g, struct int128_arg, h, __u64, i)
+{
+	stack_g = g;
+	stack_lo = (__u64)h.v;
+	stack_hi = (unsigned __int128)h.v >> 64;
+	stack_i = i;
+	return 0;
+}
+
+SEC("fexit/callee_stack")
+int stack_exit(unsigned long long *ctx)
+{
+	stack_ret = ctx[10];
+	return 0;
+}
+
+long split_e, split_lo, split_hi, split_i, split_ret;
+
+SEC("fentry/callee_split")
+int BPF_PROG2(split_entry, __u64, a, __u64, b, __u64, c, __u64, d, __u64, e,
+	      struct int128_arg, h, __u64, i)
+{
+	split_e = e;
+	split_lo = (__u64)h.v;
+	split_hi = (unsigned __int128)h.v >> 64;
+	split_i = i;
+	return 0;
+}
+
+SEC("fexit/callee_split")
+int split_exit(unsigned long long *ctx)
+{
+	split_ret = ctx[8];
+	return 0;
+}
+
+/* Refused: the trampoline would lose the stack argument. */
+SEC("?fentry/callee_unreliable")
+int unreliable_entry(unsigned long long *ctx)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index b4f0af2aeca45..38edb4addc5b2 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -197,6 +197,43 @@ bpf_testmod_test_int128_arg(__int128 a, int b, long c)
 	bpf_testmod_test_struct_arg_result = (long)a + b + c;
 	return bpf_testmod_test_struct_arg_result;
 }
+
+struct bpf_testmod_struct_int128 {
+	__int128 a;
+};
+
+/*
+ * x86-64 puts g in the first stack slot and h, aligned to 16 bytes, in
+ * the third and fourth, leaving a hole. arm64 leaves x7 empty and puts h
+ * in the first two stack slots. Either way h and i are not where the BPF
+ * convention packs them, right after g.
+ */
+noinline long
+bpf_testmod_test_int128_stack(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,
+			      u64 g, struct bpf_testmod_struct_int128 h, u64 i)
+{
+	bpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +
+		f * 6 + g * 7 + (u64)h.a * 8 +
+		(u64)((unsigned __int128)h.a >> 64) * 9 + i * 10;
+	return bpf_testmod_test_struct_arg_result;
+}
+
+/*
+ * x86-64 has no register left for f after e, so f goes to the stack and g
+ * takes R9 back; h goes to the third stack slot and i, aligned to 16
+ * bytes, to the fifth and sixth. arm64 has registers up to g, puts h in
+ * the first stack slot and i in the third and fourth.
+ */
+noinline long
+bpf_testmod_test_int128_backfill(u64 a, u64 b, u64 c, u64 d, u64 e,
+				 struct bpf_testmod_struct_arg_2 f, u64 g, u64 h,
+				 struct bpf_testmod_struct_int128 i)
+{
+	bpf_testmod_test_struct_arg_result = a + b * 2 + c * 3 + d * 4 + e * 5 +
+		f.a * 6 + f.b * 7 + g * 8 + h * 9 + (u64)i.a * 10 +
+		(u64)((unsigned __int128)i.a >> 64) * 11;
+	return bpf_testmod_test_struct_arg_result;
+}
 #endif
 
 __weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)
@@ -683,6 +720,12 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
 #ifdef __SIZEOF_INT128__
 	(void)bpf_testmod_test_int128_ret(i);
 	(void)bpf_testmod_test_int128_arg((__int128)1, 2, 3);
+	(void)bpf_testmod_test_int128_stack(1, 2, 3, 4, 5, 6, 7,
+		(struct bpf_testmod_struct_int128){ .a = ((__int128)9 << 64) | 8 },
+		10);
+	(void)bpf_testmod_test_int128_backfill(1, 2, 3, 4, 5,
+		(struct bpf_testmod_struct_arg_2){ .a = 6, .b = 7 }, 8, 9,
+		(struct bpf_testmod_struct_int128){ .a = ((__int128)11 << 64) | 10 });
 #endif
 
 	(void)trace_bpf_testmod_test_raw_tp_null_tp(NULL);
@@ -1047,6 +1090,29 @@ __bpf_kfunc u64 bpf_kfunc_call_test_i128_arg(u64 a, u64 b, __int128 v)
 	return a + b * 2 + (u64)v * 3 + (u64)((unsigned __int128)v >> 64) * 4;
 }
 
+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_odd(u64 a, __int128 v, u64 b)
+{
+	return a + (u64)v * 2 + (u64)((unsigned __int128)v >> 64) * 3 + b * 4;
+}
+
+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_odd_many(u64 a, __int128 v, u64 b, u64 c,
+						      u64 d, u64 e, u64 f, __int128 w)
+{
+	return a + (u64)v * 2 + (u64)((unsigned __int128)v >> 64) * 3 +
+	       b * 4 + c * 5 + d * 6 + e * 7 + f * 8 +
+	       (u64)w * 9 + (u64)((unsigned __int128)w >> 64) * 10;
+}
+
+__bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_stack_hole(u64 a, u64 b, u64 c, u64 d, u64 e,
+							struct prog_test_pair_arg s,
+							u64 f, u64 g,
+							struct prog_test_i128_arg w)
+{
+	return a + b * 2 + c * 3 + d * 4 + e * 5 + s.lo * 6 + s.hi * 7 +
+	       f * 8 + g * 9 + (u64)w.v * 10 +
+	       (u64)((unsigned __int128)w.v >> 64) * 11;
+}
+
 __bpf_kfunc u64 bpf_kfunc_call_test_i128_arg_pad(u64 a, u64 b, u64 c, u64 d, u64 e,
 						 u64 f, u64 g, __int128 v)
 {
@@ -1770,6 +1836,9 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_deep)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ii)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_odd)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_odd_many)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_stack_hole)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128_arg_pad)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg_nofit)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_pair_arg_tail)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index 67c02a421d133..a6a93c9014161 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -66,6 +66,12 @@ struct prog_test_pair_arg {	/* 16 bytes: two argument registers */
 	__u64 hi;
 };
 
+#ifdef __SIZEOF_INT128__
+struct prog_test_i128_arg {	/* 16 bytes, aligned to 16 like its member */
+	__int128 v;
+};
+#endif
+
 struct prog_test_ptr_arg {	/* 16 bytes, but holds a pointer */
 	void *p;
 	__u64 x;
@@ -234,6 +240,13 @@ struct prog_test_ret_ii bpf_kfunc_call_test_ret_ii(int a, int b) __ksym;
 __u64 bpf_kfunc_call_test_pair_arg(__u64 a, struct prog_test_pair_arg s, __u64 b) __ksym;
 #ifdef __SIZEOF_INT128__
 __u64 bpf_kfunc_call_test_i128_arg(__u64 a, __u64 b, __int128 v) __ksym;
+__u64 bpf_kfunc_call_test_i128_arg_odd(__u64 a, __int128 v, __u64 b) __ksym;
+__u64 bpf_kfunc_call_test_i128_arg_odd_many(__u64 a, __int128 v, __u64 b, __u64 c,
+					    __u64 d, __u64 e, __u64 f, __int128 w) __ksym;
+__u64 bpf_kfunc_call_test_i128_arg_stack_hole(__u64 a, __u64 b, __u64 c, __u64 d,
+					      __u64 e, struct prog_test_pair_arg s,
+					      __u64 f, __u64 g,
+					      struct prog_test_i128_arg w) __ksym;
 __u64 bpf_kfunc_call_test_i128_arg_pad(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
 				       __u64 f, __u64 g, __int128 v) __ksym;
 #endif

51/3 2026/10/17 03:22 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21427
							output: 22
							thoughts: 822
						

Thoughts:
**KMSAN Evaluation: A Deep Dive into BPF Argument Placement**

Okay, so the task is to decide whether this patch series warrants a dedicated KMSAN fuzzing session. The prompt lays out a clear rule: KMSAN is only necessary if the code introduces or exposes uninitialized memory risks *that are specifically caught by KMSAN*. Other bugs? KASAN, LOCKDEP, and the like should handle those. High resource costs, so we need to be smart about this.

I see the patch touches `arch/x86/net/bpf_jit_comp.c`, `kernel/bpf/verifier.c`, and some selftests. Seems like BPF-related stuff, which is always interesting. Let's start with `arch/x86/net/bpf_jit_comp.c`.

Right, this is about handling argument placement for the BPF trampoline on x86_64, specifically around alignment of 16-byte arguments like `__int128` or structures containing it. The code's dealing with stack and register interactions.

I'm looking at functions like `x86_arg_on_stack`, `x86_arg_stack_off`, and `get_nr_stack_slots`. These seem to be calculating stack slots based on `pos_of_slot`. The crucial piece is in `save_args` which uses `bpf_jit_place_args` and `__arch_prepare_bpf_trampoline`. Inside `__arch_prepare_bpf_trampoline`, I see `pos_of_slot` is declared as an array of size `MAX_BPF_FUNC_ARG_SLOTS`.

Here's the critical question: Does `bpf_jit_place_args` initialize `pos_of_slot`? That's the heart of it. If not, and it's being used later, we've got a potential uninitialized memory issue. This is *exactly* the kind of situation KMSAN is designed to catch. Now, let me use my preferred tool to search for the definition and usage of `bpf_jit_place_args`. Grepping... right, let's see what's what! This is a good sign. If we do find an uninitialized variable, this is likely KMSAN territory.



52/3 2026/10/17 03:22 tool grepper
0m Args:
{
  "Expression": "bpf_jit_place_args"
}

Results:
{
  "Output": "arch/arm64/net/bpf_jit_comp.c=2631=static int calc_arg_aux(const struct btf_func_model *m,\n--\narch/arm64/net/bpf_jit_comp.c-2635-\narch/arm64/net/bpf_jit_comp.c:2636:\ttotal = bpf_jit_place_args(\u0026arm64_arg_abi, m, a-\u003epos_of_slot);\narch/arm64/net/bpf_jit_comp.c-2637-\tif (total \u003e MAX_BPF_FUNC_ARGS)\n--\narch/x86/net/bpf_jit_comp.c=3804=static void save_args(const struct btf_func_model *m, u8 **prog,\n--\narch/x86/net/bpf_jit_comp.c-3843-\t\t * holding one, also starts at an even stack slot, which can\narch/x86/net/bpf_jit_comp.c:3844:\t\t * leave a hole before it. bpf_jit_place_args() worked out\narch/x86/net/bpf_jit_comp.c-3845-\t\t * where each eightbyte is.\n--\narch/x86/net/bpf_jit_comp.c=4171=static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\n--\narch/x86/net/bpf_jit_comp.c-4203-\t * x86-64 supports up to MAX_BPF_FUNC_ARGS argument slots, six in\narch/x86/net/bpf_jit_comp.c:4204:\t * registers and the rest on the stack, where bpf_jit_place_args()\narch/x86/net/bpf_jit_comp.c-4205-\t * puts them.\narch/x86/net/bpf_jit_comp.c-4206-\t */\narch/x86/net/bpf_jit_comp.c:4207:\tnr_regs = bpf_jit_place_args(\u0026x86_arg_abi, m, pos_of_slot);\narch/x86/net/bpf_jit_comp.c-4208-\tif (nr_regs \u003e MAX_BPF_FUNC_ARGS)\n--\ninclude/linux/filter.h=1273=const struct bpf_jit_arg_abi *bpf_jit_arg_abi(void);\ninclude/linux/filter.h:1274:u32 bpf_jit_place_args(const struct bpf_jit_arg_abi *abi,\ninclude/linux/filter.h-1275-\t\t       const struct btf_func_model *fm, u8 *pos_of_slot);\n--\nkernel/bpf/core.c=3330=const struct bpf_jit_arg_abi * __weak bpf_jit_arg_abi(void)\n--\nkernel/bpf/core.c-3334-\nkernel/bpf/core.c:3335:u32 bpf_jit_place_args(const struct bpf_jit_arg_abi *abi,\nkernel/bpf/core.c-3336-\t\t       const struct btf_func_model *fm, u8 *pos_of_slot)\n--\nkernel/bpf/core.c=3378=u32 bpf_jit_plan_arg_moves(const struct bpf_jit_arg_abi *abi,\n--\nkernel/bpf/core.c-3384-\nkernel/bpf/core.c:3385:\tnslots = bpf_jit_place_args(abi, fm, pos_of_slot);\nkernel/bpf/core.c-3386-\tback = nslots;\n--\nkernel/bpf/verifier.c=13598=static u32 kfunc_abi_slots(const struct btf_func_model *fm)\n--\nkernel/bpf/verifier.c-13609-\nkernel/bpf/verifier.c:13610:\tnslots = bpf_jit_place_args(abi, fm, pos_of_slot);\nkernel/bpf/verifier.c-13611-\tfor (i = 0; i \u003c nslots; i++)\n"
}

53/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23889
							output: 37
							thoughts: 36
						

Thoughts:
Here's my analysis of what I'm thinking:

**Examining the BPF JIT Argument Placement**

Okay, so I want to understand how arguments are handled within the BPF JIT compilation process. Specifically, I'm diving into `kernel/bpf/core.c` and focusing on the `bpf_jit_place_args` function, around line 3335. This suggests that's where the logic for argument placement during JIT compilation resides. I'm likely interested in how the BPF interpreter's arguments, that are passed to the compiled code, are mapped onto registers, memory locations, or stack frames in the generated machine code. This should give me a better understanding of the code generation and how the program accesses its inputs at runtime. I'm expecting to see details on register allocation, argument passing conventions, potential optimization strategies, and perhaps even some handling of argument type considerations. This function is key to the efficiency and correctness of any JIT'ed BPF program.



54/3 2026/10/17 03:23 tool read-file
0m Args:
{
  "File": "kernel/bpf/core.c",
  "FirstLine": 3330,
  "LineCount": 50
}

Results:
{
  "Contents": "3330:\tconst struct bpf_jit_arg_abi * __weak bpf_jit_arg_abi(void)\n3331:\t{\n3332:\t\treturn NULL;\n3333:\t}\n3334:\t\n3335:\tu32 bpf_jit_place_args(const struct bpf_jit_arg_abi *abi,\n3336:\t\t\t       const struct btf_func_model *fm, u8 *pos_of_slot)\n3337:\t{\n3338:\t\tu32 i, k, nslots, slot = 0, nregs_used = 0, stack_off = 0;\n3339:\t\tbool on_stack = false;\n3340:\t\n3341:\t\tfor (i = 0; i \u003c fm-\u003enr_args; i++) {\n3342:\t\t\tbool align16 = fm-\u003earg_flags[i] \u0026 BTF_FMODEL_ALIGN16_ARG;\n3343:\t\t\tu32 pos;\n3344:\t\n3345:\t\t\tnslots = btf_func_model_arg_slots(fm, i);\n3346:\t\n3347:\t\t\tif (align16 \u0026\u0026 abi-\u003eeven_reg_align)\n3348:\t\t\t\tnregs_used = round_up(nregs_used, 2);\n3349:\t\n3350:\t\t\tif (!on_stack \u0026\u0026 nregs_used + nslots \u003c= abi-\u003enr_arg_regs) {\n3351:\t\t\t\t/* wholly in registers */\n3352:\t\t\t\tpos = nregs_used;\n3353:\t\t\t\tnregs_used += nslots;\n3354:\t\t\t} else if (!on_stack \u0026\u0026 abi-\u003esplit_at_boundary) {\n3355:\t\t\t\t/* the last registers hold what fits, the stack the rest */\n3356:\t\t\t\tpos = nregs_used;\n3357:\t\t\t\tstack_off = (nregs_used + nslots - abi-\u003enr_arg_regs) * BPF_REG_SIZE;\n3358:\t\t\t\tnregs_used = abi-\u003enr_arg_regs;\n3359:\t\t\t\ton_stack = true;\n3360:\t\t\t} else {\n3361:\t\t\t\t/* wholly on the stack */\n3362:\t\t\t\tif (align16 \u0026\u0026 abi-\u003eeven_stack_align)\n3363:\t\t\t\t\tstack_off = round_up(stack_off, 2 * BPF_REG_SIZE);\n3364:\t\t\t\tpos = abi-\u003enr_arg_regs + stack_off / BPF_REG_SIZE;\n3365:\t\t\t\tstack_off += nslots * BPF_REG_SIZE;\n3366:\t\t\t\tif (!abi-\u003ebackfill_after_stack)\n3367:\t\t\t\t\ton_stack = true;\n3368:\t\t\t}\n3369:\t\n3370:\t\t\tfor (k = 0; k \u003c nslots; k++)\n3371:\t\t\t\tpos_of_slot[slot + k] = pos + k;\n3372:\t\t\tslot += nslots;\n3373:\t\t}\n3374:\t\n3375:\t\treturn slot;\n3376:\t}\n3377:\t\n3378:\tu32 bpf_jit_plan_arg_moves(const struct bpf_jit_arg_abi *abi,\n3379:\t\t\t\t   const struct btf_func_model *fm,\n"
}

55/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25053
							output: 42
							thoughts: 343
						

Thoughts:
**Analyzing the BPF JIT Argument Placement and Stack Management**

Okay, so I'm looking closely at how the BPF JIT compiler handles argument placement, specifically focusing on the `pos_of_slot` array. From what I can tell, `bpf_jit_place_args` seems to be the key function here. It populates the `pos_of_slot` array for a range of slots, from `0` up to `slot - 1`, and then returns the total number of slots, which is assigned to `nr_regs`.

Diving deeper into `arch/x86/net/bpf_jit_comp.c`, I see `nr_regs = bpf_jit_place_args(&x86_arg_abi, m, pos_of_slot);`. This means `nr_regs` is the result of what `bpf_jit_place_args` calculated. Then, `get_nr_stack_slots(pos_of_slot, nr_regs)` is called, and this function actually iterates *backwards* through `pos_of_slot`, from `slot = nr_slots - 1` down to 0, using the array. So it's very dependent on `pos_of_slot` being populated properly for all these index values, but also the index value `slot` being a positive integer.

Furthermore, looking at `save_args`, the code iterates through a loop where `i` goes from `0` to `m->nr_args` and `j` goes from 0 to `arg_regs`. Within this nested loop, it's accessing `pos_of_slot[slot]` while incrementing `slot`. So, again, it relies on that complete range of indices in the array. Then, in `restore_regs`, another loop, with a `slot` variable going from `0` to `nr_slots`. Again, `pos_of_slot[slot]` is accessed. Therefore, it's imperative that the entire range of `0 <= slot < nr_regs` is correctly initialized in `pos_of_slot`, or else this will not behave as intended.

Now, I'm trying to wrap my head around this. Let's shift our focus to `clean_stack_garbage`. I need to figure out how this function interacts with everything else. I have to see how it's defined and what exactly it does in this context. It could be crucial for maintaining stack integrity after argument passing. I'm going to track down its definition to understand its role in this argument-handling process.



56/3 2026/10/17 03:23 tool grepper
0m Args:
{
  "Expression": "clean_stack_garbage",
  "PathPrefixes": [
    "arch/x86/net/bpf_jit_comp.c"
  ]
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=2377=static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,\n--\narch/x86/net/bpf_jit_comp.c-3716-\narch/x86/net/bpf_jit_comp.c:3717:static void clean_stack_garbage(const struct btf_func_model *m,\narch/x86/net/bpf_jit_comp.c-3718-\t\t\t\tu8 **pprog, int nr_stack_slots,\n--\narch/x86/net/bpf_jit_comp.c=3804=static void save_args(const struct btf_func_model *m, u8 **prog,\n--\narch/x86/net/bpf_jit_comp.c-3899-\narch/x86/net/bpf_jit_comp.c:3900:\tclean_stack_garbage(m, prog, nr_stack_slots, first_off);\narch/x86/net/bpf_jit_comp.c-3901-}\n"
}

57/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26310
							output: 45
							thoughts: 30
						

Thoughts:
Okay, let's dive into this. Here's my understanding, presented as my own mental processing, and tailored for a fellow expert:

**Diving into `clean_stack_garbage` in `arch/x86/net/bpf_jit_comp.c`**

Alright, so the task at hand is to understand how the garbage is cleaned up in the BPF JIT compiler within the x86 architecture, specifically within the network stack. I'm going to pull up that `clean_stack_garbage` function from `arch/x86/net/bpf_jit_comp.c` and start dissecting it. My experience tells me this will likely involve some form of stack unwinding or, more likely, explicit stack adjustment to deal with temporary data and register spills that the JIT compiler likely introduces during its code generation.

The key questions immediately popping into my head are: What constitutes "garbage" in this context? Is it stale register values, temporary stack allocations, or something else entirely? How is it being identified and tracked during the compilation process? And most importantly, what are the performance implications of this cleanup routine? Does it involve costly memory operations, or is it cleverly optimized with register moves or similar techniques?

I’m expecting to see specific instructions targeting the stack pointer (RSP), likely `add` or `sub` instructions, along with perhaps some `mov`s to overwrite values. I'll be looking to see if there's any sophisticated analysis involved to only clean what's truly necessary, or if it's a more brute-force, conservative approach. I'll also be watching for any use of SSE or AVX instructions to optimize data movement. Let's see if we can find some clever tricks. Time to get into it.



58/3 2026/10/17 03:23 tool read-file
0m Args:
{
  "File": "arch/x86/net/bpf_jit_comp.c",
  "FirstLine": 3715,
  "LineCount": 50
}

Results:
{
  "Contents": "3715:\t}\n3716:\t\n3717:\tstatic void clean_stack_garbage(const struct btf_func_model *m,\n3718:\t\t\t\t\tu8 **pprog, int nr_stack_slots,\n3719:\t\t\t\t\tint stack_size)\n3720:\t{\n3721:\t\tint arg_size, off;\n3722:\t\tu8 *prog;\n3723:\t\n3724:\t\t/* Generally speaking, the compiler will pass the arguments\n3725:\t\t * on-stack with \"push\" instruction, which will take 8-byte\n3726:\t\t * on the stack. In this case, there won't be garbage values\n3727:\t\t * while we copy the arguments from origin stack frame to current\n3728:\t\t * in BPF_DW.\n3729:\t\t *\n3730:\t\t * However, sometimes the compiler will only allocate 4-byte on\n3731:\t\t * the stack for the arguments. For now, this case will only\n3732:\t\t * happen if there is only one argument on-stack and its size\n3733:\t\t * not more than 4 byte. In this case, there will be garbage\n3734:\t\t * values on the upper 4-byte where we store the argument on\n3735:\t\t * current stack frame.\n3736:\t\t *\n3737:\t\t * arguments on origin stack:\n3738:\t\t *\n3739:\t\t * stack_arg_1(4-byte) xxx(4-byte)\n3740:\t\t *\n3741:\t\t * what we copy:\n3742:\t\t *\n3743:\t\t * stack_arg_1(8-byte): stack_arg_1(origin) xxx\n3744:\t\t *\n3745:\t\t * and the xxx is the garbage values which we should clean here.\n3746:\t\t */\n3747:\t\tif (nr_stack_slots != 1)\n3748:\t\t\treturn;\n3749:\t\n3750:\t\t/* the size of the last argument */\n3751:\t\targ_size = m-\u003earg_size[m-\u003enr_args - 1];\n3752:\t\tif (arg_size \u003c= 4) {\n3753:\t\t\toff = -(stack_size - 4);\n3754:\t\t\tprog = *pprog;\n3755:\t\t\t/* mov DWORD PTR [rbp + off], 0 */\n3756:\t\t\tif (!is_imm8(off))\n3757:\t\t\t\tEMIT2_off32(0xC7, 0x85, off);\n3758:\t\t\telse\n3759:\t\t\t\tEMIT3(0xC7, 0x45, off);\n3760:\t\t\tEMIT(0, 4);\n3761:\t\t\t*pprog = prog;\n3762:\t\t}\n3763:\t}\n3764:\t\n"
}

59/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27526
							output: 24
							thoughts: 742
						

Thoughts:
**Analyzing the Verifier Change**

Okay, let's break down this verifier change in `kernel/bpf/verifier.c`. I need to understand how it handles function argument validation, especially when dealing with potentially unreliable BTF (Berkeley Trace Format) data. I see the addition of `flatten_bpf_func_model` which seems to ensure the number of argument slots for a function doesn't exceed `MAX_BPF_FUNC_ARGS`. It calculates the total number of slots, checks the limit, and then, importantly, sets the `arg_size` to `sizeof(u64)` and `arg_flags` to 0 for each slot. It also updates `m->nr_args` to be the flattened number of slots. This is a common pattern in BPF; let's see how this ties into the subsequent checks.

Now, `check_unreliable_func_args` is particularly interesting. It deals with situations where the BTF information for a static function might be, for lack of a better term, suspect. The comment mentions a "trampoline" and the limited number of register arguments. The core logic here hinges on `btf_distill_func_proto`. If that function returns an error (non-zero), `nslots` is set to `btf_type_vlen(func_proto)`. So, the number of slots is derived directly from the `func_proto` itself. If `btf_distill_func_proto` succeeds, it uses the `m` struct and calculates slots based on `m.nr_args`, a critical detail. In this success case, it *iterates* through `m.nr_args`, which is where things get interesting, and sums argument slots using `btf_func_model_arg_slots`. This suggests there's some kind of expansion or decomposition of arguments going on within the model.

The key condition here seems to be if the calculated `nslots` is greater than `MAX_BPF_FUNC_REG_ARGS`, which is also a hint. The code then logs an error and returns `-EINVAL`. This check is probably designed to prevent attaching BPF programs to static functions that rely too heavily on stack arguments, especially when the BTF is considered untrustworthy.

I need to dig deeper into how `btf_distill_func_proto` initializes the `m` struct to see how the arguments are handled in the first place. That seems to be the linchpin.



60/3 2026/10/17 03:23 tool grepper
0m Args:
{
  "Expression": "int btf_distill_func_proto"
}

Results:
{
  "Output": "include/linux/bpf.h=3310=bool btf_struct_ids_match(struct bpf_verifier_log *log,\n--\ninclude/linux/bpf.h-3314-\ninclude/linux/bpf.h:3315:int btf_distill_func_proto(struct bpf_verifier_log *log,\ninclude/linux/bpf.h-3316-\t\t\t   struct btf *btf,\n--\nkernel/bpf/btf.c=8227=static u8 __get_arg_fmodel_flags(const struct btf *btf,\n--\nkernel/bpf/btf.c-8245-\nkernel/bpf/btf.c:8246:int btf_distill_func_proto(struct bpf_verifier_log *log,\nkernel/bpf/btf.c-8247-\t\t\t   struct btf *btf,\n"
}

61/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29067
							output: 38
							thoughts: 27
						

Thoughts:
Okay, here's my interpretation of what's going through my mind, presented as a first-person summary, keeping in mind the expert audience.

**Diving into `btf_distill_func_proto`**

Alright, let's get down to brass tacks. I'm going to crack open `btf_distill_func_proto` within the kernel's `bpf/btf.c` source file. I need to refresh my understanding of how function prototypes are being handled during BTF distillation. I'm particularly interested in how they're represented, optimized, and ultimately used within the BPF context. I'll be looking for any potential nuances in how it interacts with different types of function signatures, especially those involving complex data structures or potential ABI considerations. My goal is to quickly spot any areas that might present opportunities for improvement, or perhaps hidden gotchas that could bite us down the road.



62/3 2026/10/17 03:23 tool read-file
0m Args:
{
  "File": "kernel/bpf/btf.c",
  "FirstLine": 8246,
  "LineCount": 40
}

Results:
{
  "Contents": "8246:\tint btf_distill_func_proto(struct bpf_verifier_log *log,\n8247:\t\t\t\t   struct btf *btf,\n8248:\t\t\t\t   const struct btf_type *func,\n8249:\t\t\t\t   const char *tname,\n8250:\t\t\t\t   struct btf_func_model *m)\n8251:\t{\n8252:\t\tconst struct btf_param *args;\n8253:\t\tconst struct btf_type *t;\n8254:\t\tu32 i, nargs;\n8255:\t\tint ret;\n8256:\t\n8257:\t\tif (!func) {\n8258:\t\t\t/* BTF function prototype doesn't match the verifier types.\n8259:\t\t\t * Fall back to MAX_BPF_FUNC_REG_ARGS u64 args.\n8260:\t\t\t */\n8261:\t\t\tfor (i = 0; i \u003c MAX_BPF_FUNC_REG_ARGS; i++) {\n8262:\t\t\t\tm-\u003earg_size[i] = 8;\n8263:\t\t\t\tm-\u003earg_flags[i] = 0;\n8264:\t\t\t}\n8265:\t\t\tm-\u003eret_size = 8;\n8266:\t\t\tm-\u003eret_flags = 0;\n8267:\t\t\tm-\u003enr_args = MAX_BPF_FUNC_REG_ARGS;\n8268:\t\t\treturn 0;\n8269:\t\t}\n8270:\t\targs = (const struct btf_param *)(func + 1);\n8271:\t\tnargs = btf_type_vlen(func);\n8272:\t\tif (nargs \u003e MAX_BPF_FUNC_ARGS) {\n8273:\t\t\tbpf_log(log,\n8274:\t\t\t\t\"The function %s has %d arguments. Too many.\\n\",\n8275:\t\t\t\ttname, nargs);\n8276:\t\t\treturn -EINVAL;\n8277:\t\t}\n8278:\t\tret = __get_type_size(btf, func-\u003etype, \u0026t);\n8279:\t\tif (ret \u003c 0 || ret \u003e 16) {\n8280:\t\t\tbpf_log(log,\n8281:\t\t\t\t\"The function %s return type %s is unsupported.\\n\",\n8282:\t\t\t\ttname, btf_type_str(t));\n8283:\t\t\treturn -EINVAL;\n8284:\t\t}\n8285:\t\tm-\u003eret_size = ret;\n"
}

63/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29991
							output: 38
							thoughts: 17
						

Thoughts:
Okay, here's my interpretation of "Let's read further in `btf_distill_func_proto`", presented as a first-person expert summary:

**Diving Deeper into `btf_distill_func_proto`**

Right, let's get into the weeds. My immediate thought is, okay, we're not stopping at a superficial glance. The mention of `btf_distill_func_proto` signals a deeper dive. I'm assuming we're dealing with a function prototype or possibly even the actual function definition within a BPF (Berkeley Packet Filter) context, given the `btf` prefix. I'm expecting to find details related to:

*   **BTF (BPF Type Format) metadata:** This could be crucial. We are likely aiming to understand how the function's structure and arguments are represented within the BTF information. Is this about distilling the type information, extracting specifics about the parameters, return type, or perhaps resolving any complex typedefs?
*   **Function Signature and Semantics:** We’ll need to scrutinize the function signature. Are there any unusual calling conventions or restrictions? What are the implications of the argument types and return type within the BPF context? Are there potential performance considerations tied to these types?
*   **Purpose of the Distillation:** Why are we distilling this function proto? Is it for code generation, static analysis, debugging, or optimization? Understanding the end goal will greatly inform how I approach this. Are we trying to generate a C struct, convert from a struct, or another goal?

In short, this isn’t just about *reading* the prototype, it's about *understanding* its implications within the larger BPF ecosystem. Let's see what we can find.



64/3 2026/10/17 03:23 tool read-file
0m Args:
{
  "File": "kernel/bpf/btf.c",
  "FirstLine": 8285,
  "LineCount": 40
}

Results:
{
  "Contents": "8285:\t\tm-\u003eret_size = ret;\n8286:\t\tm-\u003eret_flags = __get_type_fmodel_flags(t);\n8287:\t\n8288:\t\tfor (i = 0; i \u003c nargs; i++) {\n8289:\t\t\tif (i == nargs - 1 \u0026\u0026 args[i].type == 0) {\n8290:\t\t\t\tbpf_log(log,\n8291:\t\t\t\t\t\"The function %s with variable args is unsupported.\\n\",\n8292:\t\t\t\t\ttname);\n8293:\t\t\t\treturn -EINVAL;\n8294:\t\t\t}\n8295:\t\t\tret = __get_type_size(btf, args[i].type, \u0026t);\n8296:\t\n8297:\t\t\t/* No support of struct argument size greater than 16 bytes */\n8298:\t\t\tif (ret \u003c 0 || ret \u003e 16) {\n8299:\t\t\t\tbpf_log(log,\n8300:\t\t\t\t\t\"The function %s arg%d type %s is unsupported.\\n\",\n8301:\t\t\t\t\ttname, i, btf_type_str(t));\n8302:\t\t\t\treturn -EINVAL;\n8303:\t\t\t}\n8304:\t\t\tif (ret == 0) {\n8305:\t\t\t\tbpf_log(log,\n8306:\t\t\t\t\t\"The function %s has malformed void argument.\\n\",\n8307:\t\t\t\t\ttname);\n8308:\t\t\t\treturn -EINVAL;\n8309:\t\t\t}\n8310:\t\t\tm-\u003earg_size[i] = ret;\n8311:\t\t\tm-\u003earg_flags[i] = __get_arg_fmodel_flags(btf, \u0026args[i], t);\n8312:\t\t}\n8313:\t\tm-\u003enr_args = nargs;\n8314:\t\treturn 0;\n8315:\t}\n8316:\t\n8317:\t/* Compare BTFs of two functions assuming only scalars and pointers to context.\n8318:\t * t1 points to BTF_KIND_FUNC in btf1\n8319:\t * t2 points to BTF_KIND_FUNC in btf2\n8320:\t * Returns:\n8321:\t * EINVAL - function prototype mismatch\n8322:\t * EFAULT - verifier bug\n8323:\t * 0 - 99% match. The last 1% is validated by the verifier.\n8324:\t */\n"
}

65/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31074
							output: 38
							thoughts: 255
						

Thoughts:
**My Analysis of Function Argument Initialization in BTF and BPF Verification**

Okay, so if `btf_distill_func_proto` succeeds – that is, returns 0 – then `m->nr_args` gets populated, and all the corresponding entries within `m->arg_size` and `m->arg_flags` are initialized. This seems straightforward enough; the arguments are set up when the distillation is successful. But, if `btf_distill_func_proto` *fails* (returns non-zero), then `m` is never read at all, which is important to consider. In that case, we are not guaranteed that `m` contains valid data.

Now, it seems we must consider the flow where `btf_distill_func_proto` fails, and how argument slot count `nslots` gets calculated in that scenario. From looking at the code, in the success path, the argument slot count `nslots` gets calculated directly based on the length of `func_proto` within BTF (using `btf_type_vlen`). In the failure path, it looks like a different mechanism is used where `btf_func_model_arg_slots` will be used to determine the necessary `nslots`. This must be the part I need to pay the most attention to.

Next, I need to check how `bpf_check_attach_target` in `kernel/bpf/verifier.c` handles this, around lines 22320-22360 to see if I can find an explanation of how and why these calculations must work.



66/3 2026/10/17 03:23 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 22315,
  "LineCount": 55
}

Results:
{
  "Contents": "22315:\t\t\t\treturn -EINVAL;\n22316:\t\t\t}\n22317:\t\t\tif (prog_extension \u0026\u0026\n22318:\t\t\t    btf_check_type_match(log, prog, btf, t))\n22319:\t\t\t\treturn -EINVAL;\n22320:\t\t\tt = btf_attach_func_proto(log, btf, btf_id);\n22321:\t\t\tif (!t || !btf_type_is_func_proto(t))\n22322:\t\t\t\treturn -EINVAL;\n22323:\t\n22324:\t\t\tif ((prog-\u003eaux-\u003esaved_dst_prog_type || prog-\u003eaux-\u003esaved_dst_attach_type) \u0026\u0026\n22325:\t\t\t    (!tgt_prog || prog-\u003eaux-\u003esaved_dst_prog_type != tgt_prog-\u003etype ||\n22326:\t\t\t     prog-\u003eaux-\u003esaved_dst_attach_type != tgt_prog-\u003eexpected_attach_type))\n22327:\t\t\t\treturn -EINVAL;\n22328:\t\n22329:\t\t\tif (tgt_prog \u0026\u0026 conservative) {\n22330:\t\t\t\tif (subprog \u003e 0) {\n22331:\t\t\t\t\tret = check_unreliable_func_args(log, btf, t, tname);\n22332:\t\t\t\t\tif (ret)\n22333:\t\t\t\t\t\treturn ret;\n22334:\t\t\t\t}\n22335:\t\t\t\tt = NULL;\n22336:\t\t\t}\n22337:\t\n22338:\t\t\tret = btf_distill_func_proto(log, btf, t, tname, \u0026tgt_info-\u003efmodel);\n22339:\t\t\tif (ret \u003c 0)\n22340:\t\t\t\treturn ret;\n22341:\t\t\tif (tgt_prog) {\n22342:\t\t\t\tret = flatten_bpf_func_model(log, tname, \u0026tgt_info-\u003efmodel);\n22343:\t\t\t\tif (ret \u003c 0)\n22344:\t\t\t\t\treturn ret;\n22345:\t\t\t}\n22346:\t\n22347:\t\t\tif (tgt_info-\u003efmodel.ret_size \u003e 8 \u0026\u0026\n22348:\t\t\t    attach_uses_trampoline_retval(prog-\u003eexpected_attach_type)) {\n22349:\t\t\t\tbpf_log(log,\n22350:\t\t\t\t\t\"Attach to function %s with a \u003e8 byte return value is not supported for this attach type\\n\",\n22351:\t\t\t\t\ttname);\n22352:\t\t\t\treturn -EOPNOTSUPP;\n22353:\t\t\t}\n22354:\t\n22355:\t\t\t/*\n22356:\t\t\t * *.multi programs don't need an address during program\n22357:\t\t\t * verification, we just take the module ref if needed.\n22358:\t\t\t */\n22359:\t\t\tif (is_tracing_multi_id(prog, btf_id)) {\n22360:\t\t\t\tif (btf_is_module(btf)) {\n22361:\t\t\t\t\tmod = btf_try_get_module(btf);\n22362:\t\t\t\t\tif (!mod)\n22363:\t\t\t\t\t\treturn -ENOENT;\n22364:\t\t\t\t}\n22365:\t\t\t\taddr = 0;\n22366:\t\t\t} else if (tgt_prog) {\n22367:\t\t\t\tif (subprog == 0)\n22368:\t\t\t\t\taddr = (long) tgt_prog-\u003ebpf_func;\n22369:\t\t\t\telse\n"
}

67/3 2026/10/17 03:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32654
							output: 25
							thoughts: 992
						

Thoughts:
**Analyzing BPF Function Argument Handling in x86-64 Architecture**

Okay, let's break this down. First, I need to understand what `flatten_bpf_func_model` does. It seems to take a `btf_func_model` structure as input and normalizes the argument handling. It iterates through the arguments, calculates the total number of "slots" used by each, and enforces a `MAX_BPF_FUNC_ARGS` limit. If the limit is exceeded, it logs an error. Then it initializes `arg_size` and `arg_flags` for each of those slots and sets `nr_args` to the calculated number of slots. Looks like a kind of function argument normalization and sanity check, basically ensuring that we have a standard representation of the function's arguments.

Next, I'm diving into `arch/x86/net/bpf_jit_comp.c`, specifically the changes related to argument passing. I'm focusing on `save_args` and `__arch_prepare_bpf_trampoline`. In the `__arch_prepare_bpf_trampoline` function, I see the argument handling logic. It seems to have changed and introduced a new variable `pos_of_slot`. I see the `nr_regs` calculation and it is related to function arguments. Ah, there is now `bpf_jit_place_args` being called. It looks like the code is trying to place function arguments, up to `MAX_BPF_FUNC_ARGS`, and some of them may be passed via registers, the rest via the stack.

Wait a second. I just realized there's a potential mismatch here: `MAX_BPF_FUNC_ARG_SLOTS` versus `MAX_BPF_FUNC_ARGS`. It seems like the code is now using slots, but what's the difference? I need to check the definitions of these macros to ensure that the argument count is handled correctly, and that this code change is safe.



68/3 2026/10/17 03:23 tool grepper
0m Args:
{
  "Expression": "MAX_BPF_FUNC_ARG_SLOTS"
}

Results:
{
  "Output": "arch/arm64/net/bpf_jit_comp.c=2609=struct arg_aux {\n--\narch/arm64/net/bpf_jit_comp.c-2627-\t */\narch/arm64/net/bpf_jit_comp.c:2628:\tu8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];\narch/arm64/net/bpf_jit_comp.c-2629-};\n--\narch/x86/net/bpf_jit_comp.c=4171=static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\n--\narch/x86/net/bpf_jit_comp.c-4177-\tint i, ret, nr_regs, stack_size = 0;\narch/x86/net/bpf_jit_comp.c:4178:\tu8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];\narch/x86/net/bpf_jit_comp.c-4179-\tint regs_off, func_meta_off, ip_off, run_ctx_off, arg_stack_off, rbx_off;\n--\ninclude/linux/bpf.h=1005=static_assert(__BPF_RET_TYPE_MAX \u003c= BPF_BASE_TYPE_LIMIT);\n--\ninclude/linux/bpf.h-1026- */\ninclude/linux/bpf.h:1027:#define MAX_BPF_FUNC_ARG_SLOTS (2 * MAX_BPF_FUNC_ARGS)\ninclude/linux/bpf.h-1028-\n--\ninclude/linux/filter.h=1274=u32 bpf_jit_place_args(const struct bpf_jit_arg_abi *abi,\n--\ninclude/linux/filter.h-1280-/* Every argument slot moves at most once, and the scratch goes out and back. */\ninclude/linux/filter.h:1281:#define BPF_JIT_MAX_ARG_MOVES\t(MAX_BPF_FUNC_ARG_SLOTS + 2)\ninclude/linux/filter.h-1282-\n--\nkernel/bpf/core.c=3378=u32 bpf_jit_plan_arg_moves(const struct bpf_jit_arg_abi *abi,\n--\nkernel/bpf/core.c-3381-{\nkernel/bpf/core.c:3382:\tu8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];\nkernel/bpf/core.c-3383-\tu32 nslots, n = 0, s, back;\n--\nkernel/bpf/verifier.c=13598=static u32 kfunc_abi_slots(const struct btf_func_model *fm)\n--\nkernel/bpf/verifier.c-13600-\tconst struct bpf_jit_arg_abi *abi = bpf_jit_arg_abi();\nkernel/bpf/verifier.c:13601:\tu8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];\nkernel/bpf/verifier.c-13602-\tu32 i, nslots, slots = 0;\n"
}

69/3 2026/10/17 03:24 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34952
							output: 24
							thoughts: 734
						

Thoughts:
**Analyzing Argument Placement and Stack Allocation in BPF JIT Compilation**

Okay, let's break this down. First, I'm noting that `MAX_BPF_FUNC_ARG_SLOTS` is defined as twice `MAX_BPF_FUNC_ARGS`. And I see that `nr_regs = bpf_jit_place_args()` is called, and that if `nr_regs` exceeds `MAX_BPF_FUNC_ARGS`, the function returns `-ENOTSUPP`. This means `nr_regs` is guaranteed to be less than or equal to `MAX_BPF_FUNC_ARGS`, which is itself less than `MAX_BPF_FUNC_ARG_SLOTS`. Crucially, `bpf_jit_place_args` has populated `pos_of_slot` with `nr_regs` entries.

Now, let's move on to `get_nr_stack_slots`. I can see from the loop that only initialized elements `pos_of_slot[slot]` are accessed where `0 <= slot < nr_slots`. This is good; the index is safe because `bpf_jit_place_args` has already populated these slots.

Alright, now let's focus on `save_args`. I'm starting to understand how arguments are stored on the stack. I see that there are different offsets depending on `use_jmp` and `flags`. The sequence of operations involves saving the function arguments onto the stack using `mov` instructions.

Hmm, this `arg_regs` calculation is interesting: `arg_regs = (m->arg_size[i] + 7) / 8`. This represents the number of registers (or parts of registers) needed to hold the argument. I need to make sure this is consistent with what `btf_func_model_arg_slots(fm, i)` returns. I should go and check if this calculation is accurate by calling `btf_func_model_arg_slots`. That is where the key understanding lies.



70/3 2026/10/17 03:24 tool grepper
0m Args:
{
  "Expression": "btf_func_model_arg_slots"
}

Results:
{
  "Output": "include/linux/bpf.h=1259=struct btf_func_model {\n--\ninclude/linux/bpf.h-1266-\ninclude/linux/bpf.h:1267:static inline u32 btf_func_model_arg_slots(const struct btf_func_model *m, u32 arg)\ninclude/linux/bpf.h-1268-{\n--\nkernel/bpf/core.c=3335=u32 bpf_jit_place_args(const struct bpf_jit_arg_abi *abi,\n--\nkernel/bpf/core.c-3344-\nkernel/bpf/core.c:3345:\t\tnslots = btf_func_model_arg_slots(fm, i);\nkernel/bpf/core.c-3346-\n--\nkernel/bpf/verifier.c=9619=static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 prev_slot,\n--\nkernel/bpf/verifier.c-10198- * The slots a parameter takes, from its BTF type. This has to agree with\nkernel/bpf/verifier.c:10199: * btf_func_model_arg_slots(), which answers the same from the size the\nkernel/bpf/verifier.c-10200- * func model recorded, or the verifier would check an argument at a slot\n--\nkernel/bpf/verifier.c=13598=static u32 kfunc_abi_slots(const struct btf_func_model *fm)\n--\nkernel/bpf/verifier.c-13604-\tfor (i = 0; i \u003c fm-\u003enr_args; i++)\nkernel/bpf/verifier.c:13605:\t\tslots += btf_func_model_arg_slots(fm, i);\nkernel/bpf/verifier.c-13606-\n--\nkernel/bpf/verifier.c=13880=static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-13898-\tfor (i = 0, slots_used = 0; i \u003c nargs; i++) {\nkernel/bpf/verifier.c:13899:\t\tu32 nslots = btf_func_model_arg_slots(fm, i);\nkernel/bpf/verifier.c-13900-\n--\nkernel/bpf/verifier.c-13923-\tfor (i = 0, slots_used = 0; i \u003c nargs; i++) {\nkernel/bpf/verifier.c:13924:\t\tu32 nslots = btf_func_model_arg_slots(fm, i);\nkernel/bpf/verifier.c-13925-\n--\nkernel/bpf/verifier.c=22014=static int flatten_bpf_func_model(struct bpf_verifier_log *log, const char *tname,\n--\nkernel/bpf/verifier.c-22019-\tfor (i = 0; i \u003c m-\u003enr_args; i++)\nkernel/bpf/verifier.c:22020:\t\tnslots += btf_func_model_arg_slots(m, i);\nkernel/bpf/verifier.c-22021-\tif (nslots \u003e MAX_BPF_FUNC_ARGS) {\n--\nkernel/bpf/verifier.c=22041=static int check_unreliable_func_args(struct bpf_verifier_log *log, struct btf *btf,\n--\nkernel/bpf/verifier.c-22051-\t\tfor (i = 0; i \u003c m.nr_args; i++)\nkernel/bpf/verifier.c:22052:\t\t\tnslots += btf_func_model_arg_slots(\u0026m, i);\nkernel/bpf/verifier.c-22053-\t}\n"
}

71/3 2026/10/17 03:24 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37169
							output: 37
							thoughts: 29
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into btf_func_model_arg_slots**

Alright, let's take a quick look at `include/linux/bpf.h` and specifically, that `btf_func_model_arg_slots` function. I'm curious what it's returning. I want to understand how the BPF infrastructure is modeling function arguments and what kind of information we can glean from it. Given my understanding of BPF internals, I suspect we're talking about the number of arguments, and possibly something to do with the representation of those arguments in the BTF (BPF Type Format) metadata, but I'll confirm. I'm particularly interested in how this interacts with things like variadic functions, and how the argument slots are handled for different function calling conventions. Time to dive into the code...



72/3 2026/10/17 03:24 tool read-file
0m Args:
{
  "File": "include/linux/bpf.h",
  "FirstLine": 1265,
  "LineCount": 10
}

Results:
{
  "Contents": "1265:\t};\n1266:\t\n1267:\tstatic inline u32 btf_func_model_arg_slots(const struct btf_func_model *m, u32 arg)\n1268:\t{\n1269:\t\treturn (m-\u003earg_size[arg] + sizeof(u64) - 1) / sizeof(u64);\n1270:\t}\n1271:\t\n1272:\t/* Restore arguments before returning from trampoline to let original function\n1273:\t * continue executing. This flag is used for fentry progs when there are no\n1274:\t * fexit progs.\n"
}

73/3 2026/10/17 03:24 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37608
							output: 45
							thoughts: 110
						

Thoughts:
Okay, here's my breakdown of this thought process:

**Decoding Argument Placement in BPF JIT**

Yes! That's it, precisely. The calculation `(m->arg_size[arg] + 7) / 8` provides the correct number of 8-byte slots needed for an argument. It aligns perfectly with how `bpf_jit_place_args` handles incrementing `slot` by `arg_regs` for each argument `i`, mirroring the `slot += nslots` behavior. The integer division neatly handles rounding up the size of the slot.

Now, I'm at the point where I want to understand the *why* behind this. What's the purpose of this patch? What is it actually changing? It's time to dig into the context. Let's see if I can find more information by reviewing the git log and looking for the commit message related to this code. I need to understand the history to fully grasp the impact and reasoning behind this change. Can I find any relevant commit messages or examine the most recent commits? That should give me a good handle on things.



74/3 2026/10/17 03:24 tool grepper
0m Args:
{
  "Expression": "bpf_jit_place_args",
  "PathPrefixes": [
    "arch/x86/net/bpf_jit_comp.c"
  ]
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=3804=static void save_args(const struct btf_func_model *m, u8 **prog,\n--\narch/x86/net/bpf_jit_comp.c-3843-\t\t * holding one, also starts at an even stack slot, which can\narch/x86/net/bpf_jit_comp.c:3844:\t\t * leave a hole before it. bpf_jit_place_args() worked out\narch/x86/net/bpf_jit_comp.c-3845-\t\t * where each eightbyte is.\n--\narch/x86/net/bpf_jit_comp.c=4171=static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\n--\narch/x86/net/bpf_jit_comp.c-4203-\t * x86-64 supports up to MAX_BPF_FUNC_ARGS argument slots, six in\narch/x86/net/bpf_jit_comp.c:4204:\t * registers and the rest on the stack, where bpf_jit_place_args()\narch/x86/net/bpf_jit_comp.c-4205-\t * puts them.\narch/x86/net/bpf_jit_comp.c-4206-\t */\narch/x86/net/bpf_jit_comp.c:4207:\tnr_regs = bpf_jit_place_args(\u0026x86_arg_abi, m, pos_of_slot);\narch/x86/net/bpf_jit_comp.c-4208-\tif (nr_regs \u003e MAX_BPF_FUNC_ARGS)\n"
}

75/3 2026/10/17 03:24 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38463
							output: 21
							thoughts: 18
						

Thoughts:
Okay, let's dive into this. Here's my understanding, presented as a personal thought process, given my expertise:

**Examining the `x86_arg_abi` Definition**

Alright, time to get my hands dirty. I need to understand this `x86_arg_abi` definition. I know this is likely related to the calling conventions for x86 architectures, and, since I'm an expert in this field, I'm already starting to think about register usage, stack frame layout, and the overall mechanics of argument passing. I immediately start mentally parsing what might be encapsulated in this term – is it a macro, a structure, an enum, something else entirely? What level of abstraction are we dealing with here? Is this part of a specific compiler's implementation, or a more general system definition? I'm hoping it's well-documented! The context is key here; the *meaning* of this name is vital. I'll need to know the specific target architecture too – is it 32-bit or 64-bit? System V ABI, Windows x64 calling convention, or something else? I bet this has everything to do with how function arguments are passed – whether they're passed through registers, pushed onto the stack, or a combination of both. Right, let's see what's what. Let's start digging to clarify all these questions.



76/3 2026/10/17 03:24 tool grepper
0m Args:
{
  "Expression": "x86_arg_abi"
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=2173=static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip,\n--\narch/x86/net/bpf_jit_comp.c-2214-\narch/x86/net/bpf_jit_comp.c:2215:static const struct bpf_jit_arg_abi x86_arg_abi = {\narch/x86/net/bpf_jit_comp.c-2216-\t.nr_arg_regs\t\t= 6,\n--\narch/x86/net/bpf_jit_comp.c=2225=static bool x86_arg_on_stack(u8 pos)\narch/x86/net/bpf_jit_comp.c-2226-{\narch/x86/net/bpf_jit_comp.c:2227:\treturn pos \u003e= x86_arg_abi.nr_arg_regs;\narch/x86/net/bpf_jit_comp.c-2228-}\n--\narch/x86/net/bpf_jit_comp.c=2230=static s32 x86_arg_stack_off(u8 pos)\narch/x86/net/bpf_jit_comp.c-2231-{\narch/x86/net/bpf_jit_comp.c:2232:\treturn (pos - x86_arg_abi.nr_arg_regs) * 8;\narch/x86/net/bpf_jit_comp.c-2233-}\n--\narch/x86/net/bpf_jit_comp.c=2240=static int emit_kfunc_arg_moves(const struct btf_func_model *fm, s32 stack_base, u8 **pprog)\n--\narch/x86/net/bpf_jit_comp.c-2242-\tstruct bpf_jit_arg_move moves[BPF_JIT_MAX_ARG_MOVES];\narch/x86/net/bpf_jit_comp.c:2243:\tconst u8 nreg = x86_arg_abi.nr_arg_regs;\narch/x86/net/bpf_jit_comp.c-2244-\tu8 *prog = *pprog, *start = prog;\n--\narch/x86/net/bpf_jit_comp.c-2246-\narch/x86/net/bpf_jit_comp.c:2247:\tn = bpf_jit_plan_arg_moves(\u0026x86_arg_abi, fm, moves);\narch/x86/net/bpf_jit_comp.c-2248-\n--\narch/x86/net/bpf_jit_comp.c=4171=static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\n--\narch/x86/net/bpf_jit_comp.c-4206-\t */\narch/x86/net/bpf_jit_comp.c:4207:\tnr_regs = bpf_jit_place_args(\u0026x86_arg_abi, m, pos_of_slot);\narch/x86/net/bpf_jit_comp.c-4208-\tif (nr_regs \u003e MAX_BPF_FUNC_ARGS)\n--\narch/x86/net/bpf_jit_comp.c=4879=const struct bpf_jit_arg_abi *bpf_jit_arg_abi(void)\narch/x86/net/bpf_jit_comp.c-4880-{\narch/x86/net/bpf_jit_comp.c:4881:\treturn \u0026x86_arg_abi;\narch/x86/net/bpf_jit_comp.c-4882-}\n"
}

77/3 2026/10/17 03:24 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 39815
							output: 45
							thoughts: 35
						

Thoughts:
Okay, let's dive into this. Here's my thought process laid out:

**Examining the x86_arg_abi in bpf_jit_comp.c**

Alright, I need to get a handle on how arguments are handled in the BPF JIT compilation for x86. The best place to start is likely looking directly at the argument ABI definition. Let's dig into `arch/x86/net/bpf_jit_comp.c`. I'm specifically looking for where `x86_arg_abi` is defined or used within that file. I expect to find details concerning register usage, stack frame layout (if any), and possibly calling conventions. This will give me the foundational rules for how BPF programs are translated into native x86 instructions with respect to argument passing. This should be a good starting point for understanding the low-level mechanics.



78/3 2026/10/17 03:24 tool read-file
0m Args:
{
  "File": "arch/x86/net/bpf_jit_comp.c",
  "FirstLine": 2210,
  "LineCount": 25
}

Results:
{
  "Contents": "2210:\t\t}\n2211:\t\t*pprog = prog;\n2212:\t\treturn 0;\n2213:\t}\n2214:\t\n2215:\tstatic const struct bpf_jit_arg_abi x86_arg_abi = {\n2216:\t\t.nr_arg_regs\t\t= 6,\n2217:\t\t.backfill_after_stack\t= true,\n2218:\t\t.even_stack_align\t= true,\n2219:\t};\n2220:\t\n2221:\tstatic const u8 x86_arg_reg[] = {\n2222:\t\tBPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5, X86_REG_R9,\n2223:\t};\n2224:\t\n2225:\tstatic bool x86_arg_on_stack(u8 pos)\n2226:\t{\n2227:\t\treturn pos \u003e= x86_arg_abi.nr_arg_regs;\n2228:\t}\n2229:\t\n2230:\tstatic s32 x86_arg_stack_off(u8 pos)\n2231:\t{\n2232:\t\treturn (pos - x86_arg_abi.nr_arg_regs) * 8;\n2233:\t}\n2234:\t\n"
}

79/3 2026/10/17 03:24 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 40475
							output: 45
							thoughts: 126
						

Thoughts:
**Investigating the ARM64 BPF JIT Implementation**

Okay, let's dive into what's happening with the `arm64` architecture and its BPF JIT. I need to understand the implementation of `bpf_jit_place_args`, so my first instinct is to check the commit history. I'd typically use `git log` to see the last few commits affecting `arch/arm64/net/bpf_jit_comp.c`. Shoot, I don't have the `git log` tool readily available here. No problem, I can still use the available tools. Since I can't directly use `git log`, I'll perform a search within the ARM64 files to locate instances of `bpf_jit_place_args`. From there, I'll go directly to the file to investigate the code around line 2630 in `arch/arm64/net/bpf_jit_comp.c`. That should give me the context I need.



80/3 2026/10/17 03:24 tool read-file
0m Args:
{
  "File": "arch/arm64/net/bpf_jit_comp.c",
  "FirstLine": 2620,
  "LineCount": 40
}

Results:
{
  "Contents": "2620:\t\t/* home much stack is used for additional args passed to the\n2621:\t\t * original function when called from trampoline (this one needs\n2622:\t\t * arguments to be properly aligned)\n2623:\t\t */\n2624:\t\tint ostack_for_args;\n2625:\t\t/* where AAPCS64 puts each argument slot: an argument register below\n2626:\t\t * the eighth, an on-stack argument slot from it up\n2627:\t\t */\n2628:\t\tu8 pos_of_slot[MAX_BPF_FUNC_ARG_SLOTS];\n2629:\t};\n2630:\t\n2631:\tstatic int calc_arg_aux(const struct btf_func_model *m,\n2632:\t\t\t\t struct arg_aux *a)\n2633:\t{\n2634:\t\tint slots, i, slot, total;\n2635:\t\n2636:\t\ttotal = bpf_jit_place_args(\u0026arm64_arg_abi, m, a-\u003epos_of_slot);\n2637:\t\tif (total \u003e MAX_BPF_FUNC_ARGS)\n2638:\t\t\treturn -ENOTSUPP;\n2639:\t\n2640:\t\t/* verifier ensures m-\u003enr_args \u003c= MAX_BPF_FUNC_ARGS */\n2641:\t\tfor (i = 0, slot = 0; i \u003c m-\u003enr_args; i++) {\n2642:\t\t\tslots = (m-\u003earg_size[i] + 7) / 8;\n2643:\t\t\tif (a64_arg_on_stack(a-\u003epos_of_slot[slot])) /* passed through register ? */\n2644:\t\t\t\tbreak;\n2645:\t\t\tslot += slots;\n2646:\t\t}\n2647:\t\n2648:\t\ta-\u003eargs_in_regs = i;\n2649:\t\ta-\u003eregs_for_args = slot;\n2650:\t\ta-\u003eostack_for_args = 0;\n2651:\t\ta-\u003ebstack_for_args = 0;\n2652:\t\n2653:\t\t/* the rest arguments are passed through stack */\n2654:\t\tfor (; i \u003c m-\u003enr_args; i++)\n2655:\t\t\ta-\u003ebstack_for_args += ((m-\u003earg_size[i] + 7) / 8) * 8;\n2656:\t\n2657:\t\t/* the outgoing area reaches the last slot, over any alignment hole */\n2658:\t\tif (a-\u003ebstack_for_args)\n2659:\t\t\ta-\u003eostack_for_args = a64_arg_stack_off(a-\u003epos_of_slot[total - 1]) + 8;\n"
}

81/3 2026/10/17 03:24 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41583
							output: 449
							thoughts: 3383
						

Thoughts:
**Analysis of BPF JIT Trampoline and KMSAN Needs**

Okay, let's break this down. The core of this patch seems to be about improving the x86-64 BPF JIT compiler's trampoline to correctly handle arguments that require 16-byte alignment, like `__int128` or structs with such members, and updating the verifier to cope with static functions with potentially unreliable BTF (BTF are used to describe function signatures, etc.). I need to make sure this doesn't introduce any uninitialized memory reads or information leaks that KMSAN would uniquely catch.

My first thought is always to consider if any kernel data is exposed to user space, but this patch doesn't do anything like that. The modifications are internal to the BPF JIT compiler and verifier, so it's all about how arguments are handled on the stack and within the BPF context when a program calls into another function.

I see that the code now carefully calculates stack offsets and alignment for these potentially complex arguments. The `pos_of_slot` array and stack offsets used in the trampoline code are all initialized and used within bounds. The verifier code checks that all stack slots are initialized, and any function models are carefully initialized using `btf_distill_func_proto` before being read. If the initialization fails the data is never used.

I'm checking every KMSAN condition and I don't see anything. I've considered that the BPF context in which arguments are saved for fentry/fexit programs is correctly initialized. The code uses `clean_stack_garbage`, but I made sure it is not doing anything that would introduce an uninitialized read.

The main risk here is in the JIT compilation itself: are the right registers assigned? Are the stack offsets correct? I think I'm reasonably confident in this code because it's been through review, and if there are bugs like those, they'll show up as crashes, out-of-bounds reads/writes, or the BPF program producing the wrong results. I suspect that KASAN or standard fault handlers would catch any memory corruption issues arising from incorrect code generation or trampoline stack management, and unit tests will likely catch correctness problems.

The key thing is that KMSAN doesn't instrument JIT-generated machine code. My understanding is that KMSAN focuses on C code, not the raw assembly that the JIT compiler produces at runtime. So, any issue in that assembly won't be caught by KMSAN, but I do not believe this patch has introduced any paths in the C code where uninitialized memory can leak.

Given all this, I'm confident that this patch doesn't introduce any new risks of uninitialized memory usage or information leaks, and any other bugs would be detected by standard bug detection techniques.

Therefore, `NeedsKMSAN = false`.



82/3 2026/10/17 03:25 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch modifies the x86-64 BPF JIT compiler and the BPF verifier to correctly handle argument placement (including alignment holes and stack/register splits) for 16-byte aligned arguments (such as __int128 or structs containing them) in BPF trampolines.\n\n1. Analysis of memory initialization:\n- In `kernel/bpf/verifier.c`, `flatten_bpf_func_model()` operates on an already initialized `struct btf_func_model` and explicitly sets `arg_size` and `arg_flags` for all slots. In `check_unreliable_func_args()`, `struct btf_func_model m` is passed to `btf_distill_func_proto()` which initializes its fields upon success; on error, `m` is never read.\n- In `arch/x86/net/bpf_jit_comp.c`, `pos_of_slot` is sized to `MAX_BPF_FUNC_ARG_SLOTS` and populated up to `nr_regs` elements by `bpf_jit_place_args()`. All subsequent reads in `get_nr_stack_slots()`, `save_args()`, and `restore_regs()` operate strictly within the initialized `nr_regs` slots.\n- No uninitialized variables or struct paddings are read or leaked to userspace.\n\n2. KMSAN vs. KASAN applicability:\n- The changes do not expose any uninitialized memory reads, info-leaks to user space, or branching on uninitialized data.\n- KMSAN does not instrument runtime JIT-generated machine instructions.\n- Potential defects in this patch would relate to JIT assembly generation (incorrect stack offsets, improper register save/restore, or invalid RSP manipulation), which are caught by standard testing, functional execution, or KASAN if memory corruption or OOB accesses occur.\n\nConsequently, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch modifies the x86-64 BPF JIT compiler and the BPF verifier to correctly handle argument placement (including alignment holes and stack/register splits) for 16-byte aligned arguments (such as __int128 or structs containing them) in BPF trampolines.\n\n1. Analysis of memory initialization:\n- In `kernel/bpf/verifier.c`, `flatten_bpf_func_model()` operates on an already initialized `struct btf_func_model` and explicitly sets `arg_size` and `arg_flags` for all slots. In `check_unreliable_func_args()`, `struct btf_func_model m` is passed to `btf_distill_func_proto()` which initializes its fields upon success; on error, `m` is never read.\n- In `arch/x86/net/bpf_jit_comp.c`, `pos_of_slot` is sized to `MAX_BPF_FUNC_ARG_SLOTS` and populated up to `nr_regs` elements by `bpf_jit_place_args()`. All subsequent reads in `get_nr_stack_slots()`, `save_args()`, and `restore_regs()` operate strictly within the initialized `nr_regs` slots.\n- No uninitialized variables or struct paddings are read or leaked to userspace.\n\n2. KMSAN vs. KASAN applicability:\n- The changes do not expose any uninitialized memory reads, info-leaks to user space, or branching on uninitialized data.\n- KMSAN does not instrument runtime JIT-generated machine instructions.\n- Potential defects in this patch would relate to JIT assembly generation (incorrect stack offsets, improper register save/restore, or invalid RSP manipulation), which are caught by standard testing, functional execution, or KASAN if memory corruption or OOB accesses occur.\n\nConsequently, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)