| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/03 16:13 | flow | patch-triage |
3mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_collect_arena_args",
"btf_distill_func_proto",
"btf_ctx_access"
],
"Reasoning": "The patch introduces `__arena` and `__arena_nullable` annotations for BPF kfuncs and struct_ops arguments. It modifies the BPF verifier, JIT compiler (x86), and trampoline logic to support arena pointers, including rebasing them to kernel addresses. This is a functional change to the core BPF subsystem and should be fuzzed to ensure memory safety and correct verification of arena pointers.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/03 16:13 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 00afc6746da0ec902beb66367c45701fd0658b10\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 3 16:13:07 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst\nindex cbde86d082cce..48cf746f9ff9d 100644\n--- a/Documentation/bpf/kfuncs.rst\n+++ b/Documentation/bpf/kfuncs.rst\n@@ -278,6 +278,43 @@ An example is given below::\n ...\n }\n \n+2.3.8 __arena and __arena_nullable Annotations\n+----------------------------------------------\n+\n+Both annotations indicate that the pointer argument points into the\n+calling program's arena. The JIT rebases the value at the call site so\n+the kfunc receives a directly dereferenceable kernel address, subject to\n+the access rules described in :ref:`BPF_kfunc_arena_access` (at most\n+``GUARD_SZ / 2``, 32 KiB, past the pointer in a single unchecked access).\n+\n+With ``__arena`` the rebase is unconditional and the argument is never\n+NULL: a value whose lower 32 bits are zero arrives as the arena base\n+address (arena offset 0). The kfunc must not check the argument for NULL.\n+With ``__arena_nullable`` such a value arrives as NULL instead and the\n+kfunc must check before dereferencing.\n+\n+An example is given below::\n+\n+ __bpf_kfunc int bpf_process_item(struct item *item__arena)\n+ {\n+ ...\n+ }\n+\n+Calling such a kfunc requires the program to use an arena map and a JIT with\n+arena argument support (currently x86-64); verification fails otherwise. The\n+program can pass any value without compromising the kernel. A value that does\n+not point into the arena is a program bug.\n+\n+The suffixes have the same meaning on the arguments of struct_ops stub\n+functions, with the conversion running in the opposite direction. The\n+kernel caller passes the kernel arena address and the trampoline converts\n+it while saving the arguments, so the callback receives an arena pointer\n+it can dereference directly. With ``__arena`` the kernel caller must not\n+pass NULL. With ``__arena_nullable`` a NULL kernel pointer arrives as NULL.\n+However, there is no obligation to prove to the verifier that such a pointer is\n+non-NULL before use, in-line with existing semantics of arena pointers used in\n+a program (or obtained from any other source).\n+\n .. _BPF_kfunc_nodef:\n \n 2.4 Using an existing kernel function\n@@ -515,6 +552,8 @@ In order to accommodate such requirements, the verifier will enforce strict\n PTR_TO_BTF_ID type matching if two types have the exact same name, with one\n being suffixed with ``___init``.\n \n+.. _BPF_kfunc_arena_access:\n+\n 2.8 Accessing arena memory through kfunc arguments\n --------------------------------------------------\n \ndiff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c\nindex 01e7ce569c1ed..4b3e5d8a4a563 100644\n--- a/arch/x86/net/bpf_jit_comp.c\n+++ b/arch/x86/net/bpf_jit_comp.c\n@@ -1678,6 +1678,50 @@ static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip,\n \treturn 0;\n }\n \n+/*\n+ * Rebase the __arena args of a kfunc call to arena kernel addresses,\n+ * rN = kern_vm_start + (u32)rN, with R12 holding kern_vm_start. A nullable\n+ * arg preserves NULL by skipping the add, tested on the truncated value as\n+ * arena NULL is offset 0. Return the number of emitted bytes.\n+ */\n+static int emit_kfunc_arena_args(struct bpf_prog *bpf_prog,\n+\t\t\t\t const struct bpf_insn *insn, u8 **pprog)\n+{\n+\tconst struct btf_func_model *fm;\n+\tu8 *prog = *pprog;\n+\tu8 *start = prog;\n+\tint i;\n+\n+\tfm = bpf_jit_find_kfunc_model(bpf_prog, insn);\n+\tif (!fm)\n+\t\treturn -EINVAL;\n+\n+\tfor (i = 0; i \u003c min_t(int, fm-\u003enr_args, MAX_BPF_FUNC_REG_ARGS); i++) {\n+\t\tu8 flags = fm-\u003earg_flags[i];\n+\t\tu32 reg = BPF_REG_1 + i;\n+\n+\t\tif (!(flags \u0026 BTF_FMODEL_ARENA_ARG))\n+\t\t\tcontinue;\n+\t\tif (WARN_ON_ONCE(!bpf_prog-\u003eaux-\u003earena))\n+\t\t\treturn -EINVAL;\n+\n+\t\t/* mov eN, eN: truncate and clear the upper 32 bits */\n+\t\temit_mov_reg(\u0026prog, false, reg, reg);\n+\t\tif (flags \u0026 BTF_FMODEL_NULLABLE_ARG) {\n+\t\t\t/* test eN, eN; jz over the 3-byte add */\n+\t\t\tmaybe_emit_mod(\u0026prog, reg, reg, false);\n+\t\t\tEMIT2(0x85, add_2reg(0xC0, reg, reg));\n+\t\t\tEMIT2(X86_JE, 3);\n+\t\t}\n+\t\t/* add rN, r12 */\n+\t\tmaybe_emit_mod(\u0026prog, reg, X86_REG_R12, true);\n+\t\tEMIT2(0x01, add_2reg(0xC0, reg, X86_REG_R12));\n+\t}\n+\n+\t*pprog = prog;\n+\treturn prog - start;\n+}\n+\n static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,\n \t\t u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)\n {\n@@ -2583,6 +2627,12 @@ st:\t\t\tinsn_off = insn-\u003eoff;\n \t\t\t}\n \t\t\tif (!imm32)\n \t\t\t\treturn -EINVAL;\n+\t\t\tif (src_reg == BPF_PSEUDO_KFUNC_CALL) {\n+\t\t\t\terr = emit_kfunc_arena_args(bpf_prog, insn, \u0026prog);\n+\t\t\t\tif (err \u003c 0)\n+\t\t\t\t\treturn err;\n+\t\t\t\tip += err;\n+\t\t\t}\n \t\t\tif (priv_frame_ptr) {\n \t\t\t\tpush_r9(\u0026prog);\n \t\t\t\tip += 2;\n@@ -2993,12 +3043,40 @@ static int get_nr_used_regs(const struct btf_func_model *m)\n \treturn nr_used_regs;\n }\n \n+/*\n+ * Convert an arena kernel address into the arena pointer form on its way\n+ * into the BPF ctx, rax = (u32)(src - kern_vm_start). A nullable arg\n+ * preserves NULL, tested on the full 64-bit kernel pointer. The 32-bit\n+ * subtraction both truncates and clears the upper half, so the stored\n+ * value satisfies the JIT invariant for arena pointer registers.\n+ */\n+static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base_lo)\n+{\n+\tu8 *prog = *pprog;\n+\n+\tif (nullable) {\n+\t\tif (src_reg != BPF_REG_0)\n+\t\t\temit_mov_reg(\u0026prog, true, BPF_REG_0, src_reg);\n+\t\t/* test rax, rax; jz over the 5-byte sub */\n+\t\tEMIT3(0x48, 0x85, 0xC0);\n+\t\tEMIT2(X86_JE, 5);\n+\t} else if (src_reg != BPF_REG_0) {\n+\t\temit_mov_reg(\u0026prog, false, BPF_REG_0, src_reg);\n+\t}\n+\t/* sub eax, base_lo */\n+\tEMIT1_off32(0x2D, base_lo);\n+\n+\t*pprog = prog;\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 int stack_size, bool for_call_origin, u32 flags,\n+\t\t const struct bpf_tramp_arena_args *aargs)\n {\n \tint arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;\n \tbool use_jmp = bpf_trampoline_use_jmp(flags);\n-\tint i, j;\n+\tint stack_args_off = (use_jmp || (flags \u0026 BPF_TRAMP_F_INDIRECT)) ? 16 : 24;\n+\tint i, j, slot = 0;\n \n \t/* Store function arguments to stack.\n \t * For a function that accepts two pointers the sequence will be:\n@@ -3029,16 +3107,20 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\n \t\t\t/* copy function arguments from origin stack frame\n \t\t\t * into current stack frame.\n \t\t\t *\n-\t\t\t * The starting address of the arguments on-stack\n-\t\t\t * is:\n-\t\t\t * rbp + 8(push rbp) +\n-\t\t\t * 8(return addr of origin call) +\n-\t\t\t * 8(return addr of the caller)\n-\t\t\t * which means: rbp + 24\n+\t\t\t * The arguments on-stack start above the saved rbp\n+\t\t\t * and the return addresses: two return addresses\n+\t\t\t * (origin call and caller) when the trampoline is\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\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 + 16 + (!use_jmp) * 8);\n+\t\t\t\t\t nr_stack_slots * 8 + stack_args_off);\n+\t\t\t\tif (aargs \u0026\u0026 (aargs-\u003eslots \u0026 BIT(slot)))\n+\t\t\t\t\temit_arena_arg_conv(prog, BPF_REG_0,\n+\t\t\t\t\t\t\t aargs-\u003enullable_slots \u0026 BIT(slot),\n+\t\t\t\t\t\t\t (u32)aargs-\u003ekern_vm_start);\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@@ -3046,6 +3128,7 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\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\tslot++;\n \t\t\t}\n \t\t} else {\n \t\t\t/* Only copy the arguments on-stack to current\n@@ -3054,16 +3137,24 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\n \t\t\t */\n \t\t\tif (for_call_origin) {\n \t\t\t\tnr_regs += arg_regs;\n+\t\t\t\tslot += 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\temit_stx(prog, BPF_DW, BPF_REG_FP,\n-\t\t\t\t\t nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,\n-\t\t\t\t\t -stack_size);\n+\t\t\t\tu32 src = nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs;\n+\n+\t\t\t\tif (aargs \u0026\u0026 (aargs-\u003eslots \u0026 BIT(slot))) {\n+\t\t\t\t\temit_arena_arg_conv(prog, src,\n+\t\t\t\t\t\t\t aargs-\u003enullable_slots \u0026 BIT(slot),\n+\t\t\t\t\t\t\t (u32)aargs-\u003ekern_vm_start);\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\tslot++;\n \t\t\t}\n \t\t}\n \t}\n@@ -3354,11 +3445,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\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+\tstruct bpf_tramp_arena_args aargs;\n \tvoid *orig_call = func_addr;\n \tint cookie_off, cookie_cnt;\n \tu8 **branches = NULL;\n \tu64 func_meta;\n \tu8 *prog;\n+\tbool has_aargs;\n \tbool save_ret;\n \n \t/*\n@@ -3369,6 +3462,8 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \tWARN_ON_ONCE((flags \u0026 BPF_TRAMP_F_INDIRECT) \u0026\u0026\n \t\t (flags \u0026 ~(BPF_TRAMP_F_INDIRECT | BPF_TRAMP_F_RET_FENTRY_RET)));\n \n+\thas_aargs = bpf_tramp_collect_arena_args(tnodes, flags, \u0026aargs);\n+\n \tfor (i = 0; i \u003c m-\u003enr_args; i++)\n \t\tnr_regs += (m-\u003earg_size[i] + 7) / 8 - 1;\n \n@@ -3503,7 +3598,8 @@ 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);\n+\tsave_args(m, \u0026prog, regs_off, false, flags,\n+\t\t has_aargs ? \u0026aargs : NULL);\n \n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n \t\t/* arg1: mov rdi, im */\n@@ -3545,7 +3641,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\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);\n+\t\tsave_args(m, \u0026prog, arg_stack_off, true, flags, NULL);\n \n \t\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX) {\n \t\t\t/* Before calling the original function, load the\n@@ -4046,6 +4142,11 @@ bool bpf_jit_supports_stack_args(void)\n \treturn true;\n }\n \n+bool bpf_jit_supports_arena_args(void)\n+{\n+\treturn true;\n+}\n+\n void *bpf_arch_text_copy(void *dst, void *src, size_t len)\n {\n \tif (text_poke_copy(dst, src, len) == NULL)\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex 356884587ae11..2f606c3256806 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -1213,6 +1213,12 @@ struct bpf_prog_offload {\n /* The argument is signed. */\n #define BTF_FMODEL_SIGNED_ARG\t\tBIT(1)\n \n+/* The argument is an arena pointer. */\n+#define BTF_FMODEL_ARENA_ARG\t\tBIT(2)\n+\n+/* The argument is nullable. */\n+#define BTF_FMODEL_NULLABLE_ARG\t\tBIT(3)\n+\n struct btf_func_model {\n \tu8 ret_size;\n \tu8 ret_flags;\n@@ -1286,6 +1292,20 @@ struct bpf_tramp_nodes {\n \tint nr_nodes;\n };\n \n+/*\n+ * Which 8-byte ctx slots of a struct_ops trampoline hold arena kernel\n+ * pointers that save_args() converts to the arena pointer form,\n+ * ctx[slot] = (u32)(kaddr - kern_vm_start).\n+ */\n+struct bpf_tramp_arena_args {\n+\tu32 slots;\n+\tu32 nullable_slots;\t/* subset of @slots where NULL is preserved */\n+\tu64 kern_vm_start;\n+};\n+\n+bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,\n+\t\t\t\t struct bpf_tramp_arena_args *aargs);\n+\n struct bpf_tramp_run_ctx;\n \n /* Different use cases for BPF trampoline:\n@@ -1687,6 +1707,11 @@ struct bpf_ctx_arg_aux {\n \tu32 btf_id;\n \tu32 ref_id;\n \tbool refcounted;\n+\t/*\n+\t * We don't encode NULL-ness in the type for the program, but still need\n+\t * to distinguish it for the purposes of telling JITs what sequence to emit.\n+\t */\n+\tbool arena_nullable;\n };\n \n struct btf_mod_pair {\ndiff --git a/include/linux/filter.h b/include/linux/filter.h\nindex 32d5297c557e5..36ce3403fe599 100644\n--- a/include/linux/filter.h\n+++ b/include/linux/filter.h\n@@ -1183,6 +1183,7 @@ bool bpf_jit_supports_subprog_tailcalls(void);\n bool bpf_jit_supports_percpu_insn(void);\n bool bpf_jit_supports_kfunc_call(void);\n bool bpf_jit_supports_stack_args(void);\n+bool bpf_jit_supports_arena_args(void);\n bool bpf_jit_supports_far_kfunc_call(void);\n bool bpf_jit_supports_exceptions(void);\n bool bpf_jit_supports_ptr_xchg(void);\ndiff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c\nindex 4e7a48c02be5c..827a6216a6201 100644\n--- a/kernel/bpf/bpf_struct_ops.c\n+++ b/kernel/bpf/bpf_struct_ops.c\n@@ -147,6 +147,8 @@ void bpf_struct_ops_image_free(void *image)\n \n #define MAYBE_NULL_SUFFIX \"__nullable\"\n #define REFCOUNTED_SUFFIX \"__ref\"\n+#define ARENA_SUFFIX \"__arena\"\n+#define ARENA_MAYBE_NULL_SUFFIX \"__arena_nullable\"\n \n /* Prepare argument info for every nullable argument of a member of a\n * struct_ops type.\n@@ -159,7 +161,7 @@ void bpf_struct_ops_image_free(void *image)\n * to provide an array of struct bpf_ctx_arg_aux, which in turn provides\n * the information that used by the verifier to check the arguments of the\n * BPF struct_ops program assigned to the member. Here, we only care about\n- * the arguments that are marked as __nullable.\n+ * the arguments that are marked as __nullable, __ref or __arena.\n *\n * The array of struct bpf_ctx_arg_aux is eventually assigned to\n * prog-\u003eaux-\u003ectx_arg_info of BPF struct_ops programs and passed to the\n@@ -175,7 +177,8 @@ static int prepare_arg_info(struct btf *btf,\n \t\t\t struct bpf_struct_ops_arg_info *arg_info)\n {\n \tconst struct btf_type *stub_func_proto, *pointed_type;\n-\tbool is_nullable = false, is_refcounted = false;\n+\tbool is_nullable = false, is_refcounted = false, is_arena = false;\n+\tbool is_arena_nullable = false;\n \tconst struct btf_param *stub_args, *args;\n \tstruct bpf_ctx_arg_aux *info, *info_buf;\n \tu32 nargs, arg_no, info_cnt = 0;\n@@ -226,26 +229,30 @@ static int prepare_arg_info(struct btf *btf,\n \tinfo = info_buf;\n \tfor (arg_no = 0; arg_no \u003c nargs; arg_no++) {\n \t\t/* Skip arguments that is not suffixed with\n-\t\t * \"__nullable or __ref\".\n+\t\t * \"__nullable\", \"__ref\", \"__arena\" or \"__arena_nullable\".\n \t\t */\n \t\tis_nullable = btf_param_match_suffix(btf, \u0026stub_args[arg_no],\n \t\t\t\t\t\t MAYBE_NULL_SUFFIX);\n \t\tis_refcounted = btf_param_match_suffix(btf, \u0026stub_args[arg_no],\n \t\t\t\t\t\t REFCOUNTED_SUFFIX);\n+\t\tis_arena_nullable = btf_param_match_suffix(btf, \u0026stub_args[arg_no],\n+\t\t\t\t\t\t\t ARENA_MAYBE_NULL_SUFFIX);\n+\t\tis_arena = btf_param_match_suffix(btf, \u0026stub_args[arg_no], ARENA_SUFFIX);\n \n \t\tif (is_nullable)\n \t\t\tsuffix = MAYBE_NULL_SUFFIX;\n \t\telse if (is_refcounted)\n \t\t\tsuffix = REFCOUNTED_SUFFIX;\n+\t\telse if (is_arena_nullable)\n+\t\t\tsuffix = ARENA_MAYBE_NULL_SUFFIX;\n+\t\telse if (is_arena)\n+\t\t\tsuffix = ARENA_SUFFIX;\n \t\telse\n \t\t\tcontinue;\n \n-\t\t/* Should be a pointer to struct */\n-\t\tpointed_type = btf_type_resolve_ptr(btf,\n-\t\t\t\t\t\t args[arg_no].type,\n-\t\t\t\t\t\t \u0026arg_btf_id);\n-\t\tif (!pointed_type ||\n-\t\t !btf_type_is_struct(pointed_type)) {\n+\t\t/* Should be a pointer to struct, or any pointer for __arena/__arena_nullable */\n+\t\tpointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, \u0026arg_btf_id);\n+\t\tif (!pointed_type || (!is_arena \u0026\u0026 !is_arena_nullable \u0026\u0026 !btf_type_is_struct(pointed_type))) {\n \t\t\tpr_warn(\"stub function %s has %s tagging to an unsupported type\\n\",\n \t\t\t\tstub_fname, suffix);\n \t\t\tgoto err_out;\n@@ -273,6 +280,15 @@ static int prepare_arg_info(struct btf *btf,\n \t\t} else if (is_refcounted) {\n \t\t\tinfo-\u003ereg_type = PTR_TRUSTED | PTR_TO_BTF_ID;\n \t\t\tinfo-\u003erefcounted = true;\n+\t\t} else if (is_arena || is_arena_nullable) {\n+\t\t\t/*\n+\t\t\t * Both types get PTR_TO_ARENA. In verifier state,\n+\t\t\t * PTR_TO_ARENA encompasses potential NULL values, but\n+\t\t\t * we do not force the program to check it, or maintain\n+\t\t\t * precision around it, since it has no safety implication.\n+\t\t\t */\n+\t\t\tinfo-\u003ereg_type = PTR_TO_ARENA;\n+\t\t\tinfo-\u003earena_nullable = is_arena_nullable;\n \t\t}\n \n \t\tinfo++;\ndiff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c\nindex 5e8ac45ce56af..e31f56f401ebf 100644\n--- a/kernel/bpf/btf.c\n+++ b/kernel/bpf/btf.c\n@@ -6963,15 +6963,19 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,\n \t\treturn false;\n \t}\n \n-\t/* check for PTR_TO_RDONLY_BUF_OR_NULL or PTR_TO_RDWR_BUF_OR_NULL */\n+\t/*\n+\t * Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL or\n+\t * PTR_TO_ARENA (both nullable and non-nullable cases).\n+\t */\n \tfor (i = 0; i \u003c prog-\u003eaux-\u003ectx_arg_info_size; i++) {\n \t\tconst struct bpf_ctx_arg_aux *ctx_arg_info = \u0026prog-\u003eaux-\u003ectx_arg_info[i];\n \t\tu32 type, flag;\n \n \t\ttype = base_type(ctx_arg_info-\u003ereg_type);\n \t\tflag = type_flag(ctx_arg_info-\u003ereg_type);\n-\t\tif (ctx_arg_info-\u003eoffset == off \u0026\u0026 type == PTR_TO_BUF \u0026\u0026\n-\t\t (flag \u0026 PTR_MAYBE_NULL)) {\n+\t\tif (ctx_arg_info-\u003eoffset == off \u0026\u0026\n+\t\t (type == PTR_TO_ARENA ||\n+\t\t (type == PTR_TO_BUF \u0026\u0026 (flag \u0026 PTR_MAYBE_NULL)))) {\n \t\t\tinfo-\u003ereg_type = ctx_arg_info-\u003ereg_type;\n \t\t\treturn true;\n \t\t}\n@@ -7541,6 +7545,22 @@ static u8 __get_type_fmodel_flags(const struct btf_type *t)\n \treturn flags;\n }\n \n+static u8 __get_arg_fmodel_flags(const struct btf *btf,\n+\t\t\t\t const struct btf_param *arg,\n+\t\t\t\t const struct btf_type *t)\n+{\n+\tu8 flags = __get_type_fmodel_flags(t);\n+\n+\tif (btf_param_match_suffix(btf, arg, \"__arena\") ||\n+\t btf_param_match_suffix(btf, arg, \"__arena_nullable\"))\n+\t\tflags |= BTF_FMODEL_ARENA_ARG;\n+\tif (btf_param_match_suffix(btf, arg, \"__nullable\") ||\n+\t btf_param_match_suffix(btf, arg, \"__arena_nullable\"))\n+\t\tflags |= BTF_FMODEL_NULLABLE_ARG;\n+\n+\treturn flags;\n+}\n+\n int btf_distill_func_proto(struct bpf_verifier_log *log,\n \t\t\t struct btf *btf,\n \t\t\t const struct btf_type *func,\n@@ -7606,7 +7626,7 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,\n \t\t\treturn -EINVAL;\n \t\t}\n \t\tm-\u003earg_size[i] = ret;\n-\t\tm-\u003earg_flags[i] = __get_type_fmodel_flags(t);\n+\t\tm-\u003earg_flags[i] = __get_arg_fmodel_flags(btf, \u0026args[i], t);\n \t}\n \tm-\u003enr_args = nargs;\n \treturn 0;\ndiff --git a/kernel/bpf/core.c b/kernel/bpf/core.c\nindex e2076667b2451..a3e1fae32eace 100644\n--- a/kernel/bpf/core.c\n+++ b/kernel/bpf/core.c\n@@ -3308,6 +3308,11 @@ bool __weak bpf_jit_supports_stack_args(void)\n \treturn false;\n }\n \n+bool __weak bpf_jit_supports_arena_args(void)\n+{\n+\treturn false;\n+}\n+\n bool __weak bpf_jit_supports_far_kfunc_call(void)\n {\n \treturn false;\ndiff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c\nindex ed7999ad6c66c..d2d7a2904345d 100644\n--- a/kernel/bpf/trampoline.c\n+++ b/kernel/bpf/trampoline.c\n@@ -529,6 +529,53 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a\n \treturn tnodes;\n }\n \n+static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c prog-\u003eaux-\u003ectx_arg_info_size; i++)\n+\t\tif (base_type(prog-\u003eaux-\u003ectx_arg_info[i].reg_type) == PTR_TO_ARENA)\n+\t\t\treturn true;\n+\treturn false;\n+}\n+\n+/*\n+ * Collect which ctx slots of a struct_ops trampoline hold arena kernel\n+ * pointers that save_args() must convert to the arena pointer form. Only\n+ * the struct_ops indirect trampoline converts: it dispatches to a single\n+ * prog whose arena is known at generation time. Return false when there\n+ * is nothing to convert.\n+ */\n+bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,\n+\t\t\t\t struct bpf_tramp_arena_args *aargs)\n+{\n+\tconst struct bpf_prog *prog;\n+\tint i;\n+\n+\tmemset(aargs, 0, sizeof(*aargs));\n+\n+\tif (!(flags \u0026 BPF_TRAMP_F_INDIRECT) ||\n+\t tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)\n+\t\treturn false;\n+\n+\tprog = tnodes[BPF_TRAMP_FENTRY].nodes[0]-\u003elink-\u003eprog;\n+\tfor (i = 0; i \u003c prog-\u003eaux-\u003ectx_arg_info_size; i++) {\n+\t\tconst struct bpf_ctx_arg_aux *info = \u0026prog-\u003eaux-\u003ectx_arg_info[i];\n+\n+\t\tif (base_type(info-\u003ereg_type) != PTR_TO_ARENA)\n+\t\t\tcontinue;\n+\t\taargs-\u003eslots |= BIT(info-\u003eoffset / 8);\n+\t\tif (info-\u003earena_nullable)\n+\t\t\taargs-\u003enullable_slots |= BIT(info-\u003eoffset / 8);\n+\t}\n+\tif (!aargs-\u003eslots)\n+\t\treturn false;\n+\tif (WARN_ON_ONCE(!prog-\u003eaux-\u003earena))\n+\t\treturn false;\n+\taargs-\u003ekern_vm_start = bpf_arena_get_kern_vm_start(prog-\u003eaux-\u003earena);\n+\treturn true;\n+}\n+\n static void bpf_tramp_image_free(struct bpf_tramp_image *im)\n {\n \tbpf_image_ksym_del(\u0026im-\u003eksym);\n@@ -685,6 +732,7 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut\n \tu32 orig_flags = tr-\u003eflags;\n \tbool ip_arg = false;\n \tint err, total, size;\n+\tint kind, i;\n \n \ttnodes = bpf_trampoline_get_progs(tr, \u0026total, \u0026ip_arg);\n \tif (IS_ERR(tnodes))\n@@ -695,6 +743,22 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut\n \t\tgoto out;\n \t}\n \n+\t/*\n+\t * Arena ctx args are converted only by the struct_ops indirect\n+\t * trampoline, which dispatches to a single known prog. Generic\n+\t * trampolines can mix progs with different arenas, so no conversion\n+\t * is possible here. Not reachable today: only struct_ops progs get\n+\t * arena ctx args and they never ride generic trampolines.\n+\t */\n+\tfor (kind = 0; kind \u003c BPF_TRAMP_MAX; kind++) {\n+\t\tfor (i = 0; i \u003c tnodes[kind].nr_nodes; i++) {\n+\t\t\tif (bpf_prog_has_arena_ctx_arg(tnodes[kind].nodes[i]-\u003elink-\u003eprog)) {\n+\t\t\t\terr = -ENOTSUPP;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \t/* clear all bits except SHARE_IPMODIFY and TAIL_CALL_CTX */\n \ttr-\u003eflags \u0026= (BPF_TRAMP_F_SHARE_IPMODIFY | BPF_TRAMP_F_TAIL_CALL_CTX);\n \ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex b274004fccfd9..c9129330dede6 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -10907,6 +10907,16 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param\n \treturn btf_param_match_suffix(btf, arg, \"__irq_flag\");\n }\n \n+static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)\n+{\n+\treturn btf_param_match_suffix(btf, arg, \"__arena\");\n+}\n+\n+static bool is_kfunc_arg_arena_nullable(const struct btf *btf, const struct btf_param *arg)\n+{\n+\treturn btf_param_match_suffix(btf, arg, \"__arena_nullable\");\n+}\n+\n static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,\n \t\t\t\t\t const struct btf_param *arg,\n \t\t\t\t\t const char *name)\n@@ -11127,6 +11137,7 @@ enum kfunc_ptr_arg_type {\n \tKF_ARG_PTR_TO_IRQ_FLAG,\n \tKF_ARG_PTR_TO_RES_SPIN_LOCK,\n \tKF_ARG_PTR_TO_TASK_WORK,\n+\tKF_ARG_PTR_TO_ARENA,\n };\n \n enum special_kfunc_type {\n@@ -11412,7 +11423,6 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n \t\t\treg_arg_name(env, argno), btf_type_str(t));\n \t\treturn -EINVAL;\n \t}\n-\n \tref_t = btf_type_skip_modifiers(meta-\u003ebtf, t-\u003etype, NULL);\n \tref_tname = btf_name_by_offset(meta-\u003ebtf, ref_t-\u003ename_off);\n \n@@ -11461,6 +11471,9 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n \t\targ_type = KF_ARG_PTR_TO_RES_SPIN_LOCK;\n \telse if (is_kfunc_arg_callback(env, meta-\u003ebtf, \u0026args[arg]))\n \t\targ_type = KF_ARG_PTR_TO_CALLBACK;\n+\telse if (is_kfunc_arg_arena(meta-\u003ebtf, \u0026args[arg]) ||\n+\t\t is_kfunc_arg_arena_nullable(meta-\u003ebtf, \u0026args[arg]))\n+\t\targ_type = KF_ARG_PTR_TO_ARENA;\n \telse if (arg + 1 \u003c nargs \u0026\u0026\n \t\t (is_kfunc_arg_mem_size(meta-\u003ebtf, \u0026args[arg + 1]) ||\n \t\t is_kfunc_arg_const_mem_size(meta-\u003ebtf, \u0026args[arg + 1]))) {\n@@ -12154,6 +12167,31 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \n \t\tt = btf_type_skip_modifiers(btf, args[i].type, NULL);\n \n+\t\tif (base_type(kf_arg_type) == KF_ARG_PTR_TO_ARENA) {\n+\t\t\tif (!bpf_jit_supports_arena_args()) {\n+\t\t\t\tverbose(env, \"JIT does not support kfunc %s() with arena pointer arguments\\n\",\n+\t\t\t\t\tfunc_name);\n+\t\t\t\treturn -ENOTSUPP;\n+\t\t\t}\n+\t\t\tif (!env-\u003eprog-\u003eaux-\u003earena) {\n+\t\t\t\tverbose(env,\n+\t\t\t\t\t\"%s arena pointer requires a program with an associated arena\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\tif (regno \u003c 0) {\n+\t\t\t\tverbose(env, \"%s arena pointer cannot be a stack argument\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\tif (reg-\u003etype != PTR_TO_ARENA \u0026\u0026 reg-\u003etype != SCALAR_VALUE) {\n+\t\t\t\tverbose(env, \"%s is not a pointer to arena or scalar\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (btf_type_is_ptr(t) \u0026\u0026 (bpf_register_is_null(reg) || type_may_be_null(reg-\u003etype)) \u0026\u0026\n \t\t !is_kfunc_arg_nullable(meta-\u003ebtf, \u0026args[i])) {\n \t\t\tverbose(env, \"Possibly NULL pointer passed to trusted %s\\n\",\n@@ -18807,6 +18845,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\n {\n \tconst struct btf_type *t, *func_proto;\n \tconst struct bpf_struct_ops_desc *st_ops_desc;\n+\tconst struct bpf_struct_ops_arg_info *arg_info;\n \tconst struct bpf_struct_ops *st_ops;\n \tconst struct btf_member *member;\n \tstruct bpf_prog *prog = env-\u003eprog;\n@@ -18885,10 +18924,23 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\n \t\treturn -EACCES;\n \t}\n \n-\tfor (i = 0; i \u003c st_ops_desc-\u003earg_info[member_idx].cnt; i++) {\n-\t\tif (st_ops_desc-\u003earg_info[member_idx].info[i].refcounted) {\n+\targ_info = \u0026st_ops_desc-\u003earg_info[member_idx];\n+\tfor (i = 0; i \u003c arg_info-\u003ecnt; i++) {\n+\t\tconst struct bpf_ctx_arg_aux *info = \u0026arg_info-\u003einfo[i];\n+\n+\t\tif (info-\u003erefcounted)\n \t\t\thas_refcounted_arg = true;\n-\t\t\tbreak;\n+\t\tif (base_type(info-\u003ereg_type) == PTR_TO_ARENA) {\n+\t\t\tif (!bpf_jit_supports_arena_args()) {\n+\t\t\t\tverbose(env, \"JIT does not support arena arguments\\n\");\n+\t\t\t\treturn -ENOTSUPP;\n+\t\t\t}\n+\t\t\tif (!prog-\u003eaux-\u003earena) {\n+\t\t\t\tverbose(env,\n+\t\t\t\t\t\"arena argument of %s requires a program with an associated arena\\n\",\n+\t\t\t\t\tmname);\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -18909,8 +18961,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\n \tprog-\u003eaux-\u003eattach_func_name = mname;\n \tenv-\u003eops = st_ops-\u003everifier_ops;\n \n-\treturn bpf_prog_ctx_arg_info_init(prog, st_ops_desc-\u003earg_info[member_idx].info,\n-\t\t\t\t\t st_ops_desc-\u003earg_info[member_idx].cnt);\n+\treturn bpf_prog_ctx_arg_info_init(prog, arg_info-\u003einfo, arg_info-\u003ecnt);\n }\n #define SECURITY_PREFIX \"security_\"\n \ndiff --git a/tools/testing/selftests/bpf/prog_tests/arena_kfunc.c b/tools/testing/selftests/bpf/prog_tests/arena_kfunc.c\nnew file mode 100644\nindex 0000000000000..9be19565f0d53\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/arena_kfunc.c\n@@ -0,0 +1,15 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003ctest_progs.h\u003e\n+\n+#include \"arena_kfunc.skel.h\"\n+\n+/*\n+ * The test kfuncs live in bpf_testmod. Resolving kfuncs against module\n+ * BTFs needs CAP_SYS_ADMIN, so run with full capabilities instead of\n+ * through the verifier tests' capability-restricted runner.\n+ */\n+void test_arena_kfunc(void)\n+{\n+\tRUN_TESTS(arena_kfunc);\n+}\ndiff --git a/tools/testing/selftests/bpf/prog_tests/arena_kfunc_jit.c b/tools/testing/selftests/bpf/prog_tests/arena_kfunc_jit.c\nnew file mode 100644\nindex 0000000000000..2359cde24c45d\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/arena_kfunc_jit.c\n@@ -0,0 +1,13 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003ctest_progs.h\u003e\n+#include \"arena_kfunc_jit.skel.h\"\n+\n+/*\n+ * Runs with full capabilities: resolving module kfunc ksyms requires\n+ * CAP_SYS_ADMIN, which rules out the capability-restricted runner.\n+ */\n+void test_arena_kfunc_jit(void)\n+{\n+\tRUN_TESTS(arena_kfunc_jit);\n+}\ndiff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c\nnew file mode 100644\nindex 0000000000000..b1e39defa38ae\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c\n@@ -0,0 +1,74 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003ctest_progs.h\u003e\n+\n+#include \"struct_ops_arena.skel.h\"\n+#include \"struct_ops_arena_fail.skel.h\"\n+\n+#if defined(__x86_64__)\n+/*\n+ * Attach callbacks with __arena and __arena_nullable arguments and drive\n+ * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.\n+ */\n+static void arena_arg(void)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, topts);\n+\tstruct struct_ops_arena *skel;\n+\tstruct bpf_link *link = NULL;\n+\tint err;\n+\n+\tskel = struct_ops_arena__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"struct_ops_arena__open_and_load\"))\n+\t\treturn;\n+\n+\tlink = bpf_map__attach_struct_ops(skel-\u003emaps.testmod_arena);\n+\tif (!ASSERT_OK_PTR(link, \"attach_struct_ops\"))\n+\t\tgoto out;\n+\n+\terr = bpf_prog_test_run_opts(bpf_program__fd(skel-\u003eprogs.trigger),\n+\t\t\t\t \u0026topts);\n+\tASSERT_OK(err, \"test_run\");\n+\tASSERT_EQ(topts.retval, 0, \"trigger_retval\");\n+\n+out:\n+\tbpf_link__destroy(link);\n+\tstruct_ops_arena__destroy(skel);\n+}\n+\n+/*\n+ * A program with no arena cannot attach to a member with an __arena\n+ * argument.\n+ */\n+static void arena_arg_fail(void)\n+{\n+\tstruct struct_ops_arena_fail *skel;\n+\n+\tskel = struct_ops_arena_fail__open_and_load();\n+\tif (ASSERT_ERR_PTR(skel, \"struct_ops_arena_fail__open_and_load\"))\n+\t\treturn;\n+\n+\tstruct_ops_arena_fail__destroy(skel);\n+}\n+#endif\n+\n+/*\n+ * Serialized because it attaches the singleton bpf_testmod_ops3, which\n+ * test_struct_ops_private_stack also attaches; registering it twice fails\n+ * with -EEXIST.\n+ */\n+void serial_test_struct_ops_arena(void)\n+{\n+\t/*\n+\t * Arena struct_ops arguments need JIT support, currently x86-64 only.\n+\t * Elsewhere verification fails with \"JIT does not support arena\n+\t * arguments\", so the programs cannot even load.\n+\t */\n+#if defined(__x86_64__)\n+\tif (test__start_subtest(\"arena_arg\"))\n+\t\tarena_arg();\n+\tif (test__start_subtest(\"arena_arg_fail\"))\n+\t\tarena_arg_fail();\n+#else\n+\ttest__skip();\n+#endif\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c\nnew file mode 100644\nindex 0000000000000..15d48151797da\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c\n@@ -0,0 +1,260 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+\n+#define BPF_NO_KFUNC_PROTOTYPES\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_misc.h\"\n+#include \"bpf_experimental.h\"\n+#include \u003cbpf_arena_common.h\u003e\n+#include \"../test_kmods/bpf_testmod_kfunc.h\"\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARENA);\n+\t__uint(map_flags, BPF_F_MMAPABLE);\n+\t/* page 0 hosts the arena global, page 1 is for allocations */\n+\t__uint(max_entries, 2);\n+} arena SEC(\".maps\");\n+\n+/*\n+ * Occupies page 0 so no allocation lands at arena offset 0, which the\n+ * nullable tests below must be able to tell apart from NULL.\n+ */\n+u64 __arena arena_pad;\n+\n+/* volatile to force the scalar reloads below */\n+volatile u64 stash;\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_forms(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\tu64 ret;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\t/* PTR_TO_ARENA argument */\n+\t*val = 41;\n+\tret = bpf_kfunc_arena_arg_test((u64 *)val);\n+\tif (ret != 41 || *val != 42)\n+\t\treturn 2;\n+\n+\t/* the low 32 bits as a scalar */\n+\tstash = (u32)(u64)val;\n+\tret = bpf_kfunc_arena_arg_test((u64 *)stash);\n+\tif (ret != 42 || *val != 43)\n+\t\treturn 3;\n+\n+\t/* the full user address as a scalar */\n+\tstash = (u64)val;\n+\tbpf_addr_space_cast(stash, 1, 0);\n+\tret = bpf_kfunc_arena_arg_test((u64 *)stash);\n+\tif (ret != 43 || *val != 44)\n+\t\treturn 4;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+/*\n+ * Pin the rebase semantics using the capture kfuncs, which return the raw\n+ * argument value: __arena rebases unconditionally, so zero low 32 bits\n+ * arrive as the arena kernel base, while __arena_nullable turns them into\n+ * NULL.\n+ */\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_rebase(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\tu64 base, off;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tbase = bpf_kfunc_arena_cap_test(NULL);\n+\tif (!base)\n+\t\treturn 2;\n+\n+\t/* only the low 32 bits contribute */\n+\tstash = 0xbadc0ffe00000000;\n+\tif (bpf_kfunc_arena_cap_test((u64 *)stash) != base)\n+\t\treturn 3;\n+\n+\toff = (u32)(u64)val;\n+\tif (bpf_kfunc_arena_cap_test((u64 *)val) != base + off)\n+\t\treturn 4;\n+\n+\tif (bpf_kfunc_arena_cap_nullable_test(NULL) != 0)\n+\t\treturn 5;\n+\n+\tstash = 0xbadc0ffe00000000;\n+\tif (bpf_kfunc_arena_cap_nullable_test((u64 *)stash) != 0)\n+\t\treturn 6;\n+\n+\tif (bpf_kfunc_arena_cap_nullable_test((u64 *)val) != base + off)\n+\t\treturn 7;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_nullable(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\tu64 ret;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\t*val = 41;\n+\tret = bpf_kfunc_arena_nullable_arg_test((u64 *)val);\n+\tif (ret != 41 || *val != 42)\n+\t\treturn 2;\n+\n+\tif (bpf_kfunc_arena_nullable_arg_test(NULL) != 0xdeadbeef)\n+\t\treturn 3;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_args5(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tval[0] = 1;\n+\tval[1] = 2;\n+\tval[2] = 4;\n+\tval[3] = 8;\n+\tval[4] = 16;\n+\n+\tif (bpf_kfunc_arena_args5_test((u64 *)\u0026val[0], (u64 *)\u0026val[1],\n+\t\t\t\t (u64 *)\u0026val[2], (u64 *)\u0026val[3],\n+\t\t\t\t (u64 *)\u0026val[4]) != 31)\n+\t\treturn 2;\n+\tif (bpf_kfunc_arena_args5_test((u64 *)\u0026val[0], (u64 *)\u0026val[1],\n+\t\t\t\t (u64 *)\u0026val[2], (u64 *)\u0026val[3], NULL) != 15)\n+\t\treturn 3;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_mixed(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tval[0] = 7;\n+\tval[1] = 5;\n+\n+\tif (bpf_kfunc_arena_mixed_test((u64 *)\u0026val[0], NULL) != 7)\n+\t\treturn 2;\n+\n+\tif (bpf_kfunc_arena_mixed_test((u64 *)\u0026val[0], (u64 *)\u0026val[1]) != 12)\n+\t\treturn 3;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+/* kernel-side faults on unpopulated pages recover via the scratch page */\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_unpopulated(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tstash = (u64)val + PAGE_SIZE;\n+\tbpf_kfunc_arena_arg_test((u64 *)stash);\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__failure __msg(\"arena pointer requires a program with an associated arena\")\n+int arena_arg_no_arena(void *ctx)\n+{\n+\tbpf_kfunc_arena_arg_test((u64 *)1);\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__failure __msg(\"is not a pointer to arena or scalar\")\n+int arena_arg_bad_reg(void *ctx)\n+{\n+\tu64 buf = 0;\n+\n+\t/* use the arena so the program passes the arena presence check */\n+\tbpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tbpf_kfunc_arena_arg_test(\u0026buf);\n+\treturn 0;\n+}\n+\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) \u0026\u0026 \\\n+\tdefined(__BPF_FEATURE_STACK_ARGUMENT)\n+SEC(\"syscall\")\n+__arch_x86_64\n+__failure __msg(\"arena pointer cannot be a stack argument\")\n+int arena_arg_stack(void *ctx)\n+{\n+\tbpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tbpf_kfunc_arena_stack_arg_test(1, 2, 3, 4, 5, (u64 *)1);\n+\treturn 0;\n+}\n+#else\n+SEC(\"syscall\")\n+__arch_x86_64\n+__description(\"arena_arg_stack: not supported, dummy test\")\n+__success\n+int arena_arg_stack(void *ctx)\n+{\n+\treturn 0;\n+}\n+#endif\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c\nnew file mode 100644\nindex 0000000000000..84747611725f6\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c\n@@ -0,0 +1,96 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+\n+/*\n+ * Verify the JIT-emitted rebase sequences for __arena and __arena_nullable\n+ * kfunc arguments. The capture kfuncs take the argument without\n+ * dereferencing it, so these tests pin only the emitted code.\n+ */\n+#define BPF_NO_KFUNC_PROTOTYPES\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_misc.h\"\n+#include \"bpf_experimental.h\"\n+#include \u003cbpf_arena_common.h\u003e\n+#include \"../test_kmods/bpf_testmod_kfunc.h\"\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARENA);\n+\t__uint(map_flags, BPF_F_MMAPABLE);\n+\t__uint(max_entries, 1);\n+} arena SEC(\".maps\");\n+\n+/* volatile to force the scalar reloads below */\n+volatile u64 stash;\n+\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__jited(\"...\")\n+__jited(\"\tmovl\t%edi, %edi\")\n+__jited(\"\taddq\t%r12, %rdi\")\n+__jited(\"...\")\n+__jited(\"\tcallq\t{{.*}}\")\n+__success\n+int arena_arg_jit_rebase(void *ctx)\n+{\n+\tstash = (u64)bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tbpf_kfunc_arena_cap_test((u64 *)stash);\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__jited(\"...\")\n+__jited(\"\tmovl\t%edi, %edi\")\n+__jited(\"\ttestl\t%edi, %edi\")\n+__jited(\"\tje\t{{.*}}\")\n+__jited(\"\taddq\t%r12, %rdi\")\n+__success\n+int arena_arg_jit_nullable(void *ctx)\n+{\n+\tstash = (u64)bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tbpf_kfunc_arena_cap_nullable_test((u64 *)stash);\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__jited(\"...\")\n+__jited(\"\tmovl\t%edi, %edi\")\n+__jited(\"\taddq\t%r12, %rdi\")\n+__jited(\"\tmovl\t%esi, %esi\")\n+__jited(\"\taddq\t%r12, %rsi\")\n+__jited(\"\tmovl\t%edx, %edx\")\n+__jited(\"\taddq\t%r12, %rdx\")\n+__jited(\"\tmovl\t%ecx, %ecx\")\n+__jited(\"\taddq\t%r12, %rcx\")\n+__jited(\"\tmovl\t%r8d, %r8d\")\n+__jited(\"\ttestl\t%r8d, %r8d\")\n+__jited(\"\tje\t{{.*}}\")\n+__jited(\"\taddq\t%r12, %r8\")\n+__success\n+int arena_arg_jit_args5(void *ctx)\n+{\n+\tu64 __arena *val;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tval[0] = 1;\n+\tval[1] = 2;\n+\tval[2] = 4;\n+\tval[3] = 8;\n+\tval[4] = 16;\n+\n+\tbpf_kfunc_arena_args5_test((u64 *)\u0026val[0], (u64 *)\u0026val[1],\n+\t\t\t\t (u64 *)\u0026val[2], (u64 *)\u0026val[3],\n+\t\t\t\t (u64 *)\u0026val[4]);\n+\treturn 0;\n+}\n+\n+#endif /* __BPF_FEATURE_ADDR_SPACE_CAST */\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c\nnew file mode 100644\nindex 0000000000000..ba04c73d8d967\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c\n@@ -0,0 +1,115 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+\n+#define BPF_NO_KFUNC_PROTOTYPES\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_experimental.h\"\n+#include \u003cbpf_arena_common.h\u003e\n+#include \"../test_kmods/bpf_testmod.h\"\n+#include \"../test_kmods/bpf_testmod_kfunc.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARENA);\n+\t__uint(map_flags, BPF_F_MMAPABLE);\n+\t/* page 0 hosts the arena globals, page 1 is for allocations */\n+\t__uint(max_entries, 2);\n+} arena SEC(\".maps\");\n+\n+/* also associates the callbacks with the arena */\n+u64 __arena arena_touch;\n+/* raw value of the last __arena ctx argument, captured by test_arena_cb */\n+u64 __arena cb_ptr_val;\n+\n+SEC(\"struct_ops/test_arena\")\n+int test_arena_cb(unsigned long long *ctx)\n+{\n+\tu64 __arena *ptr = (u64 __arena *)ctx[0];\n+\n+\tarena_touch++;\n+\tcb_ptr_val = ctx[0];\n+\t*ptr += 1;\n+\treturn 0;\n+}\n+\n+SEC(\"struct_ops/test_arena_nullable\")\n+int test_arena_nullable_cb(unsigned long long *ctx)\n+{\n+\tu64 __arena *ptr = (u64 __arena *)ctx[0];\n+\n+\tarena_touch++;\n+\tif (!ptr)\n+\t\treturn 0xbee;\n+\t*ptr += 1;\n+\treturn 0;\n+}\n+\n+SEC(\"struct_ops/test_arena_stack\")\n+int test_arena_stack_cb(unsigned long long *ctx)\n+{\n+\tu64 __arena *ptr = (u64 __arena *)ctx[8];\n+\n+\tarena_touch++;\n+\t/* pin the slot layout: the leading args fill ctx[0]..ctx[7] */\n+\tif (ctx[0] != 1 || ctx[7] != 8)\n+\t\treturn 0xbad;\n+\t*ptr += 1;\n+\treturn 0;\n+}\n+\n+SEC(\".struct_ops.link\")\n+struct bpf_testmod_ops3 testmod_arena = {\n+\t.test_arena = (void *)test_arena_cb,\n+\t.test_arena_nullable = (void *)test_arena_nullable_cb,\n+\t.test_arena_stack = (void *)test_arena_stack_cb,\n+};\n+\n+SEC(\"syscall\")\n+int trigger(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\tint ret;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\t*val = 41;\n+\tret = bpf_testmod_ops3_call_test_arena((u64 *)val);\n+\tif (ret)\n+\t\treturn 2;\n+\tif (*val != 42)\n+\t\treturn 3;\n+\n+\t/*\n+\t * The callback must have seen exactly (u32)(kaddr - kern_vm_start),\n+\t * which is the arena offset of val with the upper 32 bits clear.\n+\t */\n+\tif (cb_ptr_val != (u32)(u64)val)\n+\t\treturn 4;\n+\n+\tret = bpf_testmod_ops3_call_test_arena_nullable((u64 *)val);\n+\tif (ret)\n+\t\treturn 5;\n+\tif (*val != 43)\n+\t\treturn 6;\n+\n+\t/* NULL survives the nullable kfunc and the trampoline as NULL */\n+\tret = bpf_testmod_ops3_call_test_arena_nullable(NULL);\n+\tif (ret != 0xbee)\n+\t\treturn 7;\n+\n+\t/* the arena pointer is stack-passed into the trampoline here */\n+\tret = bpf_testmod_ops3_call_test_arena_stack((u64 *)val);\n+\tif (ret)\n+\t\treturn 8;\n+\tif (*val != 44)\n+\t\treturn 9;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c\nnew file mode 100644\nindex 0000000000000..1c0ec727d6374\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c\n@@ -0,0 +1,20 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"../test_kmods/bpf_testmod.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+/* No arena in the program: attaching to test_arena must be rejected. */\n+SEC(\"struct_ops/test_arena\")\n+int test_arena_no_arena(unsigned long long *ctx)\n+{\n+\treturn 0;\n+}\n+\n+SEC(\".struct_ops.link\")\n+struct bpf_testmod_ops3 testmod_arena_fail = {\n+\t.test_arena = (void *)test_arena_no_arena,\n+};\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\nindex eb0f9b5e18d85..fdeaa56356b52 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n@@ -237,6 +237,56 @@ __bpf_kfunc void bpf_kfunc_common_test(void)\n {\n }\n \n+__bpf_kfunc u64 bpf_kfunc_arena_arg_test(u64 *val__arena)\n+{\n+\tu64 old;\n+\n+\told = *val__arena;\n+\t*val__arena = old + 1;\n+\treturn old;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_nullable_arg_test(u64 *val__arena_nullable)\n+{\n+\tu64 old;\n+\n+\tif (!val__arena_nullable)\n+\t\treturn 0xdeadbeef;\n+\n+\told = *val__arena_nullable;\n+\t*val__arena_nullable = old + 1;\n+\treturn old;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_cap_test(u64 *val__arena)\n+{\n+\treturn (u64)val__arena;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_cap_nullable_test(u64 *val__arena_nullable)\n+{\n+\treturn (u64)val__arena_nullable;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_args5_test(u64 *a__arena, u64 *b__arena,\n+\t\t\t\t\t u64 *c__arena, u64 *d__arena,\n+\t\t\t\t\t u64 *e__arena_nullable)\n+{\n+\treturn *a__arena + *b__arena + *c__arena + *d__arena +\n+\t (e__arena_nullable ? *e__arena_nullable : 0);\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_stack_arg_test(u64 a, u64 b, u64 c, u64 d, u64 e,\n+\t\t\t\t\t\tu64 *f__arena)\n+{\n+\treturn a + b + c + d + e + *f__arena;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_mixed_test(u64 *a__arena, u64 *b__arena_nullable)\n+{\n+\treturn *a__arena + (b__arena_nullable ? *b__arena_nullable : 0);\n+}\n+\n __bpf_kfunc void bpf_kfunc_dynptr_test(struct bpf_dynptr *ptr,\n \t\t\t\t struct bpf_dynptr *ptr__nullable)\n {\n@@ -347,9 +397,29 @@ static int bpf_testmod_test_4(void)\n \treturn 0;\n }\n \n+static int bpf_testmod_ops3__test_arena(u64 *ptr__arena)\n+{\n+\treturn 0;\n+}\n+\n+static int bpf_testmod_ops3__test_arena_nullable(u64 *ptr__arena_nullable)\n+{\n+\treturn 0;\n+}\n+\n+static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,\n+\t\t\t\t\t u64 e, u64 f, u64 g, u64 h,\n+\t\t\t\t\t u64 *ptr__arena)\n+{\n+\treturn 0;\n+}\n+\n static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {\n \t.test_1 = bpf_testmod_test_3,\n \t.test_2 = bpf_testmod_test_4,\n+\t.test_arena = bpf_testmod_ops3__test_arena,\n+\t.test_arena_nullable = bpf_testmod_ops3__test_arena_nullable,\n+\t.test_arena_stack = bpf_testmod_ops3__test_arena_stack,\n };\n \n static void bpf_testmod_test_struct_ops3(void)\n@@ -368,6 +438,21 @@ __bpf_kfunc void bpf_testmod_ops3_call_test_2(void)\n \tst_ops3-\u003etest_2();\n }\n \n+__bpf_kfunc int bpf_testmod_ops3_call_test_arena(u64 *ptr__arena)\n+{\n+\treturn st_ops3-\u003etest_arena(ptr__arena);\n+}\n+\n+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena_nullable)\n+{\n+\treturn st_ops3-\u003etest_arena_nullable(ptr__arena_nullable);\n+}\n+\n+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_stack(u64 *ptr__arena)\n+{\n+\treturn st_ops3-\u003etest_arena_stack(1, 2, 3, 4, 5, 6, 7, 8, ptr__arena);\n+}\n+\n struct bpf_testmod_btf_type_tag_1 {\n \tint a;\n };\n@@ -755,6 +840,13 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)\n BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)\n BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)\n BTF_ID_FLAGS(func, bpf_kfunc_common_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_arg_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_nullable_arg_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_nullable_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_args5_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_stack_arg_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_mixed_test)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)\n BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)\n BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)\n@@ -770,6 +862,9 @@ BTF_ID_FLAGS(func, bpf_testmod_ctx_create, KF_ACQUIRE | KF_RET_NULL)\n BTF_ID_FLAGS(func, bpf_testmod_ctx_release, KF_RELEASE)\n BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)\n BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)\n+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)\n+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)\n+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)\n BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);\n BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);\n BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\nindex 863fd10f16199..33f2af5b70857 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\n@@ -106,6 +106,12 @@ struct bpf_testmod_ops2 {\n struct bpf_testmod_ops3 {\n \tint (*test_1)(void);\n \tint (*test_2)(void);\n+\t/* Used to test arena pointer arguments. */\n+\tint (*test_arena)(u64 *ptr);\n+\tint (*test_arena_nullable)(u64 *ptr);\n+\t/* enough leading args to force @ptr onto the stack on x86 and arm64 */\n+\tint (*test_arena_stack)(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,\n+\t\t\t\tu64 g, u64 h, u64 *ptr);\n };\n \n struct st_ops_args {\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 c36bb911defa7..1a72d0fda53c4 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@@ -98,6 +98,16 @@ void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym;\n void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym;\n \n void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym;\n+__u64 bpf_kfunc_arena_arg_test(__u64 *val__arena) __ksym;\n+__u64 bpf_kfunc_arena_nullable_arg_test(__u64 *val__arena_nullable) __ksym;\n+__u64 bpf_kfunc_arena_cap_test(__u64 *val__arena) __ksym;\n+__u64 bpf_kfunc_arena_cap_nullable_test(__u64 *val__arena_nullable) __ksym;\n+__u64 bpf_kfunc_arena_args5_test(__u64 *a__arena, __u64 *b__arena,\n+\t\t\t\t __u64 *c__arena, __u64 *d__arena,\n+\t\t\t\t __u64 *e__arena_nullable) __ksym;\n+__u64 bpf_kfunc_arena_stack_arg_test(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,\n+\t\t\t\t __u64 *f__arena) __ksym;\n+__u64 bpf_kfunc_arena_mixed_test(__u64 *a__arena, __u64 *b__arena_nullable) __ksym;\n int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym;\n int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;\n int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;\n@@ -111,6 +121,9 @@ u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym;\n #endif\n \n void bpf_testmod_test_mod_kfunc(int i) __ksym;\n+int bpf_testmod_ops3_call_test_arena(__u64 *ptr__arena) __ksym;\n+int bpf_testmod_ops3_call_test_arena_nullable(__u64 *ptr__arena_nullable) __ksym;\n+int bpf_testmod_ops3_call_test_arena_stack(__u64 *ptr__arena) __ksym;\n \n __u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,\n \t\t\t\t__u32 c, __u64 d) __ksym;\n"
} |
| 2/1 | 2026/08/03 16:13 | agent | patch-evaluator |
3mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_collect_arena_args",
"btf_distill_func_proto",
"btf_ctx_access"
],
"Reasoning": "The patch introduces `__arena` and `__arena_nullable` annotations for BPF kfuncs and struct_ops arguments. It modifies the BPF verifier, JIT compiler (x86), and trampoline logic to support arena pointers, including rebasing them to kernel addresses. This is a functional change to the core BPF subsystem and should be fuzzed to ensure memory safety and correct verification of arena pointers.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a '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: For your convenience, here is the diff of the changes:
commit 00afc6746da0ec902beb66367c45701fd0658b10
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 3 16:13:07 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index cbde86d082cce..48cf746f9ff9d 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -278,6 +278,43 @@ An example is given below::
...
}
+2.3.8 __arena and __arena_nullable Annotations
+----------------------------------------------
+
+Both annotations indicate that the pointer argument points into the
+calling program's arena. The JIT rebases the value at the call site so
+the kfunc receives a directly dereferenceable kernel address, subject to
+the access rules described in :ref:`BPF_kfunc_arena_access` (at most
+``GUARD_SZ / 2``, 32 KiB, past the pointer in a single unchecked access).
+
+With ``__arena`` the rebase is unconditional and the argument is never
+NULL: a value whose lower 32 bits are zero arrives as the arena base
+address (arena offset 0). The kfunc must not check the argument for NULL.
+With ``__arena_nullable`` such a value arrives as NULL instead and the
+kfunc must check before dereferencing.
+
+An example is given below::
+
+ __bpf_kfunc int bpf_process_item(struct item *item__arena)
+ {
+ ...
+ }
+
+Calling such a kfunc requires the program to use an arena map and a JIT with
+arena argument support (currently x86-64); verification fails otherwise. The
+program can pass any value without compromising the kernel. A value that does
+not point into the arena is a program bug.
+
+The suffixes have the same meaning on the arguments of struct_ops stub
+functions, with the conversion running in the opposite direction. The
+kernel caller passes the kernel arena address and the trampoline converts
+it while saving the arguments, so the callback receives an arena pointer
+it can dereference directly. With ``__arena`` the kernel caller must not
+pass NULL. With ``__arena_nullable`` a NULL kernel pointer arrives as NULL.
+However, there is no obligation to prove to the verifier that such a pointer is
+non-NULL before use, in-line with existing semantics of arena pointers used in
+a program (or obtained from any other source).
+
.. _BPF_kfunc_nodef:
2.4 Using an existing kernel function
@@ -515,6 +552,8 @@ In order to accommodate such requirements, the verifier will enforce strict
PTR_TO_BTF_ID type matching if two types have the exact same name, with one
being suffixed with ``___init``.
+.. _BPF_kfunc_arena_access:
+
2.8 Accessing arena memory through kfunc arguments
--------------------------------------------------
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 01e7ce569c1ed..4b3e5d8a4a563 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1678,6 +1678,50 @@ static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip,
return 0;
}
+/*
+ * Rebase the __arena args of a kfunc call to arena kernel addresses,
+ * rN = kern_vm_start + (u32)rN, with R12 holding kern_vm_start. A nullable
+ * arg preserves NULL by skipping the add, tested on the truncated value as
+ * arena NULL is offset 0. Return the number of emitted bytes.
+ */
+static int emit_kfunc_arena_args(struct bpf_prog *bpf_prog,
+ const struct bpf_insn *insn, u8 **pprog)
+{
+ const struct btf_func_model *fm;
+ u8 *prog = *pprog;
+ u8 *start = prog;
+ int i;
+
+ fm = bpf_jit_find_kfunc_model(bpf_prog, insn);
+ if (!fm)
+ return -EINVAL;
+
+ for (i = 0; i < min_t(int, fm->nr_args, MAX_BPF_FUNC_REG_ARGS); i++) {
+ u8 flags = fm->arg_flags[i];
+ u32 reg = BPF_REG_1 + i;
+
+ if (!(flags & BTF_FMODEL_ARENA_ARG))
+ continue;
+ if (WARN_ON_ONCE(!bpf_prog->aux->arena))
+ return -EINVAL;
+
+ /* mov eN, eN: truncate and clear the upper 32 bits */
+ emit_mov_reg(&prog, false, reg, reg);
+ if (flags & BTF_FMODEL_NULLABLE_ARG) {
+ /* test eN, eN; jz over the 3-byte add */
+ maybe_emit_mod(&prog, reg, reg, false);
+ EMIT2(0x85, add_2reg(0xC0, reg, reg));
+ EMIT2(X86_JE, 3);
+ }
+ /* add rN, r12 */
+ maybe_emit_mod(&prog, reg, X86_REG_R12, true);
+ EMIT2(0x01, add_2reg(0xC0, reg, X86_REG_R12));
+ }
+
+ *pprog = prog;
+ return prog - start;
+}
+
static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
{
@@ -2583,6 +2627,12 @@ st: insn_off = insn->off;
}
if (!imm32)
return -EINVAL;
+ if (src_reg == BPF_PSEUDO_KFUNC_CALL) {
+ err = emit_kfunc_arena_args(bpf_prog, insn, &prog);
+ if (err < 0)
+ return err;
+ ip += err;
+ }
if (priv_frame_ptr) {
push_r9(&prog);
ip += 2;
@@ -2993,12 +3043,40 @@ static int get_nr_used_regs(const struct btf_func_model *m)
return nr_used_regs;
}
+/*
+ * Convert an arena kernel address into the arena pointer form on its way
+ * into the BPF ctx, rax = (u32)(src - kern_vm_start). A nullable arg
+ * preserves NULL, tested on the full 64-bit kernel pointer. The 32-bit
+ * subtraction both truncates and clears the upper half, so the stored
+ * value satisfies the JIT invariant for arena pointer registers.
+ */
+static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base_lo)
+{
+ u8 *prog = *pprog;
+
+ if (nullable) {
+ if (src_reg != BPF_REG_0)
+ emit_mov_reg(&prog, true, BPF_REG_0, src_reg);
+ /* test rax, rax; jz over the 5-byte sub */
+ EMIT3(0x48, 0x85, 0xC0);
+ EMIT2(X86_JE, 5);
+ } else if (src_reg != BPF_REG_0) {
+ emit_mov_reg(&prog, false, BPF_REG_0, src_reg);
+ }
+ /* sub eax, base_lo */
+ EMIT1_off32(0x2D, base_lo);
+
+ *pprog = prog;
+}
+
static void save_args(const struct btf_func_model *m, u8 **prog,
- int stack_size, bool for_call_origin, u32 flags)
+ int stack_size, bool for_call_origin, u32 flags,
+ const struct bpf_tramp_arena_args *aargs)
{
int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
bool use_jmp = bpf_trampoline_use_jmp(flags);
- int i, j;
+ int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
+ int i, j, slot = 0;
/* Store function arguments to stack.
* For a function that accepts two pointers the sequence will be:
@@ -3029,16 +3107,20 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
/* copy function arguments from origin stack frame
* into current stack frame.
*
- * The starting address of the arguments on-stack
- * is:
- * rbp + 8(push rbp) +
- * 8(return addr of origin call) +
- * 8(return addr of the caller)
- * which means: rbp + 24
+ * The arguments on-stack start above the saved rbp
+ * and the return addresses: two return addresses
+ * (origin call and caller) when the trampoline is
+ * 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.
*/
for (j = 0; j < arg_regs; j++) {
emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
- nr_stack_slots * 8 + 16 + (!use_jmp) * 8);
+ nr_stack_slots * 8 + stack_args_off);
+ if (aargs && (aargs->slots & BIT(slot)))
+ emit_arena_arg_conv(prog, BPF_REG_0,
+ aargs->nullable_slots & BIT(slot),
+ (u32)aargs->kern_vm_start);
emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,
-stack_size);
@@ -3046,6 +3128,7 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
first_off = stack_size;
stack_size -= 8;
nr_stack_slots++;
+ slot++;
}
} else {
/* Only copy the arguments on-stack to current
@@ -3054,16 +3137,24 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
*/
if (for_call_origin) {
nr_regs += arg_regs;
+ slot += arg_regs;
continue;
}
/* copy the arguments from regs into stack */
for (j = 0; j < arg_regs; j++) {
- emit_stx(prog, BPF_DW, BPF_REG_FP,
- nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,
- -stack_size);
+ u32 src = nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs;
+
+ if (aargs && (aargs->slots & BIT(slot))) {
+ emit_arena_arg_conv(prog, src,
+ aargs->nullable_slots & BIT(slot),
+ (u32)aargs->kern_vm_start);
+ src = BPF_REG_0;
+ }
+ emit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);
stack_size -= 8;
nr_regs++;
+ slot++;
}
}
}
@@ -3354,11 +3445,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
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];
+ struct bpf_tramp_arena_args aargs;
void *orig_call = func_addr;
int cookie_off, cookie_cnt;
u8 **branches = NULL;
u64 func_meta;
u8 *prog;
+ bool has_aargs;
bool save_ret;
/*
@@ -3369,6 +3462,8 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
WARN_ON_ONCE((flags & BPF_TRAMP_F_INDIRECT) &&
(flags & ~(BPF_TRAMP_F_INDIRECT | BPF_TRAMP_F_RET_FENTRY_RET)));
+ has_aargs = bpf_tramp_collect_arena_args(tnodes, flags, &aargs);
+
for (i = 0; i < m->nr_args; i++)
nr_regs += (m->arg_size[i] + 7) / 8 - 1;
@@ -3503,7 +3598,8 @@ 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);
+ save_args(m, &prog, regs_off, false, flags,
+ has_aargs ? &aargs : NULL);
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* arg1: mov rdi, im */
@@ -3545,7 +3641,7 @@ 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);
+ save_args(m, &prog, arg_stack_off, true, flags, NULL);
if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
/* Before calling the original function, load the
@@ -4046,6 +4142,11 @@ bool bpf_jit_supports_stack_args(void)
return true;
}
+bool bpf_jit_supports_arena_args(void)
+{
+ return true;
+}
+
void *bpf_arch_text_copy(void *dst, void *src, size_t len)
{
if (text_poke_copy(dst, src, len) == NULL)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 356884587ae11..2f606c3256806 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1213,6 +1213,12 @@ struct bpf_prog_offload {
/* The argument is signed. */
#define BTF_FMODEL_SIGNED_ARG BIT(1)
+/* The argument is an arena pointer. */
+#define BTF_FMODEL_ARENA_ARG BIT(2)
+
+/* The argument is nullable. */
+#define BTF_FMODEL_NULLABLE_ARG BIT(3)
+
struct btf_func_model {
u8 ret_size;
u8 ret_flags;
@@ -1286,6 +1292,20 @@ struct bpf_tramp_nodes {
int nr_nodes;
};
+/*
+ * Which 8-byte ctx slots of a struct_ops trampoline hold arena kernel
+ * pointers that save_args() converts to the arena pointer form,
+ * ctx[slot] = (u32)(kaddr - kern_vm_start).
+ */
+struct bpf_tramp_arena_args {
+ u32 slots;
+ u32 nullable_slots; /* subset of @slots where NULL is preserved */
+ u64 kern_vm_start;
+};
+
+bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
+ struct bpf_tramp_arena_args *aargs);
+
struct bpf_tramp_run_ctx;
/* Different use cases for BPF trampoline:
@@ -1687,6 +1707,11 @@ struct bpf_ctx_arg_aux {
u32 btf_id;
u32 ref_id;
bool refcounted;
+ /*
+ * We don't encode NULL-ness in the type for the program, but still need
+ * to distinguish it for the purposes of telling JITs what sequence to emit.
+ */
+ bool arena_nullable;
};
struct btf_mod_pair {
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 32d5297c557e5..36ce3403fe599 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1183,6 +1183,7 @@ bool bpf_jit_supports_subprog_tailcalls(void);
bool bpf_jit_supports_percpu_insn(void);
bool bpf_jit_supports_kfunc_call(void);
bool bpf_jit_supports_stack_args(void);
+bool bpf_jit_supports_arena_args(void);
bool bpf_jit_supports_far_kfunc_call(void);
bool bpf_jit_supports_exceptions(void);
bool bpf_jit_supports_ptr_xchg(void);
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 4e7a48c02be5c..827a6216a6201 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -147,6 +147,8 @@ void bpf_struct_ops_image_free(void *image)
#define MAYBE_NULL_SUFFIX "__nullable"
#define REFCOUNTED_SUFFIX "__ref"
+#define ARENA_SUFFIX "__arena"
+#define ARENA_MAYBE_NULL_SUFFIX "__arena_nullable"
/* Prepare argument info for every nullable argument of a member of a
* struct_ops type.
@@ -159,7 +161,7 @@ void bpf_struct_ops_image_free(void *image)
* to provide an array of struct bpf_ctx_arg_aux, which in turn provides
* the information that used by the verifier to check the arguments of the
* BPF struct_ops program assigned to the member. Here, we only care about
- * the arguments that are marked as __nullable.
+ * the arguments that are marked as __nullable, __ref or __arena.
*
* The array of struct bpf_ctx_arg_aux is eventually assigned to
* prog->aux->ctx_arg_info of BPF struct_ops programs and passed to the
@@ -175,7 +177,8 @@ static int prepare_arg_info(struct btf *btf,
struct bpf_struct_ops_arg_info *arg_info)
{
const struct btf_type *stub_func_proto, *pointed_type;
- bool is_nullable = false, is_refcounted = false;
+ bool is_nullable = false, is_refcounted = false, is_arena = false;
+ bool is_arena_nullable = false;
const struct btf_param *stub_args, *args;
struct bpf_ctx_arg_aux *info, *info_buf;
u32 nargs, arg_no, info_cnt = 0;
@@ -226,26 +229,30 @@ static int prepare_arg_info(struct btf *btf,
info = info_buf;
for (arg_no = 0; arg_no < nargs; arg_no++) {
/* Skip arguments that is not suffixed with
- * "__nullable or __ref".
+ * "__nullable", "__ref", "__arena" or "__arena_nullable".
*/
is_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
MAYBE_NULL_SUFFIX);
is_refcounted = btf_param_match_suffix(btf, &stub_args[arg_no],
REFCOUNTED_SUFFIX);
+ is_arena_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
+ ARENA_MAYBE_NULL_SUFFIX);
+ is_arena = btf_param_match_suffix(btf, &stub_args[arg_no], ARENA_SUFFIX);
if (is_nullable)
suffix = MAYBE_NULL_SUFFIX;
else if (is_refcounted)
suffix = REFCOUNTED_SUFFIX;
+ else if (is_arena_nullable)
+ suffix = ARENA_MAYBE_NULL_SUFFIX;
+ else if (is_arena)
+ suffix = ARENA_SUFFIX;
else
continue;
- /* Should be a pointer to struct */
- pointed_type = btf_type_resolve_ptr(btf,
- args[arg_no].type,
- &arg_btf_id);
- if (!pointed_type ||
- !btf_type_is_struct(pointed_type)) {
+ /* Should be a pointer to struct, or any pointer for __arena/__arena_nullable */
+ pointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, &arg_btf_id);
+ if (!pointed_type || (!is_arena && !is_arena_nullable && !btf_type_is_struct(pointed_type))) {
pr_warn("stub function %s has %s tagging to an unsupported type\n",
stub_fname, suffix);
goto err_out;
@@ -273,6 +280,15 @@ static int prepare_arg_info(struct btf *btf,
} else if (is_refcounted) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID;
info->refcounted = true;
+ } else if (is_arena || is_arena_nullable) {
+ /*
+ * Both types get PTR_TO_ARENA. In verifier state,
+ * PTR_TO_ARENA encompasses potential NULL values, but
+ * we do not force the program to check it, or maintain
+ * precision around it, since it has no safety implication.
+ */
+ info->reg_type = PTR_TO_ARENA;
+ info->arena_nullable = is_arena_nullable;
}
info++;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 5e8ac45ce56af..e31f56f401ebf 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -6963,15 +6963,19 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
return false;
}
- /* check for PTR_TO_RDONLY_BUF_OR_NULL or PTR_TO_RDWR_BUF_OR_NULL */
+ /*
+ * Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL or
+ * PTR_TO_ARENA (both nullable and non-nullable cases).
+ */
for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
u32 type, flag;
type = base_type(ctx_arg_info->reg_type);
flag = type_flag(ctx_arg_info->reg_type);
- if (ctx_arg_info->offset == off && type == PTR_TO_BUF &&
- (flag & PTR_MAYBE_NULL)) {
+ if (ctx_arg_info->offset == off &&
+ (type == PTR_TO_ARENA ||
+ (type == PTR_TO_BUF && (flag & PTR_MAYBE_NULL)))) {
info->reg_type = ctx_arg_info->reg_type;
return true;
}
@@ -7541,6 +7545,22 @@ static u8 __get_type_fmodel_flags(const struct btf_type *t)
return flags;
}
+static u8 __get_arg_fmodel_flags(const struct btf *btf,
+ const struct btf_param *arg,
+ const struct btf_type *t)
+{
+ u8 flags = __get_type_fmodel_flags(t);
+
+ if (btf_param_match_suffix(btf, arg, "__arena") ||
+ btf_param_match_suffix(btf, arg, "__arena_nullable"))
+ flags |= BTF_FMODEL_ARENA_ARG;
+ if (btf_param_match_suffix(btf, arg, "__nullable") ||
+ btf_param_match_suffix(btf, arg, "__arena_nullable"))
+ flags |= BTF_FMODEL_NULLABLE_ARG;
+
+ return flags;
+}
+
int btf_distill_func_proto(struct bpf_verifier_log *log,
struct btf *btf,
const struct btf_type *func,
@@ -7606,7 +7626,7 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,
return -EINVAL;
}
m->arg_size[i] = ret;
- m->arg_flags[i] = __get_type_fmodel_flags(t);
+ m->arg_flags[i] = __get_arg_fmodel_flags(btf, &args[i], t);
}
m->nr_args = nargs;
return 0;
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index e2076667b2451..a3e1fae32eace 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3308,6 +3308,11 @@ bool __weak bpf_jit_supports_stack_args(void)
return false;
}
+bool __weak bpf_jit_supports_arena_args(void)
+{
+ return false;
+}
+
bool __weak bpf_jit_supports_far_kfunc_call(void)
{
return false;
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index ed7999ad6c66c..d2d7a2904345d 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -529,6 +529,53 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
return tnodes;
}
+static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
+{
+ int i;
+
+ for (i = 0; i < prog->aux->ctx_arg_info_size; i++)
+ if (base_type(prog->aux->ctx_arg_info[i].reg_type) == PTR_TO_ARENA)
+ return true;
+ return false;
+}
+
+/*
+ * Collect which ctx slots of a struct_ops trampoline hold arena kernel
+ * pointers that save_args() must convert to the arena pointer form. Only
+ * the struct_ops indirect trampoline converts: it dispatches to a single
+ * prog whose arena is known at generation time. Return false when there
+ * is nothing to convert.
+ */
+bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
+ struct bpf_tramp_arena_args *aargs)
+{
+ const struct bpf_prog *prog;
+ int i;
+
+ memset(aargs, 0, sizeof(*aargs));
+
+ if (!(flags & BPF_TRAMP_F_INDIRECT) ||
+ tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
+ return false;
+
+ prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
+ for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
+ const struct bpf_ctx_arg_aux *info = &prog->aux->ctx_arg_info[i];
+
+ if (base_type(info->reg_type) != PTR_TO_ARENA)
+ continue;
+ aargs->slots |= BIT(info->offset / 8);
+ if (info->arena_nullable)
+ aargs->nullable_slots |= BIT(info->offset / 8);
+ }
+ if (!aargs->slots)
+ return false;
+ if (WARN_ON_ONCE(!prog->aux->arena))
+ return false;
+ aargs->kern_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena);
+ return true;
+}
+
static void bpf_tramp_image_free(struct bpf_tramp_image *im)
{
bpf_image_ksym_del(&im->ksym);
@@ -685,6 +732,7 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
u32 orig_flags = tr->flags;
bool ip_arg = false;
int err, total, size;
+ int kind, i;
tnodes = bpf_trampoline_get_progs(tr, &total, &ip_arg);
if (IS_ERR(tnodes))
@@ -695,6 +743,22 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
goto out;
}
+ /*
+ * Arena ctx args are converted only by the struct_ops indirect
+ * trampoline, which dispatches to a single known prog. Generic
+ * trampolines can mix progs with different arenas, so no conversion
+ * is possible here. Not reachable today: only struct_ops progs get
+ * arena ctx args and they never ride generic trampolines.
+ */
+ for (kind = 0; kind < BPF_TRAMP_MAX; kind++) {
+ for (i = 0; i < tnodes[kind].nr_nodes; i++) {
+ if (bpf_prog_has_arena_ctx_arg(tnodes[kind].nodes[i]->link->prog)) {
+ err = -ENOTSUPP;
+ goto out;
+ }
+ }
+ }
+
/* clear all bits except SHARE_IPMODIFY and TAIL_CALL_CTX */
tr->flags &= (BPF_TRAMP_F_SHARE_IPMODIFY | BPF_TRAMP_F_TAIL_CALL_CTX);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index b274004fccfd9..c9129330dede6 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10907,6 +10907,16 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
return btf_param_match_suffix(btf, arg, "__irq_flag");
}
+static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
+{
+ return btf_param_match_suffix(btf, arg, "__arena");
+}
+
+static bool is_kfunc_arg_arena_nullable(const struct btf *btf, const struct btf_param *arg)
+{
+ return btf_param_match_suffix(btf, arg, "__arena_nullable");
+}
+
static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
const struct btf_param *arg,
const char *name)
@@ -11127,6 +11137,7 @@ enum kfunc_ptr_arg_type {
KF_ARG_PTR_TO_IRQ_FLAG,
KF_ARG_PTR_TO_RES_SPIN_LOCK,
KF_ARG_PTR_TO_TASK_WORK,
+ KF_ARG_PTR_TO_ARENA,
};
enum special_kfunc_type {
@@ -11412,7 +11423,6 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
reg_arg_name(env, argno), btf_type_str(t));
return -EINVAL;
}
-
ref_t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
ref_tname = btf_name_by_offset(meta->btf, ref_t->name_off);
@@ -11461,6 +11471,9 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
arg_type = KF_ARG_PTR_TO_RES_SPIN_LOCK;
else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
arg_type = KF_ARG_PTR_TO_CALLBACK;
+ else if (is_kfunc_arg_arena(meta->btf, &args[arg]) ||
+ is_kfunc_arg_arena_nullable(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_ARENA;
else if (arg + 1 < nargs &&
(is_kfunc_arg_mem_size(meta->btf, &args[arg + 1]) ||
is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1]))) {
@@ -12154,6 +12167,31 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
t = btf_type_skip_modifiers(btf, args[i].type, NULL);
+ if (base_type(kf_arg_type) == KF_ARG_PTR_TO_ARENA) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support kfunc %s() with arena pointer arguments\n",
+ func_name);
+ return -ENOTSUPP;
+ }
+ if (!env->prog->aux->arena) {
+ verbose(env,
+ "%s arena pointer requires a program with an associated arena\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ if (regno < 0) {
+ verbose(env, "%s arena pointer cannot be a stack argument\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ if (reg->type != PTR_TO_ARENA && reg->type != SCALAR_VALUE) {
+ verbose(env, "%s is not a pointer to arena or scalar\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ continue;
+ }
+
if (btf_type_is_ptr(t) && (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
!is_kfunc_arg_nullable(meta->btf, &args[i])) {
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
@@ -18807,6 +18845,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
{
const struct btf_type *t, *func_proto;
const struct bpf_struct_ops_desc *st_ops_desc;
+ const struct bpf_struct_ops_arg_info *arg_info;
const struct bpf_struct_ops *st_ops;
const struct btf_member *member;
struct bpf_prog *prog = env->prog;
@@ -18885,10 +18924,23 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
return -EACCES;
}
- for (i = 0; i < st_ops_desc->arg_info[member_idx].cnt; i++) {
- if (st_ops_desc->arg_info[member_idx].info[i].refcounted) {
+ arg_info = &st_ops_desc->arg_info[member_idx];
+ for (i = 0; i < arg_info->cnt; i++) {
+ const struct bpf_ctx_arg_aux *info = &arg_info->info[i];
+
+ if (info->refcounted)
has_refcounted_arg = true;
- break;
+ if (base_type(info->reg_type) == PTR_TO_ARENA) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support arena arguments\n");
+ return -ENOTSUPP;
+ }
+ if (!prog->aux->arena) {
+ verbose(env,
+ "arena argument of %s requires a program with an associated arena\n",
+ mname);
+ return -EINVAL;
+ }
}
}
@@ -18909,8 +18961,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
prog->aux->attach_func_name = mname;
env->ops = st_ops->verifier_ops;
- return bpf_prog_ctx_arg_info_init(prog, st_ops_desc->arg_info[member_idx].info,
- st_ops_desc->arg_info[member_idx].cnt);
+ return bpf_prog_ctx_arg_info_init(prog, arg_info->info, arg_info->cnt);
}
#define SECURITY_PREFIX "security_"
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_kfunc.c b/tools/testing/selftests/bpf/prog_tests/arena_kfunc.c
new file mode 100644
index 0000000000000..9be19565f0d53
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_kfunc.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <test_progs.h>
+
+#include "arena_kfunc.skel.h"
+
+/*
+ * The test kfuncs live in bpf_testmod. Resolving kfuncs against module
+ * BTFs needs CAP_SYS_ADMIN, so run with full capabilities instead of
+ * through the verifier tests' capability-restricted runner.
+ */
+void test_arena_kfunc(void)
+{
+ RUN_TESTS(arena_kfunc);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_kfunc_jit.c b/tools/testing/selftests/bpf/prog_tests/arena_kfunc_jit.c
new file mode 100644
index 0000000000000..2359cde24c45d
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_kfunc_jit.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <test_progs.h>
+#include "arena_kfunc_jit.skel.h"
+
+/*
+ * Runs with full capabilities: resolving module kfunc ksyms requires
+ * CAP_SYS_ADMIN, which rules out the capability-restricted runner.
+ */
+void test_arena_kfunc_jit(void)
+{
+ RUN_TESTS(arena_kfunc_jit);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
new file mode 100644
index 0000000000000..b1e39defa38ae
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <test_progs.h>
+
+#include "struct_ops_arena.skel.h"
+#include "struct_ops_arena_fail.skel.h"
+
+#if defined(__x86_64__)
+/*
+ * Attach callbacks with __arena and __arena_nullable arguments and drive
+ * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.
+ */
+static void arena_arg(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ struct struct_ops_arena *skel;
+ struct bpf_link *link = NULL;
+ int err;
+
+ skel = struct_ops_arena__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_arena__open_and_load"))
+ return;
+
+ link = bpf_map__attach_struct_ops(skel->maps.testmod_arena);
+ if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
+ goto out;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.trigger),
+ &topts);
+ ASSERT_OK(err, "test_run");
+ ASSERT_EQ(topts.retval, 0, "trigger_retval");
+
+out:
+ bpf_link__destroy(link);
+ struct_ops_arena__destroy(skel);
+}
+
+/*
+ * A program with no arena cannot attach to a member with an __arena
+ * argument.
+ */
+static void arena_arg_fail(void)
+{
+ struct struct_ops_arena_fail *skel;
+
+ skel = struct_ops_arena_fail__open_and_load();
+ if (ASSERT_ERR_PTR(skel, "struct_ops_arena_fail__open_and_load"))
+ return;
+
+ struct_ops_arena_fail__destroy(skel);
+}
+#endif
+
+/*
+ * Serialized because it attaches the singleton bpf_testmod_ops3, which
+ * test_struct_ops_private_stack also attaches; registering it twice fails
+ * with -EEXIST.
+ */
+void serial_test_struct_ops_arena(void)
+{
+ /*
+ * Arena struct_ops arguments need JIT support, currently x86-64 only.
+ * Elsewhere verification fails with "JIT does not support arena
+ * arguments", so the programs cannot even load.
+ */
+#if defined(__x86_64__)
+ if (test__start_subtest("arena_arg"))
+ arena_arg();
+ if (test__start_subtest("arena_arg_fail"))
+ arena_arg_fail();
+#else
+ test__skip();
+#endif
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c
new file mode 100644
index 0000000000000..15d48151797da
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ /* page 0 hosts the arena global, page 1 is for allocations */
+ __uint(max_entries, 2);
+} arena SEC(".maps");
+
+/*
+ * Occupies page 0 so no allocation lands at arena offset 0, which the
+ * nullable tests below must be able to tell apart from NULL.
+ */
+u64 __arena arena_pad;
+
+/* volatile to force the scalar reloads below */
+volatile u64 stash;
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_forms(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ /* PTR_TO_ARENA argument */
+ *val = 41;
+ ret = bpf_kfunc_arena_arg_test((u64 *)val);
+ if (ret != 41 || *val != 42)
+ return 2;
+
+ /* the low 32 bits as a scalar */
+ stash = (u32)(u64)val;
+ ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+ if (ret != 42 || *val != 43)
+ return 3;
+
+ /* the full user address as a scalar */
+ stash = (u64)val;
+ bpf_addr_space_cast(stash, 1, 0);
+ ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+ if (ret != 43 || *val != 44)
+ return 4;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+/*
+ * Pin the rebase semantics using the capture kfuncs, which return the raw
+ * argument value: __arena rebases unconditionally, so zero low 32 bits
+ * arrive as the arena kernel base, while __arena_nullable turns them into
+ * NULL.
+ */
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_rebase(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 base, off;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ base = bpf_kfunc_arena_cap_test(NULL);
+ if (!base)
+ return 2;
+
+ /* only the low 32 bits contribute */
+ stash = 0xbadc0ffe00000000;
+ if (bpf_kfunc_arena_cap_test((u64 *)stash) != base)
+ return 3;
+
+ off = (u32)(u64)val;
+ if (bpf_kfunc_arena_cap_test((u64 *)val) != base + off)
+ return 4;
+
+ if (bpf_kfunc_arena_cap_nullable_test(NULL) != 0)
+ return 5;
+
+ stash = 0xbadc0ffe00000000;
+ if (bpf_kfunc_arena_cap_nullable_test((u64 *)stash) != 0)
+ return 6;
+
+ if (bpf_kfunc_arena_cap_nullable_test((u64 *)val) != base + off)
+ return 7;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_nullable(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ *val = 41;
+ ret = bpf_kfunc_arena_nullable_arg_test((u64 *)val);
+ if (ret != 41 || *val != 42)
+ return 2;
+
+ if (bpf_kfunc_arena_nullable_arg_test(NULL) != 0xdeadbeef)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_args5(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 1;
+ val[1] = 2;
+ val[2] = 4;
+ val[3] = 8;
+ val[4] = 16;
+
+ if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3],
+ (u64 *)&val[4]) != 31)
+ return 2;
+ if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3], NULL) != 15)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_mixed(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 7;
+ val[1] = 5;
+
+ if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], NULL) != 7)
+ return 2;
+
+ if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], (u64 *)&val[1]) != 12)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+/* kernel-side faults on unpopulated pages recover via the scratch page */
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_unpopulated(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ stash = (u64)val + PAGE_SIZE;
+ bpf_kfunc_arena_arg_test((u64 *)stash);
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__failure __msg("arena pointer requires a program with an associated arena")
+int arena_arg_no_arena(void *ctx)
+{
+ bpf_kfunc_arena_arg_test((u64 *)1);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__failure __msg("is not a pointer to arena or scalar")
+int arena_arg_bad_reg(void *ctx)
+{
+ u64 buf = 0;
+
+ /* use the arena so the program passes the arena presence check */
+ bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_arg_test(&buf);
+ return 0;
+}
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+SEC("syscall")
+__arch_x86_64
+__failure __msg("arena pointer cannot be a stack argument")
+int arena_arg_stack(void *ctx)
+{
+ bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_stack_arg_test(1, 2, 3, 4, 5, (u64 *)1);
+ return 0;
+}
+#else
+SEC("syscall")
+__arch_x86_64
+__description("arena_arg_stack: not supported, dummy test")
+__success
+int arena_arg_stack(void *ctx)
+{
+ return 0;
+}
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
new file mode 100644
index 0000000000000..84747611725f6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+/*
+ * Verify the JIT-emitted rebase sequences for __arena and __arena_nullable
+ * kfunc arguments. The capture kfuncs take the argument without
+ * dereferencing it, so these tests pin only the emitted code.
+ */
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 1);
+} arena SEC(".maps");
+
+/* volatile to force the scalar reloads below */
+volatile u64 stash;
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" addq %r12, %rdi")
+__jited("...")
+__jited(" callq {{.*}}")
+__success
+int arena_arg_jit_rebase(void *ctx)
+{
+ stash = (u64)bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_cap_test((u64 *)stash);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" testl %edi, %edi")
+__jited(" je {{.*}}")
+__jited(" addq %r12, %rdi")
+__success
+int arena_arg_jit_nullable(void *ctx)
+{
+ stash = (u64)bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_cap_nullable_test((u64 *)stash);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" addq %r12, %rdi")
+__jited(" movl %esi, %esi")
+__jited(" addq %r12, %rsi")
+__jited(" movl %edx, %edx")
+__jited(" addq %r12, %rdx")
+__jited(" movl %ecx, %ecx")
+__jited(" addq %r12, %rcx")
+__jited(" movl %r8d, %r8d")
+__jited(" testl %r8d, %r8d")
+__jited(" je {{.*}}")
+__jited(" addq %r12, %r8")
+__success
+int arena_arg_jit_args5(void *ctx)
+{
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 1;
+ val[1] = 2;
+ val[2] = 4;
+ val[3] = 8;
+ val[4] = 16;
+
+ bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3],
+ (u64 *)&val[4]);
+ return 0;
+}
+
+#endif /* __BPF_FEATURE_ADDR_SPACE_CAST */
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
new file mode 100644
index 0000000000000..ba04c73d8d967
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ /* page 0 hosts the arena globals, page 1 is for allocations */
+ __uint(max_entries, 2);
+} arena SEC(".maps");
+
+/* also associates the callbacks with the arena */
+u64 __arena arena_touch;
+/* raw value of the last __arena ctx argument, captured by test_arena_cb */
+u64 __arena cb_ptr_val;
+
+SEC("struct_ops/test_arena")
+int test_arena_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[0];
+
+ arena_touch++;
+ cb_ptr_val = ctx[0];
+ *ptr += 1;
+ return 0;
+}
+
+SEC("struct_ops/test_arena_nullable")
+int test_arena_nullable_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[0];
+
+ arena_touch++;
+ if (!ptr)
+ return 0xbee;
+ *ptr += 1;
+ return 0;
+}
+
+SEC("struct_ops/test_arena_stack")
+int test_arena_stack_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[8];
+
+ arena_touch++;
+ /* pin the slot layout: the leading args fill ctx[0]..ctx[7] */
+ if (ctx[0] != 1 || ctx[7] != 8)
+ return 0xbad;
+ *ptr += 1;
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_arena = {
+ .test_arena = (void *)test_arena_cb,
+ .test_arena_nullable = (void *)test_arena_nullable_cb,
+ .test_arena_stack = (void *)test_arena_stack_cb,
+};
+
+SEC("syscall")
+int trigger(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ int ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ *val = 41;
+ ret = bpf_testmod_ops3_call_test_arena((u64 *)val);
+ if (ret)
+ return 2;
+ if (*val != 42)
+ return 3;
+
+ /*
+ * The callback must have seen exactly (u32)(kaddr - kern_vm_start),
+ * which is the arena offset of val with the upper 32 bits clear.
+ */
+ if (cb_ptr_val != (u32)(u64)val)
+ return 4;
+
+ ret = bpf_testmod_ops3_call_test_arena_nullable((u64 *)val);
+ if (ret)
+ return 5;
+ if (*val != 43)
+ return 6;
+
+ /* NULL survives the nullable kfunc and the trampoline as NULL */
+ ret = bpf_testmod_ops3_call_test_arena_nullable(NULL);
+ if (ret != 0xbee)
+ return 7;
+
+ /* the arena pointer is stack-passed into the trampoline here */
+ ret = bpf_testmod_ops3_call_test_arena_stack((u64 *)val);
+ if (ret)
+ return 8;
+ if (*val != 44)
+ return 9;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
new file mode 100644
index 0000000000000..1c0ec727d6374
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* No arena in the program: attaching to test_arena must be rejected. */
+SEC("struct_ops/test_arena")
+int test_arena_no_arena(unsigned long long *ctx)
+{
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_arena_fail = {
+ .test_arena = (void *)test_arena_no_arena,
+};
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index eb0f9b5e18d85..fdeaa56356b52 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -237,6 +237,56 @@ __bpf_kfunc void bpf_kfunc_common_test(void)
{
}
+__bpf_kfunc u64 bpf_kfunc_arena_arg_test(u64 *val__arena)
+{
+ u64 old;
+
+ old = *val__arena;
+ *val__arena = old + 1;
+ return old;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_nullable_arg_test(u64 *val__arena_nullable)
+{
+ u64 old;
+
+ if (!val__arena_nullable)
+ return 0xdeadbeef;
+
+ old = *val__arena_nullable;
+ *val__arena_nullable = old + 1;
+ return old;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_cap_test(u64 *val__arena)
+{
+ return (u64)val__arena;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_cap_nullable_test(u64 *val__arena_nullable)
+{
+ return (u64)val__arena_nullable;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_args5_test(u64 *a__arena, u64 *b__arena,
+ u64 *c__arena, u64 *d__arena,
+ u64 *e__arena_nullable)
+{
+ return *a__arena + *b__arena + *c__arena + *d__arena +
+ (e__arena_nullable ? *e__arena_nullable : 0);
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_stack_arg_test(u64 a, u64 b, u64 c, u64 d, u64 e,
+ u64 *f__arena)
+{
+ return a + b + c + d + e + *f__arena;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_mixed_test(u64 *a__arena, u64 *b__arena_nullable)
+{
+ return *a__arena + (b__arena_nullable ? *b__arena_nullable : 0);
+}
+
__bpf_kfunc void bpf_kfunc_dynptr_test(struct bpf_dynptr *ptr,
struct bpf_dynptr *ptr__nullable)
{
@@ -347,9 +397,29 @@ static int bpf_testmod_test_4(void)
return 0;
}
+static int bpf_testmod_ops3__test_arena(u64 *ptr__arena)
+{
+ return 0;
+}
+
+static int bpf_testmod_ops3__test_arena_nullable(u64 *ptr__arena_nullable)
+{
+ return 0;
+}
+
+static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,
+ u64 e, u64 f, u64 g, u64 h,
+ u64 *ptr__arena)
+{
+ return 0;
+}
+
static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {
.test_1 = bpf_testmod_test_3,
.test_2 = bpf_testmod_test_4,
+ .test_arena = bpf_testmod_ops3__test_arena,
+ .test_arena_nullable = bpf_testmod_ops3__test_arena_nullable,
+ .test_arena_stack = bpf_testmod_ops3__test_arena_stack,
};
static void bpf_testmod_test_struct_ops3(void)
@@ -368,6 +438,21 @@ __bpf_kfunc void bpf_testmod_ops3_call_test_2(void)
st_ops3->test_2();
}
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena(u64 *ptr__arena)
+{
+ return st_ops3->test_arena(ptr__arena);
+}
+
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena_nullable)
+{
+ return st_ops3->test_arena_nullable(ptr__arena_nullable);
+}
+
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_stack(u64 *ptr__arena)
+{
+ return st_ops3->test_arena_stack(1, 2, 3, 4, 5, 6, 7, 8, ptr__arena);
+}
+
struct bpf_testmod_btf_type_tag_1 {
int a;
};
@@ -755,6 +840,13 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)
BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)
BTF_ID_FLAGS(func, bpf_kfunc_common_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_nullable_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_nullable_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_args5_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_stack_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_mixed_test)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)
BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)
@@ -770,6 +862,9 @@ BTF_ID_FLAGS(func, bpf_testmod_ctx_create, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_testmod_ctx_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)
BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
index 863fd10f16199..33f2af5b70857 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -106,6 +106,12 @@ struct bpf_testmod_ops2 {
struct bpf_testmod_ops3 {
int (*test_1)(void);
int (*test_2)(void);
+ /* Used to test arena pointer arguments. */
+ int (*test_arena)(u64 *ptr);
+ int (*test_arena_nullable)(u64 *ptr);
+ /* enough leading args to force @ptr onto the stack on x86 and arm64 */
+ int (*test_arena_stack)(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,
+ u64 g, u64 h, u64 *ptr);
};
struct st_ops_args {
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 c36bb911defa7..1a72d0fda53c4 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -98,6 +98,16 @@ void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym;
void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym;
void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym;
+__u64 bpf_kfunc_arena_arg_test(__u64 *val__arena) __ksym;
+__u64 bpf_kfunc_arena_nullable_arg_test(__u64 *val__arena_nullable) __ksym;
+__u64 bpf_kfunc_arena_cap_test(__u64 *val__arena) __ksym;
+__u64 bpf_kfunc_arena_cap_nullable_test(__u64 *val__arena_nullable) __ksym;
+__u64 bpf_kfunc_arena_args5_test(__u64 *a__arena, __u64 *b__arena,
+ __u64 *c__arena, __u64 *d__arena,
+ __u64 *e__arena_nullable) __ksym;
+__u64 bpf_kfunc_arena_stack_arg_test(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
+ __u64 *f__arena) __ksym;
+__u64 bpf_kfunc_arena_mixed_test(__u64 *a__arena, __u64 *b__arena_nullable) __ksym;
int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym;
int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;
int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;
@@ -111,6 +121,9 @@ u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym;
#endif
void bpf_testmod_test_mod_kfunc(int i) __ksym;
+int bpf_testmod_ops3_call_test_arena(__u64 *ptr__arena) __ksym;
+int bpf_testmod_ops3_call_test_arena_nullable(__u64 *ptr__arena_nullable) __ksym;
+int bpf_testmod_ops3_call_test_arena_stack(__u64 *ptr__arena) __ksym;
__u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,
__u32 c, __u64 d) __ksym;
|
| 3/2 | 2026/08/03 16:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23049 output: 29 thoughts: 717 Thoughts: **BPF Arena Pointer Implementation - Initial Assessment** Okay, so I've got this patch here introducing `__arena` and `__arena_nullable` annotations for BPF kfunc and struct_ops arguments. Seems like a functional change – impacting the BPF verifier, JIT compiler, and trampoline logic. This is definitely something that warrants a thorough fuzzing campaign, given the potential for security implications. The patch touches a bunch of files: `Documentation/bpf/kfuncs.rst`, some JIT compilation code (`arch/x86/net/bpf_jit_comp.c`), various include files (`include/linux/bpf.h`, `include/linux/filter.h`), and a bunch of core BPF code (`kernel/bpf/bpf_struct_ops.c`, `kernel/bpf/btf.c`, `kernel/bpf/core.c`, `kernel/bpf/trampoline.c`, `kernel/bpf/verifier.c`), and some selftests. Alright, let's identify the key areas of interest, the focus symbols. Looks like it's introducing these static functions: `emit_kfunc_arena_args`, `emit_arena_arg_conv`, `save_args`, and modifying functions like `__arch_prepare_bpf_trampoline`, `bpf_jit_supports_arena_args`, `prepare_arg_info`, as well as `btf_ctx_access`, `__get_arg_fmodel_flags`, `btf_distill_func_proto`, `bpf_tramp_collect_arena_args`, `bpf_trampoline_update`, `get_kfunc_arg_type`, `check_kfunc_args`, and `check_struct_ops_btf_id`. I need to narrow this down to the most critical symbols. I'm thinking I should start with `__arch_prepare_bpf_trampoline`, `bpf_tramp_collect_arena_args`, `bpf_trampoline_update`, `btf_ctx_access`, `btf_distill_func_proto`, `get_kfunc_arg_type`, `check_kfunc_args`, and `check_struct_ops_btf_id`. Let me re-evaluate, since I suspect some of the focus symbols are only called in a narrow set of circumstances, such as when preparing a trampoline, which I think means I can push `__arch_prepare_bpf_trampoline` to the lower priority list. `bpf_tramp_collect_arena_args`, on the other hand, seems like a good target. `btf_ctx_access`, `btf_distill_func_proto`, `get_kfunc_arg_type`, `check_kfunc_args`, and `check_struct_ops_btf_id` are all called during verification, which means they're also interesting. Now, before I dig too deep, let's consider any configuration dependencies. Since this is in the BPF core, `CONFIG_BPF_SYSCALL` is a given. I should probably grep for any explicit `CONFIG_BPF_ARENA` options or similar, just in case there's a configuration gate. I will confirm to avoid wasting time. |
| 4/2 | 2026/08/03 16:13 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_BPF",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/Kconfig=58=config BPF_JIT_ALWAYS_ON\n--\nkernel/bpf/Kconfig-64-\nkernel/bpf/Kconfig:65:\t When CONFIG_BPF_JIT_ALWAYS_ON is enabled, /proc/sys/net/core/bpf_jit_enable\nkernel/bpf/Kconfig-66-\t is permanently set to 1 and setting any other value than that will\n--\nkernel/bpf/Makefile=2=obj-y := core.o\nkernel/bpf/Makefile:3:ifneq ($(CONFIG_BPF_JIT_ALWAYS_ON),y)\nkernel/bpf/Makefile-4-# ___bpf_prog_run() needs GCSE disabled on x86; see 3193c0836f203 for details\n--\nkernel/bpf/Makefile=7=CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)\nkernel/bpf/Makefile-8-\nkernel/bpf/Makefile:9:obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o\nkernel/bpf/Makefile:10:obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o\nkernel/bpf/Makefile:11:obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o\nkernel/bpf/Makefile:12:obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o\nkernel/bpf/Makefile:13:obj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o\nkernel/bpf/Makefile:14:obj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o backtrack.o check_btf.o\nkernel/bpf/Makefile:15:obj-${CONFIG_BPF_LSM}\t += bpf_inode_storage.o\nkernel/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\nkernel/bpf/Makefile-19-ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\nkernel/bpf/Makefile:20:obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\nkernel/bpf/Makefile-21-endif\nkernel/bpf/Makefile:22:obj-$(CONFIG_BPF_JIT) += dispatcher.o\nkernel/bpf/Makefile-23-ifeq ($(CONFIG_NET),y)\nkernel/bpf/Makefile:24:obj-$(CONFIG_BPF_SYSCALL) += devmap.o\nkernel/bpf/Makefile:25:obj-$(CONFIG_BPF_SYSCALL) += cpumap.o\nkernel/bpf/Makefile:26:obj-$(CONFIG_BPF_SYSCALL) += offload.o\nkernel/bpf/Makefile:27:obj-$(CONFIG_BPF_SYSCALL) += net_namespace.o\nkernel/bpf/Makefile:28:obj-$(CONFIG_BPF_SYSCALL) += tcx.o\nkernel/bpf/Makefile-29-endif\nkernel/bpf/Makefile=30=ifeq ($(CONFIG_PERF_EVENTS),y)\nkernel/bpf/Makefile:31:obj-$(CONFIG_BPF_SYSCALL) += stackmap.o\nkernel/bpf/Makefile-32-endif\nkernel/bpf/Makefile=33=ifeq ($(CONFIG_CGROUPS),y)\nkernel/bpf/Makefile:34:obj-$(CONFIG_BPF_SYSCALL) += cgroup_iter.o bpf_cgrp_storage.o\nkernel/bpf/Makefile-35-endif\n--\nkernel/bpf/Makefile=37=ifeq ($(CONFIG_INET),y)\nkernel/bpf/Makefile:38:obj-$(CONFIG_BPF_SYSCALL) += reuseport_array.o\nkernel/bpf/Makefile-39-endif\n--\nkernel/bpf/Makefile=42=endif\nkernel/bpf/Makefile:43:ifeq ($(CONFIG_BPF_JIT),y)\nkernel/bpf/Makefile:44:obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o\nkernel/bpf/Makefile:45:obj-$(CONFIG_BPF_SYSCALL) += cpumask.o\nkernel/bpf/Makefile-46-# bpf_lsm_proto.o must precede bpf_lsm.o. The current pahole logic\n--\nkernel/bpf/Makefile-55-# semantics within pahole are revisited accordingly.\nkernel/bpf/Makefile:56:obj-${CONFIG_BPF_LSM} += bpf_lsm_proto.o bpf_lsm.o\nkernel/bpf/Makefile-57-endif\nkernel/bpf/Makefile=58=ifneq ($(CONFIG_CRYPTO),)\nkernel/bpf/Makefile:59:obj-$(CONFIG_BPF_SYSCALL) += crypto.o\nkernel/bpf/Makefile-60-endif\nkernel/bpf/Makefile:61:obj-$(CONFIG_BPF_PRELOAD) += preload/\nkernel/bpf/Makefile-62-\nkernel/bpf/Makefile:63:obj-$(CONFIG_BPF_SYSCALL) += relo_core.o\nkernel/bpf/Makefile:64:obj-$(CONFIG_BPF_SYSCALL) += btf_iter.o\nkernel/bpf/Makefile:65:obj-$(CONFIG_BPF_SYSCALL) += btf_relocate.o\nkernel/bpf/Makefile:66:obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o\nkernel/bpf/Makefile-67-ifeq ($(CONFIG_DMA_SHARED_BUFFER),y)\nkernel/bpf/Makefile:68:obj-$(CONFIG_BPF_SYSCALL) += dmabuf_iter.o\nkernel/bpf/Makefile-69-endif\n--\nkernel/bpf/btf.c=9834=bool btf_type_ids_nocast_alias(struct bpf_verifier_log *log,\n--\nkernel/bpf/btf.c-9888-\nkernel/bpf/btf.c:9889:#ifdef CONFIG_BPF_JIT\nkernel/bpf/btf.c-9890-static int\n--\nkernel/bpf/cgroup.c=52=void __init cgroup_bpf_lifetime_notifier_init(void)\n--\nkernel/bpf/cgroup.c-57-\nkernel/bpf/cgroup.c:58:#ifdef CONFIG_BPF_LSM\nkernel/bpf/cgroup.c-59-struct cgroup_lsm_atype {\n--\nkernel/bpf/cgroup.c=162=unsigned int __cgroup_bpf_run_lsm_current(const void *ctx,\n--\nkernel/bpf/cgroup.c-180-\nkernel/bpf/cgroup.c:181:#ifdef CONFIG_BPF_LSM\nkernel/bpf/cgroup.c-182-static enum cgroup_bpf_attach_type\n--\nkernel/bpf/cgroup.c=234=bpf_cgroup_atype_find(enum bpf_attach_type attach_type, u32 attach_btf_id)\n--\nkernel/bpf/cgroup.c-239-}\nkernel/bpf/cgroup.c:240:#endif /* CONFIG_BPF_LSM */\nkernel/bpf/cgroup.c-241-\n--\nkernel/bpf/core.c=100=struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flags)\n--\nkernel/bpf/core.c-128-\tfp-\u003ejit_requested = ebpf_jit_enabled();\nkernel/bpf/core.c:129:\tfp-\u003ejit_required = IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON);\nkernel/bpf/core.c-130-\tfp-\u003eblinding_requested = bpf_jit_blinding_enabled(fp);\n--\nkernel/bpf/core.c-143-\nkernel/bpf/core.c:144:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-145-\tbpf_prog_stream_init(fp);\n--\nkernel/bpf/core.c=541=void bpf_prog_kallsyms_del_all(struct bpf_prog *fp)\n--\nkernel/bpf/core.c-546-\nkernel/bpf/core.c:547:#ifdef CONFIG_BPF_JIT\nkernel/bpf/core.c-548-/* All BPF JIT sysctl knobs here. */\nkernel/bpf/core.c:549:int bpf_jit_enable __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);\nkernel/bpf/core.c:550:int bpf_jit_kallsyms __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);\nkernel/bpf/core.c-551-int bpf_jit_harden __read_mostly;\n--\nkernel/bpf/core.c=1655=u16 bpf_out_stack_arg_cnt(const struct bpf_verifier_env *env, const struct bpf_prog *prog)\n--\nkernel/bpf/core.c-1663-}\nkernel/bpf/core.c:1664:#endif /* CONFIG_BPF_JIT */\nkernel/bpf/core.c-1665-\n--\nkernel/bpf/core.c=1823=bool bpf_opcode_in_insntable(u8 code)\n--\nkernel/bpf/core.c-1845-\nkernel/bpf/core.c:1846:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\nkernel/bpf/core.c-1847-/* Absolute value of s32 without undefined behavior for S32_MIN */\n--\nkernel/bpf/core.c=2473=EVAL4(PROG_NAME_LIST, 416, 448, 480, 512)\n--\nkernel/bpf/core.c-2476-\nkernel/bpf/core.c:2477:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-2478-int bpf_patch_call_args(struct bpf_insn *insn, u32 stack_depth)\n--\nkernel/bpf/core.c=2610=static bool bpf_prog_select_interpreter(struct bpf_prog *fp)\n--\nkernel/bpf/core.c-2612-\tbool select_interpreter = false;\nkernel/bpf/core.c:2613:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\nkernel/bpf/core.c-2614-\tu32 stack_depth = max_t(u32, fp-\u003eaux-\u003estack_depth, 1);\n--\nkernel/bpf/core.c=2633=static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\nkernel/bpf/core.c-2634-{\nkernel/bpf/core.c:2635:#ifdef CONFIG_BPF_JIT\nkernel/bpf/core.c-2636-\tstruct bpf_prog *orig_prog;\n--\nkernel/bpf/core.c=3057=void __bpf_free_used_btfs(struct btf_mod_pair *used_btfs, u32 len)\nkernel/bpf/core.c-3058-{\nkernel/bpf/core.c:3059:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3060-\tstruct btf_mod_pair *btf_mod;\n--\nkernel/bpf/core.c=3078=static void bpf_prog_free_deferred(struct work_struct *work)\n--\nkernel/bpf/core.c-3083-\taux = container_of(work, struct bpf_prog_aux, work);\nkernel/bpf/core.c:3084:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3085-\tbpf_free_kfunc_btf_tab(aux-\u003ekfunc_btf_tab);\n--\nkernel/bpf/core.c=3405=static noinline void bpf_prog_report_may_goto_violation(void)\nkernel/bpf/core.c-3406-{\nkernel/bpf/core.c:3407:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3408-\tstruct bpf_stream_stage ss;\n--\nkernel/bpf/core.c=3445=__weak u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)\n--\nkernel/bpf/core.c-3449-\nkernel/bpf/core.c:3450:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3451-__weak bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write,\n--\nkernel/bpf/core.c=3476=EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);\nkernel/bpf/core.c-3477-\nkernel/bpf/core.c:3478:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3479-\n--\nkernel/bpf/fixups.c=127=static int add_kfunc_in_insns(struct bpf_verifier_env *env,\n--\nkernel/bpf/fixups.c-141-\nkernel/bpf/fixups.c:142:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\nkernel/bpf/fixups.c-143-static int get_callee_stack_depth(struct bpf_verifier_env *env,\n--\nkernel/bpf/fixups.c=1376=int bpf_fixup_call_args(struct bpf_verifier_env *env)\nkernel/bpf/fixups.c-1377-{\nkernel/bpf/fixups.c:1378:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\nkernel/bpf/fixups.c-1379-\tstruct bpf_prog *prog = env-\u003eprog;\n--\nkernel/bpf/fixups.c-1404-\t}\nkernel/bpf/fixups.c:1405:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\nkernel/bpf/fixups.c-1406-\tif (prog-\u003ejit_required) {\n--\nkernel/bpf/helpers.c=276=const struct bpf_func_proto bpf_get_current_comm_proto = {\n--\nkernel/bpf/helpers.c-283-\nkernel/bpf/helpers.c:284:#if defined(CONFIG_QUEUED_SPINLOCKS) || defined(CONFIG_BPF_ARCH_SPINLOCK)\nkernel/bpf/helpers.c-285-\n--\nkernel/bpf/helpers.c=4850=BTF_ID_FLAGS(func, bpf_throw)\nkernel/bpf/helpers.c:4851:#ifdef CONFIG_BPF_EVENTS\nkernel/bpf/helpers.c-4852-BTF_ID_FLAGS(func, bpf_send_signal_task)\n--\nkernel/bpf/helpers.c=4930=BTF_ID_FLAGS(func, bpf_local_irq_restore)\nkernel/bpf/helpers.c:4931:#ifdef CONFIG_BPF_EVENTS\nkernel/bpf/helpers.c-4932-BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)\n--\nkernel/bpf/helpers.c=4961=BTF_ID_FLAGS(func, bpf_strncasestr);\nkernel/bpf/helpers.c:4962:#if defined(CONFIG_BPF_LSM) \u0026\u0026 defined(CONFIG_CGROUPS)\nkernel/bpf/helpers.c-4963-BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU)\n--\nkernel/bpf/preload/Makefile=3=LIBBPF_INCLUDE = $(srctree)/tools/lib\nkernel/bpf/preload/Makefile-4-\nkernel/bpf/preload/Makefile:5:obj-$(CONFIG_BPF_PRELOAD_UMD) += bpf_preload.o\nkernel/bpf/preload/Makefile-6-CFLAGS_bpf_preload_kern.o += -I$(LIBBPF_INCLUDE)\n--\nkernel/bpf/syscall.c=68=int sysctl_unprivileged_bpf_disabled __read_mostly =\nkernel/bpf/syscall.c:69:\tIS_BUILTIN(CONFIG_BPF_UNPRIV_DEFAULT_OFF) ? 2 : 0;\nkernel/bpf/syscall.c-70-\n--\nkernel/bpf/syscall.c=6518=int kern_sys_bpf(int cmd, union bpf_attr *attr, unsigned int size)\n--\nkernel/bpf/syscall.c-6523-\tswitch (cmd) {\nkernel/bpf/syscall.c:6524:#ifdef CONFIG_BPF_JIT /* __bpf_prog_enter_sleepable used by trampoline and JIT */\nkernel/bpf/syscall.c-6525-\tcase BPF_PROG_TEST_RUN:\n--\nkernel/bpf/trampoline.c=1054=int bpf_trampoline_unlink_prog(struct bpf_tramp_node *node,\n--\nkernel/bpf/trampoline.c-1065-\nkernel/bpf/trampoline.c:1066:#if defined(CONFIG_CGROUP_BPF) \u0026\u0026 defined(CONFIG_BPF_LSM)\nkernel/bpf/trampoline.c-1067-static void bpf_shim_tramp_link_release(struct bpf_link *link)\n--\nkernel/bpf/trampoline.c=1541=int __weak arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,\n--\nkernel/bpf/trampoline.c-1548- defined(CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS) \u0026\u0026 \\\nkernel/bpf/trampoline.c:1549: defined(CONFIG_BPF_SYSCALL)\nkernel/bpf/trampoline.c-1550-\n--\nkernel/bpf/trampoline.c=1786=int bpf_trampoline_multi_detach(struct bpf_prog *prog, struct bpf_tracing_multi_link *link)\n--\nkernel/bpf/trampoline.c-1823-\t CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS \u0026\u0026\nkernel/bpf/trampoline.c:1824:\t CONFIG_BPF_SYSCALL */\nkernel/bpf/trampoline.c-1825-\n--\nkernel/bpf/verifier.c=4464=BTF_ID(struct, cgroup)\nkernel/bpf/verifier.c-4465-#endif\nkernel/bpf/verifier.c:4466:#ifdef CONFIG_BPF_JIT\nkernel/bpf/verifier.c-4467-BTF_ID(struct, bpf_cpumask)\n--\nkernel/bpf/verifier.c=11272=BTF_ID_UNUSED\nkernel/bpf/verifier.c-11273-#endif\nkernel/bpf/verifier.c:11274:#ifdef CONFIG_BPF_EVENTS\nkernel/bpf/verifier.c-11275-BTF_ID(func, bpf_session_cookie)\n--\nkernel/bpf/verifier.c=11284=BTF_ID(func, bpf_iter_num_destroy)\nkernel/bpf/verifier.c:11285:#ifdef CONFIG_BPF_LSM\nkernel/bpf/verifier.c-11286-BTF_ID(func, bpf_set_dentry_xattr)\n--\nkernel/bpf/verifier.c=11303=BTF_ID(func, bpf_arena_reserve_pages)\nkernel/bpf/verifier.c:11304:#ifdef CONFIG_BPF_EVENTS\nkernel/bpf/verifier.c-11305-BTF_ID(func, bpf_session_is_return)\n"
} |
| 5/2 | 2026/08/03 16:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29938 output: 31 thoughts: 287 Thoughts: **My Assessment of the Arena KFunc Patch** Okay, so this patch introduces `__arena` and `__arena_nullable` support, which is good. I need to get a handle on the testing strategy they've used. I see the tests are in `tools/testing/selftests/bpf/prog_tests/arena_kfunc.c`, `tools/testing/selftests/bpf/prog_tests/arena_kfunc_jit.c`, and `tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c`. Good, they're comprehensive. They are relying on `bpf_testmod` as expected. I have to make sure the relevant build configurations are in place to run these tests. I already know `CONFIG_BPF_SYSCALL` and `CONFIG_BPF_JIT` are required. I vaguely recall there being edge cases where `CONFIG_BPF_EVENTS` might be needed, but `CONFIG_BPF_SYSCALL` is the critical one. I need `CONFIG_BPF_JIT` for `bpf_jit_supports_arena_args`, that's obvious. And `CONFIG_BPF_SYSCALL` is needed for `arena.o`. Now, I should quickly check if `bpf_testmod` itself has any specific dependencies or configurations that could trip me up. I'll take a quick look to be sure. But wait, `CONFIG_BPF_SYSCALL` and `CONFIG_BPF_JIT` seem pretty standard; I'll check `kernel/bpf/Kconfig` for the specific options. Let's make sure. |
| 6/2 | 2026/08/03 16:13 | tool | grepper |
0mArgs:{
"Expression": "config BPF_",
"PathPrefix": "kernel/bpf/Kconfig"
}Results: {
"Output": "kernel/bpf/Kconfig=25=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-26-\nkernel/bpf/Kconfig:27:config BPF_SYSCALL\nkernel/bpf/Kconfig-28-\tbool \"Enable bpf() system call\"\n--\nkernel/bpf/Kconfig-41-\nkernel/bpf/Kconfig:42:config BPF_JIT\nkernel/bpf/Kconfig-43-\tbool \"Enable BPF Just In Time compiler\"\n--\nkernel/bpf/Kconfig-57-\nkernel/bpf/Kconfig:58:config BPF_JIT_ALWAYS_ON\nkernel/bpf/Kconfig-59-\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n--\nkernel/bpf/Kconfig-68-\nkernel/bpf/Kconfig:69:config BPF_JIT_DEFAULT_ON\nkernel/bpf/Kconfig-70-\tdef_bool ARCH_WANT_DEFAULT_BPF_JIT || BPF_JIT_ALWAYS_ON\n--\nkernel/bpf/Kconfig-72-\nkernel/bpf/Kconfig:73:config BPF_UNPRIV_DEFAULT_OFF\nkernel/bpf/Kconfig-74-\tbool \"Disable unprivileged BPF by default\"\n--\nkernel/bpf/Kconfig=90=source \"kernel/bpf/preload/Kconfig\"\nkernel/bpf/Kconfig-91-\nkernel/bpf/Kconfig:92:config BPF_LSM\nkernel/bpf/Kconfig-93-\tbool \"Enable BPF LSM Instrumentation\"\n"
} |
| 7/2 | 2026/08/03 16:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31038 output: 36 thoughts: 400 Thoughts: **Analyzing the Impact of this Patch** Okay, so this patch introduces a handful of new functions: `bpf_tramp_collect_arena_args`, `emit_kfunc_arena_args`, `emit_arena_arg_conv`, `bpf_jit_supports_arena_args`, and `__get_arg_fmodel_flags`. My immediate focus is on how these new components are integrated, specifically in relation to the core BPF infrastructure. The presence of functions like `arena_args` suggests changes to memory management within the JIT environment, which is always interesting. I need to confirm the scope of these changes, starting with verifying visibility of the symbols. First, let's confirm the locations of the "focus symbols" the patch highlights: `bpf_tramp_collect_arena_args`, `bpf_trampoline_update`, `btf_distill_func_proto`, `btf_ctx_access`, `get_kfunc_arg_type`, `check_kfunc_args`, and `check_struct_ops_btf_id`. `bpf_tramp_collect_arena_args` is definitely exported from `include/linux/bpf.h` and implemented in `kernel/bpf/trampoline.c` and so is non static, so that's good. `bpf_trampoline_update` is also within `kernel/bpf/trampoline.c`, as expected. `btf_distill_func_proto` and `btf_ctx_access` are located in `kernel/bpf/btf.c`. And `get_kfunc_arg_type` and `check_kfunc_args` are in `kernel/bpf/verifier.c` and `check_struct_ops_btf_id` is in `kernel/bpf/verifier.c`. Aha! `get_kfunc_arg_type` and `check_kfunc_args` are *static* within `kernel/bpf/verifier.c`. Right, so this patch introduces changes involving functions that deal with the verifier, but it's not exposing new externally callable verifier functions. I need to dig into the implementation of these static functions to see how this affects external kfuncs. Time to inspect `kernel/bpf/verifier.c` directly. |
| 8/2 | 2026/08/03 16:13 | tool | grepper |
0mArgs:{
"Expression": "get_kfunc_arg_type",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=11396=static int\nkernel/bpf/verifier.c:11397:get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\nkernel/bpf/verifier.c-11398-\t\t const struct btf_param *args, int arg, int nargs)\n--\nkernel/bpf/verifier.c=11511=static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-11537-\nkernel/bpf/verifier.c:11538:\t\targ_type = get_kfunc_arg_type(env, meta, args, i, nargs);\nkernel/bpf/verifier.c-11539-\t\tif (arg_type \u003c 0)\n"
} |
| 9/2 | 2026/08/03 16:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32227 output: 34 thoughts: 27 Thoughts: Here's my thought process, summarized as you requested: **Considering Function Argument Types** Okay, so `get_kfunc_arg_type` is definitely a static function. That's clear. But now, I'm wondering about the implications for `check_kfunc_args`. Is *it* static as well? I need to quickly determine whether that function shares the same design characteristics or if there is a different approach for this function. Is there something special about the arguments it receives or the way it is called that might influence its status? I must know whether it is static, and how that status may effect my designs. |
| 10/2 | 2026/08/03 16:13 | tool | grepper |
0mArgs:{
"Expression": "check_kfunc_args",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=12101=static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-12116-\nkernel/bpf/verifier.c:12117:static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\nkernel/bpf/verifier.c-12118-\t\t\t int insn_idx)\n--\nkernel/bpf/verifier.c=13070=static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-13154-\t/* Check the arguments */\nkernel/bpf/verifier.c:13155:\terr = check_kfunc_args(env, \u0026meta, insn_idx);\nkernel/bpf/verifier.c-13156-\tif (err \u003c 0)\n--\nkernel/bpf/verifier.c-13440-\t\telse\nkernel/bpf/verifier.c:13441:\t\t\t/* scalar. ensured by check_kfunc_args() */\nkernel/bpf/verifier.c-13442-\t\t\tmark_btf_func_reg_size(env, regno, t-\u003esize);\n"
} |
| 11/2 | 2026/08/03 16:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32779 output: 38 thoughts: 21 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Navigating the Static Nature of This Situation** Hmm, so it's inherently static. That’s a key piece of information. This constrains how I can approach the problem, probably necessitating compile-time analysis or a very careful, immutable data structure design. The limitations of static environments always make me think about workarounds, but it's important to understand the actual constraints first, before devising solutions. My mind jumps immediately to a function... what about `check_struct_ops_btf_id`? Is that relevant here? Could that hold some insight into the static structure I'm dealing with, perhaps some type ID or something similar that helps me to work with it? Gotta examine that function's signature and implementation more closely. I wonder if it has direct applicability, or if its principles can be adapted. Time to dig in and see. |
| 12/2 | 2026/08/03 16:13 | tool | grepper |
0mArgs:{
"Expression": "check_struct_ops_btf_id",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=18835=int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,\n--\nkernel/bpf/verifier.c-18843-\nkernel/bpf/verifier.c:18844:static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\nkernel/bpf/verifier.c-18845-{\n--\nkernel/bpf/verifier.c=19582=static int check_attach_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-19605-\tif (prog-\u003etype == BPF_PROG_TYPE_STRUCT_OPS)\nkernel/bpf/verifier.c:19606:\t\treturn check_struct_ops_btf_id(env);\nkernel/bpf/verifier.c-19607-\n"
} |
| 13/2 | 2026/08/03 16:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33287 output: 38 thoughts: 18 Thoughts: Okay, here's my attempt at summarizing those thoughts from a first-person, expert perspective, focusing on the implied reasoning: **Digging into BTF Distillation** Alright, so I'm revisiting this static aspect again. Hmm, just to be sure... I need to check something related to this `btf_distill_func_proto` function. I have a hunch it might be relevant, and I need to make sure I'm not overlooking any obvious connections between the static property and function prototypes. This is a common pattern, and I want to be certain the existing logic is taking the static part into account properly within the distillation process, if it needs to. I should examine how that function handles static declarations or references in prototypes. A quick scan will confirm whether I need to adjust or if it's already well handled. I need to be thorough. |
| 14/2 | 2026/08/03 16:14 | tool | grepper |
0mArgs:{
"Expression": "btf_distill_func_proto",
"PathPrefix": "kernel/bpf/btf.c"
}Results: {
"Output": "kernel/bpf/btf.c=7548=static u8 __get_arg_fmodel_flags(const struct btf *btf,\n--\nkernel/bpf/btf.c-7563-\nkernel/bpf/btf.c:7564:int btf_distill_func_proto(struct bpf_verifier_log *log,\nkernel/bpf/btf.c-7565-\t\t\t struct btf *btf,\n"
} |
| 15/2 | 2026/08/03 16:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33653 output: 35 thoughts: 18 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Navigating the Ephemeral and Considering Access Context** Right, so this isn't a static situation. That's the core assumption I'm working from. This means I need to be thinking about how things change, evolve, and potentially decay over time. The implications of 'non-static' are going to cascade through the rest of my analysis. The challenge then becomes identifying the changing variables and making sure I account for the impact of those variables. And with that said, my next thought is, what about access context? Specifically, I'm already thinking about `btf_ctx_access`. This function, or structure, whatever it is, is going to be central to how I should proceed. Given the fact that the subject is non-static, `btf_ctx_access` is likely to be a factor that is dynamic as well. Are there inherent risks that are only revealed in a specific context? I need to understand its role within the dynamic landscape, and how that context affects what I need to do. I need to understand its nuances. Its implications. My next steps are to review this function/structure. |
| 16/2 | 2026/08/03 16:14 | tool | grepper |
0mArgs:{
"Expression": "btf_ctx_access",
"PathPrefix": "kernel/bpf/btf.c"
}Results: {
"Output": "kernel/bpf/btf.c=6658=static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {\n--\nkernel/bpf/btf.c-6849-\nkernel/bpf/btf.c:6850:bool btf_ctx_access(int off, int size, enum bpf_access_type type,\nkernel/bpf/btf.c-6851-\t\t const struct bpf_prog *prog,\n--\nkernel/bpf/btf.c-7103-}\nkernel/bpf/btf.c:7104:EXPORT_SYMBOL_GPL(btf_ctx_access);\nkernel/bpf/btf.c-7105-\n"
} |
| 17/2 | 2026/08/03 16:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34164 output: 36 thoughts: 19 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Navigating the Dynamic Landscape of eBPF** Hmm, this is definitely not a static situation we're dealing with. The whole nature of this feels inherently dynamic and ever-evolving. My primary focus is on the impact of changes. Okay, let's zoom in a little bit, what about that `bpf_trampoline_update` function? That's the key variable to update, isn't it? It has all the impact on how everything else functions. |
| 18/2 | 2026/08/03 16:14 | tool | grepper |
0mArgs:{
"Expression": "bpf_trampoline_update",
"PathPrefix": "kernel/bpf/trampoline.c"
}Results: {
"Output": "kernel/bpf/trampoline.c=62=struct bpf_trampoline_ops {\n--\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,\nkernel/bpf/trampoline.c-71-\t\t\t\t const struct bpf_trampoline_ops *ops, void *data);\n--\nkernel/bpf/trampoline.c=98=static int bpf_tramp_ftrace_ops_func(struct ftrace_ops *ops, unsigned long ip,\n--\nkernel/bpf/trampoline.c-156-\t\t !(tr-\u003eflags \u0026 BPF_TRAMP_F_ORIG_STACK))\nkernel/bpf/trampoline.c:157:\t\t\tret = bpf_trampoline_update(tr, false /* lock_direct_mutex */,\nkernel/bpf/trampoline.c-158-\t\t\t\t\t\t \u0026trampoline_ops, NULL);\n--\nkernel/bpf/trampoline.c-163-\t\tif (tr-\u003eflags \u0026 BPF_TRAMP_F_ORIG_STACK)\nkernel/bpf/trampoline.c:164:\t\t\tret = bpf_trampoline_update(tr, false /* lock_direct_mutex */,\nkernel/bpf/trampoline.c-165-\t\t\t\t\t\t \u0026trampoline_ops, NULL);\n--\nkernel/bpf/trampoline.c=373=static struct bpf_trampoline *bpf_trampoline_lookup(u64 key, unsigned long ip)\n--\nkernel/bpf/trampoline.c-410-\nkernel/bpf/trampoline.c:411:static int bpf_trampoline_update_fentry(struct bpf_trampoline *tr, u32 orig_flags,\nkernel/bpf/trampoline.c-412-\t\t\t\t\tvoid *old_addr, void *new_addr)\n--\nkernel/bpf/trampoline.c=432=static int unregister_fentry(struct bpf_trampoline *tr, u32 orig_flags, void *data __maybe_unused)\n--\nkernel/bpf/trampoline.c-439-\telse\nkernel/bpf/trampoline.c:440:\t\tret = bpf_trampoline_update_fentry(tr, orig_flags, old_addr, NULL);\nkernel/bpf/trampoline.c-441-\n--\nkernel/bpf/trampoline.c=450=static int modify_fentry(struct bpf_trampoline *tr, u32 orig_flags, struct bpf_tramp_image *im,\n--\nkernel/bpf/trampoline.c-459-\t} else {\nkernel/bpf/trampoline.c:460:\t\tret = bpf_trampoline_update_fentry(tr, orig_flags, old_addr,\nkernel/bpf/trampoline.c-461-\t\t\t\t\t\t new_addr);\n--\nkernel/bpf/trampoline.c=473=static int register_fentry(struct bpf_trampoline *tr, struct bpf_tramp_image *im,\n--\nkernel/bpf/trampoline.c-490-\t} else {\nkernel/bpf/trampoline.c:491:\t\tret = bpf_trampoline_update_fentry(tr, 0, NULL, new_addr);\nkernel/bpf/trampoline.c-492-\t}\n--\nkernel/bpf/trampoline.c=720=void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags)\n--\nkernel/bpf/trampoline.c-726-\nkernel/bpf/trampoline.c:727:static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex,\nkernel/bpf/trampoline.c-728-\t\t\t\t const struct bpf_trampoline_ops *ops, void *data)\n--\nkernel/bpf/trampoline.c=976=static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,\n--\nkernel/bpf/trampoline.c-1010-\t\treturn err;\nkernel/bpf/trampoline.c:1011:\terr = bpf_trampoline_update(tr, true /* lock_direct_mutex */, ops, data);\nkernel/bpf/trampoline.c-1012-\tif (err)\n--\nkernel/bpf/trampoline.c=1029=static int __bpf_trampoline_unlink_prog(struct bpf_tramp_node *node,\n--\nkernel/bpf/trampoline.c-1049-\tbpf_trampoline_remove_prog(tr, node);\nkernel/bpf/trampoline.c:1050:\treturn bpf_trampoline_update(tr, true /* lock_direct_mutex */, ops, data);\nkernel/bpf/trampoline.c-1051-}\n"
} |
| 19/2 | 2026/08/03 16:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 35621 output: 40 thoughts: 30 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Analyzing `bpf_trampoline_update`** Okay, so `bpf_trampoline_update` is static. That immediately piqued my interest. Given the context, my mind is already running through implications for linker visibility, symbol scoping, and potential inlining behaviour within the kernel's BPF infrastructure. It's a key observation, but it's not the full picture. To understand the practical impact of this declaration and where this function is used, I should now move into `bpf_tramp_collect_arena_args`. I have a feeling that function will provide some important insight here. I expect it is related. From there, I will be able to form a more complete understanding of this section of code. |
| 20/2 | 2026/08/03 16:14 | tool | grepper |
0mArgs:{
"Expression": "bpf_tramp_collect_arena_args",
"PathPrefix": "kernel/bpf/trampoline.c"
}Results: {
"Output": "kernel/bpf/trampoline.c=532=static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)\n--\nkernel/bpf/trampoline.c-548- */\nkernel/bpf/trampoline.c:549:bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,\nkernel/bpf/trampoline.c-550-\t\t\t\t struct bpf_tramp_arena_args *aargs)\n"
} |
| 21/2 | 2026/08/03 16:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36008 output: 46 thoughts: 37 Thoughts: Okay, here's my take on those thoughts, framed as a first-person expert summary: **Analyzing `bpf_tramp_collect_arena_args` and Trampoline Preparation** Alright, so `bpf_tramp_collect_arena_args` is indeed *not* a static function, which is something I need to keep in mind. Now, that's crucial because it affects the overall architecture and the possible scope of its use. I need to understand where it's called and how it's being accessed within the broader BPF context. My immediate next step is to drill into `__arch_prepare_bpf_trampoline`. That function is probably where a lot of the magic is happening regarding the setup and preparation of these BPF trampolines, and where `bpf_tramp_collect_arena_args` likely plays a significant role, perhaps even being called directly as part of that preparation process. Understanding the interaction between these two is critical for figuring out the complete picture of how BPF trampolines are structured and handled. Knowing how these functions interact will immediately tell me something very interesting about the overall design. |
| 22/2 | 2026/08/03 16:14 | tool | grepper |
0mArgs:{
"Expression": "__arch_prepare_bpf_trampoline",
"PathPrefix": "arch/x86/net/bpf_jit_comp.c"
}Results: {
"Output": "arch/x86/net/bpf_jit_comp.c=3335=static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,\n--\narch/x86/net/bpf_jit_comp.c-3436- */\narch/x86/net/bpf_jit_comp.c:3437:static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\narch/x86/net/bpf_jit_comp.c-3438-\t\t\t\t\t void *rw_image_end, void *image,\n--\narch/x86/net/bpf_jit_comp.c=3762=int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *image_end,\n--\narch/x86/net/bpf_jit_comp.c-3777-\narch/x86/net/bpf_jit_comp.c:3778:\tret = __arch_prepare_bpf_trampoline(im, rw_image, rw_image + size, image, m,\narch/x86/net/bpf_jit_comp.c-3779-\t\t\t\t\t flags, tnodes, func_addr);\n--\narch/x86/net/bpf_jit_comp.c=3791=int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,\n--\narch/x86/net/bpf_jit_comp.c-3797-\narch/x86/net/bpf_jit_comp.c:3798:\t/* Allocate a temporary buffer for __arch_prepare_bpf_trampoline().\narch/x86/net/bpf_jit_comp.c-3799-\t *\n--\narch/x86/net/bpf_jit_comp.c-3807-\narch/x86/net/bpf_jit_comp.c:3808:\tret = __arch_prepare_bpf_trampoline(\u0026im, image, image + PAGE_SIZE, image,\narch/x86/net/bpf_jit_comp.c-3809-\t\t\t\t\t m, flags, tnodes, func_addr);\n"
} |
| 23/2 | 2026/08/03 16:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36932 output: 38 thoughts: 48 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Trampoline Functions and Arena Argument Support in BPF JIT** Right, so I'm seeing a distinction here. `__arch_prepare_bpf_trampoline` is declared as `static`, which immediately tells me it's likely an internal helper function within a specific compilation unit – likely architecture-specific to prevent external linkage. On the other hand, `arch_prepare_bpf_trampoline` is *not* `static`. This implies it's intended for external use, probably as part of the public BPF JIT API on the architecture in question. I need to understand the relationship and function of these two. I imagine the non-static function will be the entry point exposed to the higher-level BPF subsystem. The static one, likely used internally by `arch_prepare_bpf_trampoline`, does the heavy lifting. I should explore the implementations to confirm this. Now, let's pivot. The next logical step is to check `bpf_jit_supports_arena_args`. I'm guessing this function, or variable, plays a crucial role in determining whether or not the architecture's JIT compiler has the necessary capabilities for "arena arguments". I'm making an assumption that the `arena` is related to how BPF programs pass arguments, or manage intermediate state. It's likely involved in memory management specific to JIT-compiled BPF code. The function name implies some runtime check, or possibly a build-time configuration flag, to indicate if this support is available. I should examine the definition and uses of this flag, or function, to determine precisely what's supported and why. I'd be looking for conditional compilation paths, checks for specific hardware features or kernel configuration options, or maybe even checks for specific kernel versions. I'll need to see how these elements combine and influence the BPF JIT's code generation process. |
| 24/2 | 2026/08/03 16:14 | tool | grepper |
0mArgs:{
"Expression": "bpf_jit_supports_arena_args",
"PathPrefix": "kernel/bpf/core.c"
}Results: {
"Output": "kernel/bpf/core.c=3306=bool __weak bpf_jit_supports_stack_args(void)\n--\nkernel/bpf/core.c-3310-\nkernel/bpf/core.c:3311:bool __weak bpf_jit_supports_arena_args(void)\nkernel/bpf/core.c-3312-{\n"
} |
| 25/2 | 2026/08/03 16:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 37544 output: 57 thoughts: 30 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Analyzing Potential Weakness and Identifying the Target** Okay, so the initial assessment flags this as a non-static, or "weak" point. That immediately triggers my internal radar; we need to dig deeper. My focus shifts to the suspected area. Let's start by scrutinizing `bpf_testmod_ops3_call_test_arena`. This could be the linchpin, and the starting point for some investigation. Time to go hunting for something interesting there. |
| 26/2 | 2026/08/03 16:14 | tool | grepper |
0mArgs:{
"Expression": "bpf_testmod_ops3_call_test_arena",
"PathPrefix": "tools/testing/selftests/bpf/test_kmods/bpf_testmod.c"
}Results: {
"Output": "tools/testing/selftests/bpf/test_kmods/bpf_testmod.c=436=__bpf_kfunc void bpf_testmod_ops3_call_test_2(void)\n--\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c-440-\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c:441:__bpf_kfunc int bpf_testmod_ops3_call_test_arena(u64 *ptr__arena)\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c-442-{\n--\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c-445-\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c:446:__bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena_nullable)\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c-447-{\n--\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c-450-\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c:451:__bpf_kfunc int bpf_testmod_ops3_call_test_arena_stack(u64 *ptr__arena)\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c-452-{\n--\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c=864=BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c:865:BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c:866:BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c:867:BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)\ntools/testing/selftests/bpf/test_kmods/bpf_testmod.c-868-BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);\n"
} |
| 27/2 | 2026/08/03 16:14 | llm | patch-evaluator |
2mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 38450 output: 167 thoughts: 1519 Thoughts: **My Analysis of BPF Arena Pointer Support** Okay, let's break this down. We're looking at changes related to BPF kfuncs and `struct_ops` arguments, specifically the introduction of `__arena` and `__arena_nullable` annotations. My initial scan focused on functions in `test_kmods`, but that's a red herring. We need to look at what's actually *integrated* into the kernel's BPF core logic. I immediately honed in on `btf_ctx_access` and `btf_distill_func_proto`, both of which are non-static and part of the core BPF code. `btf_ctx_access` is crucial because it governs context access checks for tracing and `struct_ops` programs. `btf_distill_func_proto` is essential for distilling function prototypes for trampoline operations. Then I considered other possible candidates. `bpf_tramp_collect_arena_args` seems promising, as does `bpf_jit_supports_arena_args`, but the latter's just a boolean check, which isn't as interesting from a fuzzing perspective. `bpf_tramp_collect_arena_args` and `btf_distill_func_proto` and `btf_ctx_access` are the key functions. I did a deep dive to ensure I wasn't missing other non-static functions related to BPF. I looked into `bpf_prog_ctx_arg_info_init`, `arch_prepare_bpf_trampoline`, `bpf_struct_ops_image_free`, `prepare_arg_info`, `check_struct_ops_btf_id`, `check_kfunc_args`, and `get_kfunc_arg_type`. I was careful to make sure I wasn't selecting static functions, such as `prepare_arg_info`, `check_struct_ops_btf_id`, `check_kfunc_args`, and `get_kfunc_arg_type`. So, the definitive list of *modified* or *added* non-static functions are: * `bpf_tramp_collect_arena_args` * `bpf_jit_supports_arena_args` * `btf_distill_func_proto` * `btf_ctx_access` Let's double-check `bpf_tramp_collect_arena_args`. It’s added in `kernel/bpf/trampoline.c` and is called from `__arch_prepare_bpf_trampoline` in `arch/x86/net/bpf_jit_comp.c`. This reinforces its importance in the trampoline logic. The changes to `btf_distill_func_proto` and `btf_ctx_access` are located in `kernel/bpf/btf.c`. I reviewed the other files in the kernel. In `kernel/bpf/core.c`, `bpf_jit_supports_arena_args` is added. In `arch/x86/net/bpf_jit_comp.c`, `bpf_jit_supports_arena_args` is added again. My focus narrows to these core functions: * `bpf_tramp_collect_arena_args` * `btf_distill_func_proto` * `btf_ctx_access` Now, regarding configurations, `BPF_SYSCALL` and `BPF_JIT` are obviously critical. The trampolines are part of the JIT, so we need both. I confirmed that `BPF_JIT_ALWAYS_ON` isn't strictly necessary. `BPF_EVENTS` isn't required here either. Let's quickly verify the function definitions. `bpf_tramp_collect_arena_args` is defined as: ```c bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags, struct bpf_tramp_arena_args *aargs) ``` It is exported in `include/linux/bpf.h`. `btf_distill_func_proto` is defined as: ```c int btf_distill_func_proto(struct bpf_verifier_log *log, struct btf *btf, const struct btf_type *func, const char *func_name, struct btf_func_model *m) ``` `btf |
| 28/2 | 2026/08/03 16:16 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_collect_arena_args",
"btf_distill_func_proto",
"btf_ctx_access"
],
"Reasoning": "The patch introduces `__arena` and `__arena_nullable` annotations for BPF kfuncs and struct_ops arguments. It modifies the BPF verifier, JIT compiler (x86), and trampoline logic to support arena pointers, including rebasing them to kernel addresses. This is a functional change to the core BPF subsystem and should be fuzzed to ensure memory safety and correct verification of arena pointers.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_collect_arena_args",
"btf_distill_func_proto",
"btf_ctx_access"
],
"Reasoning": "The patch introduces `__arena` and `__arena_nullable` annotations for BPF kfuncs and struct_ops arguments. It modifies the BPF verifier, JIT compiler (x86), and trampoline logic to support arena pointers, including rebasing them to kernel addresses. This is a functional change to the core BPF subsystem and should be fuzzed to ensure memory safety and correct verification of arena pointers.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|