| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/08 01:22 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_arena_base",
"bpf_struct_ops_desc_init",
"btf_distill_func_proto",
"bpf_check_attach_target",
"arch_prepare_bpf_trampoline",
"bpf_jit_supports_arena_args",
"btf_ctx_access"
],
"KMSANReasoning": "The patch series introduces support for `__arena` and `__arena__nullable` annotations in BPF kfuncs and struct_ops, modifying the BPF verifier and x86 JIT compiler to handle arena pointers. The changes primarily involve pointer arithmetic (truncation and rebasing using `kern_vm_start`) during JIT compilation and verifier type checking. \n\nThese changes do not introduce new kernel structures copied to user space, nor do they allocate complex data structures with potentially uninitialized fields. Any bugs introduced by incorrect JIT translation, pointer arithmetic, or verifier logic would result in out-of-bounds accesses, invalid pointer dereferences, or memory corruption. These types of bugs are reliably detected by KASAN and standard memory corruption checks. There is no specific risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces `__arena` and `__arena__nullable` annotations for BPF kfuncs and struct_ops arguments. It modifies the BPF verifier, JIT compiler, struct_ops, and trampoline to support passing BPF arena pointers to kfuncs and struct_ops callbacks. This is a significant functional change in the BPF subsystem that should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/08 01:22 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit f860347ae84ce23eee0573b82d4ca4aabbbae0e8\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Aug 8 01:22:09 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst\nindex 021be6d93dfb0..1004eb0bec617 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@@ -522,6 +559,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 88ed95b2eaa72..8dddb5d7af21b 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@@ -2588,6 +2632,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@@ -2998,11 +3048,39 @@ 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 u64 arena_base)\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 stack_args_off = (use_jmp || (flags \u0026 BPF_TRAMP_F_INDIRECT)) ? 16 : 24;\n \tint i, j;\n \n \t/* Store function arguments to stack.\n@@ -3011,6 +3089,9 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\n \t * mov QWORD PTR [rbp-0x8],rsi\n \t */\n \tfor (i = 0; i \u003c min_t(int, m-\u003enr_args, MAX_BPF_FUNC_ARGS); i++) {\n+\t\tbool arena_arg = arena_base \u0026\u0026 (m-\u003earg_flags[i] \u0026 BTF_FMODEL_ARENA_ARG);\n+\t\tbool nullable = m-\u003earg_flags[i] \u0026 BTF_FMODEL_NULLABLE_ARG;\n+\n \t\targ_regs = (m-\u003earg_size[i] + 7) / 8;\n \n \t\t/* According to the research of Yonghong, struct members\n@@ -3034,16 +3115,19 @@ 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 (arena_arg)\n+\t\t\t\t\temit_arena_arg_conv(prog, BPF_REG_0, nullable,\n+\t\t\t\t\t\t\t (u32)arena_base);\n \t\t\t\temit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,\n \t\t\t\t\t -stack_size);\n \n@@ -3064,9 +3148,13 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\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 (arena_arg) {\n+\t\t\t\t\temit_arena_arg_conv(prog, src, nullable, (u32)arena_base);\n+\t\t\t\t\tsrc = BPF_REG_0;\n+\t\t\t\t}\n+\t\t\t\temit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);\n \t\t\t\tstack_size -= 8;\n \t\t\t\tnr_regs++;\n \t\t\t}\n@@ -3362,6 +3450,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \tvoid *orig_call = func_addr;\n \tint cookie_off, cookie_cnt;\n \tu8 **branches = NULL;\n+\tu64 arena_base;\n \tu64 func_meta;\n \tu8 *prog;\n \tbool save_ret;\n@@ -3374,6 +3463,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+\tarena_base = bpf_tramp_arena_base(m, tnodes, flags);\n+\n \tfor (i = 0; i \u003c m-\u003enr_args; i++)\n \t\tnr_regs += (m-\u003earg_size[i] + 7) / 8 - 1;\n \n@@ -3508,7 +3599,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t\temit_store_stack_imm64(\u0026prog, BPF_REG_0, -ip_off, (long)func_addr);\n \t}\n \n-\tsave_args(m, \u0026prog, regs_off, false, flags);\n+\tsave_args(m, \u0026prog, regs_off, false, flags, arena_base);\n \n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n \t\t/* arg1: mov rdi, im */\n@@ -3550,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, 0);\n \n \t\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX) {\n \t\t\t/* Before calling the original function, load the\n@@ -4051,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 d79bf7557ef68..b4a10c9878cf8 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -1195,6 +1195,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@@ -1268,6 +1274,15 @@ struct bpf_tramp_nodes {\n \tint nr_nodes;\n };\n \n+/*\n+ * The arena base against which a struct_ops trampoline converts the\n+ * arguments marked with BTF_FMODEL_ARENA_ARG while saving them into the BPF\n+ * ctx, ctx[arg] = (u32)(kaddr - kern_vm_start). Zero when the trampoline\n+ * converts nothing.\n+ */\n+u64 bpf_tramp_arena_base(const struct btf_func_model *m,\n+\t\t\t struct bpf_tramp_nodes *tnodes, u32 flags);\n+\n struct bpf_tramp_run_ctx;\n \n /* Different use cases for BPF trampoline:\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex a2a40caca0a0e..5c8a4d9b8fce7 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -1177,8 +1177,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id)\n \t\t*btf_id = key \u0026 0x7FFFFFFF;\n }\n \n-int bpf_check_btf_info_early(struct bpf_verifier_env *env,\n-\t\t\t const union bpf_attr *attr, bpfptr_t uattr);\n+int bpf_prepare_btf_info(struct bpf_verifier_env *env,\n+\t\t\t const union bpf_attr *attr, bpfptr_t uattr);\n int bpf_check_btf_info(struct bpf_verifier_env *env,\n \t\t const union bpf_attr *attr, bpfptr_t uattr);\n \n@@ -1302,6 +1302,16 @@ static inline u32 type_flag(u32 type)\n \treturn type \u0026 ~BPF_BASE_TYPE_MASK;\n }\n \n+static inline 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 static inline enum bpf_prog_type resolve_prog_type(const struct bpf_prog *prog)\n {\n \treturn (prog-\u003etype == BPF_PROG_TYPE_EXT \u0026\u0026 prog-\u003eaux-\u003esaved_dst_prog_type) ?\ndiff --git a/include/linux/filter.h b/include/linux/filter.h\nindex 41b02d53e2228..4edba8182db1b 100644\n--- a/include/linux/filter.h\n+++ b/include/linux/filter.h\n@@ -1214,6 +1214,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..d7c3030bc63b0 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@@ -172,10 +174,12 @@ static int prepare_arg_info(struct btf *btf,\n \t\t\t const char *st_ops_name,\n \t\t\t const char *member_name,\n \t\t\t const struct btf_type *func_proto, void *stub_func_addr,\n+\t\t\t struct btf_func_model *model,\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@@ -225,27 +229,39 @@ static int prepare_arg_info(struct btf *btf,\n \t/* Prepare info for every nullable argument */\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\tbool ptr_to_arena, ptr_to_struct;\n+\n+\t\t/*\n+\t\t * Skip arguments that are not suffixed with \"__arena__nullable\",\n+\t\t * \"__arena\", \"__nullable\", or \"__ref\".\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_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+\t\tis_nullable = !is_arena_nullable \u0026\u0026\n+\t\t\t btf_param_match_suffix(btf, \u0026stub_args[arg_no], 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 \n-\t\tif (is_nullable)\n+\t\tif (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 if (is_nullable)\n \t\t\tsuffix = MAYBE_NULL_SUFFIX;\n \t\telse if (is_refcounted)\n \t\t\tsuffix = REFCOUNTED_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/*\n+\t\t * Should be a pointer to struct, or any pointer for __arena or\n+\t\t * __arena__nullable.\n+\t\t */\n+\t\tpointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, \u0026arg_btf_id);\n+\t\tptr_to_arena = pointed_type \u0026\u0026 (is_arena || is_arena_nullable);\n+\t\tptr_to_struct = pointed_type \u0026\u0026 btf_type_is_struct(pointed_type);\n+\t\tif (!ptr_to_arena \u0026\u0026 !ptr_to_struct) {\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@@ -268,7 +284,18 @@ static int prepare_arg_info(struct btf *btf,\n \t\tinfo-\u003ebtf_id = arg_btf_id;\n \t\tinfo-\u003ebtf = btf;\n \t\tinfo-\u003eoffset = offset;\n-\t\tif (is_nullable) {\n+\t\tif (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\tmodel-\u003earg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;\n+\t\t\tif (is_arena_nullable)\n+\t\t\t\tmodel-\u003earg_flags[arg_no] |= BTF_FMODEL_NULLABLE_ARG;\n+\t\t} else if (is_nullable) {\n \t\t\tinfo-\u003ereg_type = PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;\n \t\t} else if (is_refcounted) {\n \t\t\tinfo-\u003ereg_type = PTR_TRUSTED | PTR_TO_BTF_ID;\n@@ -460,6 +487,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,\n \t\tstub_func_addr = *(void **)(st_ops-\u003ecfi_stubs + moff);\n \t\terr = prepare_arg_info(btf, st_ops-\u003ename, mname,\n \t\t\t\t func_proto, stub_func_addr,\n+\t\t\t\t \u0026st_ops-\u003efunc_models[i],\n \t\t\t\t arg_info + i);\n \t\tif (err)\n \t\t\tgoto errout;\ndiff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c\nindex 42414633cf263..6606187ed4f43 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@@ -7539,6 +7543,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__nullable\"))\n+\t\tflags |= BTF_FMODEL_ARENA_ARG | BTF_FMODEL_NULLABLE_ARG;\n+\telse if (btf_param_match_suffix(btf, arg, \"__arena\"))\n+\t\tflags |= BTF_FMODEL_ARENA_ARG;\n+\telse if (btf_param_match_suffix(btf, arg, \"__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@@ -7604,7 +7624,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/check_btf.c b/kernel/bpf/check_btf.c\nindex 93bebe6fe12e7..0e8b3ccc7a5b9 100644\n--- a/kernel/bpf/check_btf.c\n+++ b/kernel/bpf/check_btf.c\n@@ -28,9 +28,9 @@ static int check_abnormal_return(struct bpf_verifier_env *env)\n #define MIN_BPF_FUNCINFO_SIZE\t8\n #define MAX_FUNCINFO_REC_SIZE\t252\n \n-static int check_btf_func_early(struct bpf_verifier_env *env,\n-\t\t\t\tconst union bpf_attr *attr,\n-\t\t\t\tbpfptr_t uattr)\n+static int prepare_btf_func(struct bpf_verifier_env *env,\n+\t\t\t const union bpf_attr *attr,\n+\t\t\t bpfptr_t uattr)\n {\n \tu32 krec_size = sizeof(struct bpf_func_info);\n \tconst struct btf_type *type, *func_proto;\n@@ -407,9 +407,9 @@ static int check_core_relo(struct bpf_verifier_env *env,\n \treturn err;\n }\n \n-int bpf_check_btf_info_early(struct bpf_verifier_env *env,\n-\t\t\t const union bpf_attr *attr,\n-\t\t\t bpfptr_t uattr)\n+int bpf_prepare_btf_info(struct bpf_verifier_env *env,\n+\t\t\t const union bpf_attr *attr,\n+\t\t\t bpfptr_t uattr)\n {\n \tstruct btf *btf;\n \tint err;\n@@ -429,7 +429,7 @@ int bpf_check_btf_info_early(struct bpf_verifier_env *env,\n \t}\n \tenv-\u003eprog-\u003eaux-\u003ebtf = btf;\n \n-\terr = check_btf_func_early(env, attr, uattr);\n+\terr = prepare_btf_func(env, attr, uattr);\n \tif (err)\n \t\treturn err;\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..e07af35ed0402 100644\n--- a/kernel/bpf/trampoline.c\n+++ b/kernel/bpf/trampoline.c\n@@ -529,6 +529,36 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a\n \treturn tnodes;\n }\n \n+/*\n+ * The arena base against which save_args() converts the arguments marked\n+ * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline\n+ * converts: it dispatches to a single prog whose arena is known at\n+ * generation time. Return 0 when there is nothing to convert.\n+ */\n+u64 bpf_tramp_arena_base(const struct btf_func_model *m,\n+\t\t\t struct bpf_tramp_nodes *tnodes, u32 flags)\n+{\n+\tconst struct bpf_prog *prog;\n+\tint i;\n+\n+\tif (!(flags \u0026 BPF_TRAMP_F_INDIRECT) ||\n+\t tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)\n+\t\treturn 0;\n+\n+\tfor (i = 0; i \u003c m-\u003enr_args; i++)\n+\t\tif (m-\u003earg_flags[i] \u0026 BTF_FMODEL_ARENA_ARG)\n+\t\t\tbreak;\n+\tif (i == m-\u003enr_args)\n+\t\treturn 0;\n+\n+\t/* Verification rejects an arena argument without an arena. */\n+\tprog = tnodes[BPF_TRAMP_FENTRY].nodes[0]-\u003elink-\u003eprog;\n+\tif (WARN_ON_ONCE(!prog-\u003eaux-\u003earena))\n+\t\treturn 0;\n+\n+\treturn bpf_arena_get_kern_vm_start(prog-\u003eaux-\u003earena);\n+}\n+\n static void bpf_tramp_image_free(struct bpf_tramp_image *im)\n {\n \tbpf_image_ksym_del(\u0026im-\u003eksym);\n@@ -920,6 +950,13 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,\n \tint cnt = 0, i;\n \n \tkind = bpf_attach_type_to_tramp(node-\u003elink-\u003eprog);\n+\t/*\n+\t * Arena ctx args are converted only by struct_ops indirect\n+\t * trampolines. They must never be attached to a generic trampoline.\n+\t */\n+\tif (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node-\u003elink-\u003eprog)))\n+\t\treturn -ENOTSUPP;\n+\n \tif (tr-\u003eextension_prog)\n \t\t/* cannot attach fentry/fexit if extension prog is attached.\n \t\t * cannot overwrite extension prog either.\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex c9533ea700ba2..4bb3102ae4333 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -2842,7 +2842,7 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)\n \treturn 0;\n }\n \n-static int add_subprog_and_kfunc(struct bpf_verifier_env *env)\n+static int add_subprogs(struct bpf_verifier_env *env)\n {\n \tstruct bpf_subprog_info *subprog = env-\u003esubprog_info;\n \tint i, ret, insn_cnt = env-\u003eprog-\u003elen, ex_cb_insn;\n@@ -2854,8 +2854,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)\n \t\treturn ret;\n \n \tfor (i = 0; i \u003c insn_cnt; i++, insn++) {\n-\t\tif (!bpf_pseudo_func(insn) \u0026\u0026 !bpf_pseudo_call(insn) \u0026\u0026\n-\t\t !bpf_pseudo_kfunc_call(insn))\n+\t\tif (!bpf_pseudo_func(insn) \u0026\u0026 !bpf_pseudo_call(insn))\n \t\t\tcontinue;\n \n \t\tif (!env-\u003ebpf_capable) {\n@@ -2863,11 +2862,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)\n \t\t\treturn -EPERM;\n \t\t}\n \n-\t\tif (bpf_pseudo_func(insn) || bpf_pseudo_call(insn))\n-\t\t\tret = add_subprog(env, i + insn-\u003eimm + 1);\n-\t\telse\n-\t\t\tret = bpf_add_kfunc_call(env, insn-\u003eimm, insn-\u003eoff);\n-\n+\t\tret = add_subprog(env, i + insn-\u003eimm + 1);\n \t\tif (ret \u003c 0)\n \t\t\treturn ret;\n \t}\n@@ -2905,6 +2900,28 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)\n \treturn 0;\n }\n \n+static int add_kfuncs(struct bpf_verifier_env *env)\n+{\n+\tstruct bpf_insn *insn = env-\u003eprog-\u003einsnsi;\n+\tint i, ret, insn_cnt = env-\u003eprog-\u003elen;\n+\n+\tfor (i = 0; i \u003c insn_cnt; i++, insn++) {\n+\t\tif (!bpf_pseudo_kfunc_call(insn))\n+\t\t\tcontinue;\n+\n+\t\tif (!env-\u003ebpf_capable) {\n+\t\t\tverbose(env, \"loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\\n\");\n+\t\t\treturn -EPERM;\n+\t\t}\n+\n+\t\tret = bpf_add_kfunc_call(env, insn-\u003eimm, insn-\u003eoff);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int check_subprogs(struct bpf_verifier_env *env)\n {\n \tint i, subprog_start, subprog_end, off, cur_subprog = 0;\n@@ -10919,7 +10936,8 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf\n \n static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)\n {\n-\treturn btf_param_match_suffix(btf, arg, \"__nullable\");\n+\treturn btf_param_match_suffix(btf, arg, \"__nullable\") ||\n+\t btf_param_match_suffix(btf, arg, \"__arena\");\n }\n \n static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)\n@@ -10937,6 +10955,12 @@ 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__nullable\") ||\n+\t btf_param_match_suffix(btf, arg, \"__arena\");\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@@ -11157,6 +11181,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@@ -11442,7 +11467,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@@ -11491,7 +11515,30 @@ 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 (arg + 1 \u003c nargs \u0026\u0026\n+\telse if (is_kfunc_arg_arena(meta-\u003ebtf, \u0026args[arg])) {\n+\t\tif (!bpf_jit_supports_arena_args()) {\n+\t\t\tverbose(env, \"JIT does not support kfunc %s() with arena pointer arguments\\n\",\n+\t\t\t\tmeta-\u003efunc_name);\n+\t\t\treturn -ENOTSUPP;\n+\t\t}\n+\t\tif (!env-\u003eprog-\u003eaux-\u003earena) {\n+\t\t\tverbose(env,\n+\t\t\t\t\"%s arena pointer requires a program with an associated arena\\n\",\n+\t\t\t\treg_arg_name(env, argno));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\tif (reg_from_argno(argno) \u003c 0) {\n+\t\t\tverbose(env, \"%s arena pointer cannot be a stack argument\\n\",\n+\t\t\t\treg_arg_name(env, argno));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\t/*\n+\t\t * Both suffixes accept a constant zero. The function model determines\n+\t\t * whether the JIT rebases it to the arena base or preserves NULL.\n+\t\t * The common nullable path below records that verifier property.\n+\t\t */\n+\t\targ_type = KF_ARG_PTR_TO_ARENA;\n+\t} else 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 \t\tif (!btf_type_is_void(ref_t) \u0026\u0026 !btf_type_is_scalar(ref_t) \u0026\u0026\n@@ -12166,7 +12213,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\tt = btf_type_skip_modifiers(btf, args[i].type, NULL);\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 !type_may_be_null(kf_arg_type)) {\n \t\t\tverbose(env, \"Possibly NULL pointer passed to trusted %s\\n\",\n \t\t\t\treg_arg_name(env, argno));\n \t\t\treturn -EACCES;\n@@ -12219,6 +12266,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\tcase KF_ARG_PTR_TO_TASK_WORK:\n \t\tcase KF_ARG_PTR_TO_IRQ_FLAG:\n \t\tcase KF_ARG_PTR_TO_RES_SPIN_LOCK:\n+\t\tcase KF_ARG_PTR_TO_ARENA:\n \t\t\tbreak;\n \t\tcase KF_ARG_PTR_TO_DYNPTR:\n \t\t\targ_type = ARG_PTR_TO_DYNPTR;\n@@ -12285,6 +12333,13 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\t\tmeta-\u003eret_btf_id = ret;\n \t\t\t}\n \t\t\tbreak;\n+\t\tcase KF_ARG_PTR_TO_ARENA:\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\tbreak;\n \t\tcase KF_ARG_PTR_TO_ALLOC_BTF_ID:\n \t\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC)) {\n \t\t\t\tif (!is_bpf_obj_drop_kfunc(meta-\u003efunc_id)) {\n@@ -18820,6 +18875,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@@ -18898,10 +18954,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@@ -18922,8 +18991,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 \n@@ -19190,6 +19258,16 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,\n \t\t\tbpf_log(log, \"Subprog %s doesn't exist\\n\", tname);\n \t\t\treturn -EINVAL;\n \t\t}\n+\t\t/*\n+\t\t * A struct_ops indirect trampoline converts arena arguments\n+\t\t * before invoking its program. A tracing or extension program\n+\t\t * attached to the main program would see the converted offset as a\n+\t\t * regular BTF pointer.\n+\t\t */\n+\t\tif (subprog == 0 \u0026\u0026 bpf_prog_has_arena_ctx_arg(tgt_prog)) {\n+\t\t\tbpf_log(log, \"Cannot attach to a target with arena context arguments\\n\");\n+\t\t\treturn -EOPNOTSUPP;\n+\t\t}\n \t\tif (aux-\u003efunc \u0026\u0026 aux-\u003efunc[subprog]-\u003eaux-\u003eexception_cb) {\n \t\t\tbpf_log(log,\n \t\t\t\t\"%s programs cannot attach to exception callback\\n\",\n@@ -20325,11 +20403,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \t\tINIT_LIST_HEAD(\u0026env-\u003eexplored_states[i]);\n \tINIT_LIST_HEAD(\u0026env-\u003efree_list);\n \n-\tret = bpf_check_btf_info_early(env, attr, uattr);\n+\t/* Prepare BTF and func_info needed to discover all subprograms. */\n+\tret = bpf_prepare_btf_info(env, attr, uattr);\n \tif (ret \u003c 0)\n \t\tgoto skip_full_check;\n \n-\tret = add_subprog_and_kfunc(env);\n+\t/* Discover all subprograms before validating their layout and BTF. */\n+\tret = add_subprogs(env);\n \tif (ret \u003c 0)\n \t\tgoto skip_full_check;\n \n@@ -20337,14 +20417,21 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \tif (ret \u003c 0)\n \t\tgoto skip_full_check;\n \n+\t/* Validate BTF against the complete subprogram layout and apply CO-RE. */\n \tret = bpf_check_btf_info(env, attr, uattr);\n \tif (ret \u003c 0)\n \t\tgoto skip_full_check;\n \n+\t/* Validate instructions and resolve the program's referenced resources. */\n \tret = check_and_resolve_insns(env);\n \tif (ret \u003c 0)\n \t\tgoto skip_full_check;\n \n+\t/* Build kfunc prototypes after resolving program resources. */\n+\tret = add_kfuncs(env);\n+\tif (ret \u003c 0)\n+\t\tgoto skip_full_check;\n+\n \tif (bpf_prog_is_offloaded(env-\u003eprog-\u003eaux)) {\n \t\tret = bpf_prog_offload_verifier_prep(env-\u003eprog);\n \t\tif (ret)\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..940ec2cda0d52\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c\n@@ -0,0 +1,128 @@\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_attach.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+\n+static void arena_arg_attach_one(int target_fd, const char *prog_name)\n+{\n+\tstruct struct_ops_arena_attach *skel;\n+\tstruct bpf_program *prog, *pos;\n+\tchar log_buf[64 * 1024];\n+\tint err;\n+\n+\tskel = struct_ops_arena_attach__open();\n+\tif (!ASSERT_OK_PTR(skel, \"struct_ops_arena_attach__open\"))\n+\t\treturn;\n+\n+\tprog = bpf_object__find_program_by_name(skel-\u003eobj, prog_name);\n+\tif (!ASSERT_OK_PTR(prog, prog_name))\n+\t\tgoto out;\n+\n+\tbpf_object__for_each_program(pos, skel-\u003eobj)\n+\t\tbpf_program__set_autoload(pos, pos == prog);\n+\n+\terr = bpf_program__set_attach_target(prog, target_fd, \"test_arena_cb\");\n+\tif (!ASSERT_OK(err, \"set_attach_target\"))\n+\t\tgoto out;\n+\n+\tlog_buf[0] = '\\0';\n+\tbpf_program__set_log_buf(prog, log_buf, sizeof(log_buf));\n+\terr = struct_ops_arena_attach__load(skel);\n+\n+\tASSERT_EQ(err, -EOPNOTSUPP, prog_name);\n+\tASSERT_HAS_SUBSTR(log_buf, \"Cannot attach to a target with arena context arguments\",\n+\t\t\t \"verifier_log\");\n+\n+out:\n+\tstruct_ops_arena_attach__destroy(skel);\n+}\n+\n+static void arena_arg_attach(void)\n+{\n+\tstruct struct_ops_arena *skel;\n+\tint target_fd;\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+\ttarget_fd = bpf_program__fd(skel-\u003eprogs.test_arena_cb);\n+\tarena_arg_attach_one(target_fd, \"fentry_test_arena\");\n+\tarena_arg_attach_one(target_fd, \"fexit_test_arena\");\n+\tarena_arg_attach_one(target_fd, \"freplace_test_arena\");\n+\n+\tstruct_ops_arena__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+\tif (test__start_subtest(\"arena_arg_attach\"))\n+\t\tarena_arg_attach();\n+#else\n+\ttest__skip();\n+#endif\n+}\ndiff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c\nindex b79bafca68f7a..e7fb3eec78011 100644\n--- a/tools/testing/selftests/bpf/prog_tests/verifier.c\n+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c\n@@ -2,6 +2,8 @@\n \n #include \u003ctest_progs.h\u003e\n \n+#include \"arena_kfunc.skel.h\"\n+#include \"arena_kfunc_jit.skel.h\"\n #include \"cap_helpers.h\"\n #include \"verifier_align.skel.h\"\n #include \"verifier_and.skel.h\"\n@@ -161,6 +163,10 @@ static void run_tests_aux(const char *skel_name,\n \n #define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)\n \n+void test_arena_kfunc(void) { RUN_TESTS(arena_kfunc); }\n+\n+void test_arena_kfunc_jit(void) { RUN_TESTS(arena_kfunc_jit); }\n+\n void test_verifier_align(void) { RUN(verifier_align); }\n void test_verifier_and(void) { RUN(verifier_and); }\n void test_verifier_arena(void) { RUN(verifier_arena); }\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..cdcea889da586\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c\n@@ -0,0 +1,234 @@\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_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..c9b9186626162\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c\n@@ -0,0 +1,98 @@\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\tL0\")\n+__jited(\"\taddq\t%r12, %rdi\")\n+__jited(\"L0:\tcallq\t{{.*}}\")\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\tL0\")\n+__jited(\"\taddq\t%r12, %r8\")\n+__jited(\"L0:\tcallq\t{{.*}}\")\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_attach.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c\nnew file mode 100644\nindex 0000000000000..081a770307e53\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c\n@@ -0,0 +1,25 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003clinux/bpf.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+SEC(\"fentry\")\n+int BPF_PROG(fentry_test_arena, unsigned long long *st_ops_ctx)\n+{\n+\treturn 0;\n+}\n+\n+SEC(\"fexit\")\n+int BPF_PROG(fexit_test_arena, unsigned long long *st_ops_ctx, int ret)\n+{\n+\treturn 0;\n+}\n+\n+SEC(\"freplace\")\n+int freplace_test_arena(unsigned long long *st_ops_ctx)\n+{\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\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 0585794606ed8..a6133f7521f34 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,44 @@ __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_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 +385,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 +426,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 +828,12 @@ 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_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 +849,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..c4383acb53c11 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,15 @@ 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_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 +120,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/08 01:22 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_arena_base",
"bpf_struct_ops_desc_init",
"btf_distill_func_proto",
"bpf_check_attach_target",
"arch_prepare_bpf_trampoline",
"bpf_jit_supports_arena_args",
"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, struct_ops, and trampoline to support passing BPF arena pointers to kfuncs and struct_ops callbacks. This is a significant functional change in the BPF subsystem that should be fuzzed.",
"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 f860347ae84ce23eee0573b82d4ca4aabbbae0e8
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 8 01:22:09 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 021be6d93dfb0..1004eb0bec617 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
@@ -522,6 +559,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 88ed95b2eaa72..8dddb5d7af21b 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)
{
@@ -2588,6 +2632,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;
@@ -2998,11 +3048,39 @@ 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,
+ u64 arena_base)
{
int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
bool use_jmp = bpf_trampoline_use_jmp(flags);
+ int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
int i, j;
/* Store function arguments to stack.
@@ -3011,6 +3089,9 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
* mov QWORD PTR [rbp-0x8],rsi
*/
for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
+ bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+ bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
arg_regs = (m->arg_size[i] + 7) / 8;
/* According to the research of Yonghong, struct members
@@ -3034,16 +3115,19 @@ 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 (arena_arg)
+ emit_arena_arg_conv(prog, BPF_REG_0, nullable,
+ (u32)arena_base);
emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,
-stack_size);
@@ -3064,9 +3148,13 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
/* 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 (arena_arg) {
+ emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
+ src = BPF_REG_0;
+ }
+ emit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);
stack_size -= 8;
nr_regs++;
}
@@ -3362,6 +3450,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
void *orig_call = func_addr;
int cookie_off, cookie_cnt;
u8 **branches = NULL;
+ u64 arena_base;
u64 func_meta;
u8 *prog;
bool save_ret;
@@ -3374,6 +3463,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)));
+ arena_base = bpf_tramp_arena_base(m, tnodes, flags);
+
for (i = 0; i < m->nr_args; i++)
nr_regs += (m->arg_size[i] + 7) / 8 - 1;
@@ -3508,7 +3599,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
emit_store_stack_imm64(&prog, BPF_REG_0, -ip_off, (long)func_addr);
}
- save_args(m, &prog, regs_off, false, flags);
+ save_args(m, &prog, regs_off, false, flags, arena_base);
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* arg1: mov rdi, im */
@@ -3550,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, 0);
if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
/* Before calling the original function, load the
@@ -4051,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 d79bf7557ef68..b4a10c9878cf8 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1195,6 +1195,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;
@@ -1268,6 +1274,15 @@ struct bpf_tramp_nodes {
int nr_nodes;
};
+/*
+ * The arena base against which a struct_ops trampoline converts the
+ * arguments marked with BTF_FMODEL_ARENA_ARG while saving them into the BPF
+ * ctx, ctx[arg] = (u32)(kaddr - kern_vm_start). Zero when the trampoline
+ * converts nothing.
+ */
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+ struct bpf_tramp_nodes *tnodes, u32 flags);
+
struct bpf_tramp_run_ctx;
/* Different use cases for BPF trampoline:
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index a2a40caca0a0e..5c8a4d9b8fce7 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1177,8 +1177,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id)
*btf_id = key & 0x7FFFFFFF;
}
-int bpf_check_btf_info_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr, bpfptr_t uattr);
+int bpf_prepare_btf_info(struct bpf_verifier_env *env,
+ const union bpf_attr *attr, bpfptr_t uattr);
int bpf_check_btf_info(struct bpf_verifier_env *env,
const union bpf_attr *attr, bpfptr_t uattr);
@@ -1302,6 +1302,16 @@ static inline u32 type_flag(u32 type)
return type & ~BPF_BASE_TYPE_MASK;
}
+static inline 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;
+}
+
static inline enum bpf_prog_type resolve_prog_type(const struct bpf_prog *prog)
{
return (prog->type == BPF_PROG_TYPE_EXT && prog->aux->saved_dst_prog_type) ?
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 41b02d53e2228..4edba8182db1b 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1214,6 +1214,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..d7c3030bc63b0 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
@@ -172,10 +174,12 @@ static int prepare_arg_info(struct btf *btf,
const char *st_ops_name,
const char *member_name,
const struct btf_type *func_proto, void *stub_func_addr,
+ struct btf_func_model *model,
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;
@@ -225,27 +229,39 @@ static int prepare_arg_info(struct btf *btf,
/* Prepare info for every nullable argument */
info = info_buf;
for (arg_no = 0; arg_no < nargs; arg_no++) {
- /* Skip arguments that is not suffixed with
- * "__nullable or __ref".
+ bool ptr_to_arena, ptr_to_struct;
+
+ /*
+ * Skip arguments that are not suffixed with "__arena__nullable",
+ * "__arena", "__nullable", or "__ref".
*/
- is_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
- MAYBE_NULL_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);
+ is_nullable = !is_arena_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);
- if (is_nullable)
+ if (is_arena_nullable)
+ suffix = ARENA_MAYBE_NULL_SUFFIX;
+ else if (is_arena)
+ suffix = ARENA_SUFFIX;
+ else if (is_nullable)
suffix = MAYBE_NULL_SUFFIX;
else if (is_refcounted)
suffix = REFCOUNTED_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 or
+ * __arena__nullable.
+ */
+ pointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, &arg_btf_id);
+ ptr_to_arena = pointed_type && (is_arena || is_arena_nullable);
+ ptr_to_struct = pointed_type && btf_type_is_struct(pointed_type);
+ if (!ptr_to_arena && !ptr_to_struct) {
pr_warn("stub function %s has %s tagging to an unsupported type\n",
stub_fname, suffix);
goto err_out;
@@ -268,7 +284,18 @@ static int prepare_arg_info(struct btf *btf,
info->btf_id = arg_btf_id;
info->btf = btf;
info->offset = offset;
- if (is_nullable) {
+ 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;
+ model->arg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;
+ if (is_arena_nullable)
+ model->arg_flags[arg_no] |= BTF_FMODEL_NULLABLE_ARG;
+ } else if (is_nullable) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
} else if (is_refcounted) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID;
@@ -460,6 +487,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
err = prepare_arg_info(btf, st_ops->name, mname,
func_proto, stub_func_addr,
+ &st_ops->func_models[i],
arg_info + i);
if (err)
goto errout;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 42414633cf263..6606187ed4f43 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;
}
@@ -7539,6 +7543,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__nullable"))
+ flags |= BTF_FMODEL_ARENA_ARG | BTF_FMODEL_NULLABLE_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__arena"))
+ flags |= BTF_FMODEL_ARENA_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__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,
@@ -7604,7 +7624,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/check_btf.c b/kernel/bpf/check_btf.c
index 93bebe6fe12e7..0e8b3ccc7a5b9 100644
--- a/kernel/bpf/check_btf.c
+++ b/kernel/bpf/check_btf.c
@@ -28,9 +28,9 @@ static int check_abnormal_return(struct bpf_verifier_env *env)
#define MIN_BPF_FUNCINFO_SIZE 8
#define MAX_FUNCINFO_REC_SIZE 252
-static int check_btf_func_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+static int prepare_btf_func(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
u32 krec_size = sizeof(struct bpf_func_info);
const struct btf_type *type, *func_proto;
@@ -407,9 +407,9 @@ static int check_core_relo(struct bpf_verifier_env *env,
return err;
}
-int bpf_check_btf_info_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+int bpf_prepare_btf_info(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
struct btf *btf;
int err;
@@ -429,7 +429,7 @@ int bpf_check_btf_info_early(struct bpf_verifier_env *env,
}
env->prog->aux->btf = btf;
- err = check_btf_func_early(env, attr, uattr);
+ err = prepare_btf_func(env, attr, uattr);
if (err)
return err;
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..e07af35ed0402 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -529,6 +529,36 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
return tnodes;
}
+/*
+ * The arena base against which save_args() converts the arguments marked
+ * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
+ * converts: it dispatches to a single prog whose arena is known at
+ * generation time. Return 0 when there is nothing to convert.
+ */
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+ struct bpf_tramp_nodes *tnodes, u32 flags)
+{
+ const struct bpf_prog *prog;
+ int i;
+
+ if (!(flags & BPF_TRAMP_F_INDIRECT) ||
+ tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
+ return 0;
+
+ for (i = 0; i < m->nr_args; i++)
+ if (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG)
+ break;
+ if (i == m->nr_args)
+ return 0;
+
+ /* Verification rejects an arena argument without an arena. */
+ prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
+ if (WARN_ON_ONCE(!prog->aux->arena))
+ return 0;
+
+ return bpf_arena_get_kern_vm_start(prog->aux->arena);
+}
+
static void bpf_tramp_image_free(struct bpf_tramp_image *im)
{
bpf_image_ksym_del(&im->ksym);
@@ -920,6 +950,13 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,
int cnt = 0, i;
kind = bpf_attach_type_to_tramp(node->link->prog);
+ /*
+ * Arena ctx args are converted only by struct_ops indirect
+ * trampolines. They must never be attached to a generic trampoline.
+ */
+ if (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node->link->prog)))
+ return -ENOTSUPP;
+
if (tr->extension_prog)
/* cannot attach fentry/fexit if extension prog is attached.
* cannot overwrite extension prog either.
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c9533ea700ba2..4bb3102ae4333 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2842,7 +2842,7 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
return 0;
}
-static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
+static int add_subprogs(struct bpf_verifier_env *env)
{
struct bpf_subprog_info *subprog = env->subprog_info;
int i, ret, insn_cnt = env->prog->len, ex_cb_insn;
@@ -2854,8 +2854,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return ret;
for (i = 0; i < insn_cnt; i++, insn++) {
- if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn) &&
- !bpf_pseudo_kfunc_call(insn))
+ if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn))
continue;
if (!env->bpf_capable) {
@@ -2863,11 +2862,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return -EPERM;
}
- if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn))
- ret = add_subprog(env, i + insn->imm + 1);
- else
- ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
-
+ ret = add_subprog(env, i + insn->imm + 1);
if (ret < 0)
return ret;
}
@@ -2905,6 +2900,28 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return 0;
}
+static int add_kfuncs(struct bpf_verifier_env *env)
+{
+ struct bpf_insn *insn = env->prog->insnsi;
+ int i, ret, insn_cnt = env->prog->len;
+
+ for (i = 0; i < insn_cnt; i++, insn++) {
+ if (!bpf_pseudo_kfunc_call(insn))
+ continue;
+
+ if (!env->bpf_capable) {
+ verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ return -EPERM;
+ }
+
+ ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int check_subprogs(struct bpf_verifier_env *env)
{
int i, subprog_start, subprog_end, off, cur_subprog = 0;
@@ -10919,7 +10936,8 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
{
- return btf_param_match_suffix(btf, arg, "__nullable");
+ return btf_param_match_suffix(btf, arg, "__nullable") ||
+ btf_param_match_suffix(btf, arg, "__arena");
}
static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
@@ -10937,6 +10955,12 @@ 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__nullable") ||
+ btf_param_match_suffix(btf, arg, "__arena");
+}
+
static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
const struct btf_param *arg,
const char *name)
@@ -11157,6 +11181,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 {
@@ -11442,7 +11467,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);
@@ -11491,7 +11515,30 @@ 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 (arg + 1 < nargs &&
+ else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support kfunc %s() with arena pointer arguments\n",
+ meta->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 (reg_from_argno(argno) < 0) {
+ verbose(env, "%s arena pointer cannot be a stack argument\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ /*
+ * Both suffixes accept a constant zero. The function model determines
+ * whether the JIT rebases it to the arena base or preserves NULL.
+ * The common nullable path below records that verifier property.
+ */
+ 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]))) {
if (!btf_type_is_void(ref_t) && !btf_type_is_scalar(ref_t) &&
@@ -12166,7 +12213,7 @@ 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 (btf_type_is_ptr(t) && (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
- !is_kfunc_arg_nullable(meta->btf, &args[i])) {
+ !type_may_be_null(kf_arg_type)) {
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
reg_arg_name(env, argno));
return -EACCES;
@@ -12219,6 +12266,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
case KF_ARG_PTR_TO_TASK_WORK:
case KF_ARG_PTR_TO_IRQ_FLAG:
case KF_ARG_PTR_TO_RES_SPIN_LOCK:
+ case KF_ARG_PTR_TO_ARENA:
break;
case KF_ARG_PTR_TO_DYNPTR:
arg_type = ARG_PTR_TO_DYNPTR;
@@ -12285,6 +12333,13 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
meta->ret_btf_id = ret;
}
break;
+ case KF_ARG_PTR_TO_ARENA:
+ 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;
+ }
+ break;
case KF_ARG_PTR_TO_ALLOC_BTF_ID:
if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
@@ -18820,6 +18875,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;
@@ -18898,10 +18954,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;
+ }
}
}
@@ -18922,8 +18991,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_"
@@ -19190,6 +19258,16 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
bpf_log(log, "Subprog %s doesn't exist\n", tname);
return -EINVAL;
}
+ /*
+ * A struct_ops indirect trampoline converts arena arguments
+ * before invoking its program. A tracing or extension program
+ * attached to the main program would see the converted offset as a
+ * regular BTF pointer.
+ */
+ if (subprog == 0 && bpf_prog_has_arena_ctx_arg(tgt_prog)) {
+ bpf_log(log, "Cannot attach to a target with arena context arguments\n");
+ return -EOPNOTSUPP;
+ }
if (aux->func && aux->func[subprog]->aux->exception_cb) {
bpf_log(log,
"%s programs cannot attach to exception callback\n",
@@ -20325,11 +20403,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
INIT_LIST_HEAD(&env->explored_states[i]);
INIT_LIST_HEAD(&env->free_list);
- ret = bpf_check_btf_info_early(env, attr, uattr);
+ /* Prepare BTF and func_info needed to discover all subprograms. */
+ ret = bpf_prepare_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
- ret = add_subprog_and_kfunc(env);
+ /* Discover all subprograms before validating their layout and BTF. */
+ ret = add_subprogs(env);
if (ret < 0)
goto skip_full_check;
@@ -20337,14 +20417,21 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
+ /* Validate BTF against the complete subprogram layout and apply CO-RE. */
ret = bpf_check_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
+ /* Validate instructions and resolve the program's referenced resources. */
ret = check_and_resolve_insns(env);
if (ret < 0)
goto skip_full_check;
+ /* Build kfunc prototypes after resolving program resources. */
+ ret = add_kfuncs(env);
+ if (ret < 0)
+ goto skip_full_check;
+
if (bpf_prog_is_offloaded(env->prog->aux)) {
ret = bpf_prog_offload_verifier_prep(env->prog);
if (ret)
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..940ec2cda0d52
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
@@ -0,0 +1,128 @@
+// 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_attach.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);
+}
+
+static void arena_arg_attach_one(int target_fd, const char *prog_name)
+{
+ struct struct_ops_arena_attach *skel;
+ struct bpf_program *prog, *pos;
+ char log_buf[64 * 1024];
+ int err;
+
+ skel = struct_ops_arena_attach__open();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_arena_attach__open"))
+ return;
+
+ prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+ if (!ASSERT_OK_PTR(prog, prog_name))
+ goto out;
+
+ bpf_object__for_each_program(pos, skel->obj)
+ bpf_program__set_autoload(pos, pos == prog);
+
+ err = bpf_program__set_attach_target(prog, target_fd, "test_arena_cb");
+ if (!ASSERT_OK(err, "set_attach_target"))
+ goto out;
+
+ log_buf[0] = '\0';
+ bpf_program__set_log_buf(prog, log_buf, sizeof(log_buf));
+ err = struct_ops_arena_attach__load(skel);
+
+ ASSERT_EQ(err, -EOPNOTSUPP, prog_name);
+ ASSERT_HAS_SUBSTR(log_buf, "Cannot attach to a target with arena context arguments",
+ "verifier_log");
+
+out:
+ struct_ops_arena_attach__destroy(skel);
+}
+
+static void arena_arg_attach(void)
+{
+ struct struct_ops_arena *skel;
+ int target_fd;
+
+ skel = struct_ops_arena__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_arena__open_and_load"))
+ return;
+
+ target_fd = bpf_program__fd(skel->progs.test_arena_cb);
+ arena_arg_attach_one(target_fd, "fentry_test_arena");
+ arena_arg_attach_one(target_fd, "fexit_test_arena");
+ arena_arg_attach_one(target_fd, "freplace_test_arena");
+
+ struct_ops_arena__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();
+ if (test__start_subtest("arena_arg_attach"))
+ arena_arg_attach();
+#else
+ test__skip();
+#endif
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index b79bafca68f7a..e7fb3eec78011 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -2,6 +2,8 @@
#include <test_progs.h>
+#include "arena_kfunc.skel.h"
+#include "arena_kfunc_jit.skel.h"
#include "cap_helpers.h"
#include "verifier_align.skel.h"
#include "verifier_and.skel.h"
@@ -161,6 +163,10 @@ static void run_tests_aux(const char *skel_name,
#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)
+void test_arena_kfunc(void) { RUN_TESTS(arena_kfunc); }
+
+void test_arena_kfunc_jit(void) { RUN_TESTS(arena_kfunc_jit); }
+
void test_verifier_align(void) { RUN(verifier_align); }
void test_verifier_and(void) { RUN(verifier_and); }
void test_verifier_arena(void) { RUN(verifier_arena); }
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..cdcea889da586
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -0,0 +1,234 @@
+// 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_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..c9b9186626162
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
@@ -0,0 +1,98 @@
+// 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 L0")
+__jited(" addq %r12, %rdi")
+__jited("L0: callq {{.*}}")
+__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 L0")
+__jited(" addq %r12, %r8")
+__jited("L0: callq {{.*}}")
+__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_attach.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c
new file mode 100644
index 0000000000000..081a770307e53
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+SEC("fentry")
+int BPF_PROG(fentry_test_arena, unsigned long long *st_ops_ctx)
+{
+ return 0;
+}
+
+SEC("fexit")
+int BPF_PROG(fexit_test_arena, unsigned long long *st_ops_ctx, int ret)
+{
+ return 0;
+}
+
+SEC("freplace")
+int freplace_test_arena(unsigned long long *st_ops_ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
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 0585794606ed8..a6133f7521f34 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -237,6 +237,44 @@ __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_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 +385,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 +426,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 +828,12 @@ 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_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 +849,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..c4383acb53c11 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,15 @@ 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_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 +120,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/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26330 output: 24 thoughts: 680 Thoughts: **My Initial Assessment of the BPF Arena Patch** Okay, so I'm looking at this patch introducing `__arena` and `__arena__nullable` annotations for BPF kfuncs and struct_ops arguments. This is a significant functional change, impacting the BPF subsystem quite broadly. The verifier, JIT, struct_ops, and trampoline mechanisms are all touched, so this is definitely prime territory for fuzzing. Arena pointers in BPF kfuncs and struct_ops are new enough to warrant a focused investigation. My first thought is to identify the critical functions to pay attention to. Let's see... in `arch/x86/net/bpf_jit_comp.c`, I've got `emit_kfunc_arena_args`, `emit_arena_arg_conv`, and `bpf_jit_supports_arena_args` to consider for the JIT side of things. Then, in `kernel/bpf/bpf_struct_ops.c`, `prepare_arg_info` is modified, which is central to that functionality. The BTF changes are extensive as well. `btf_ctx_access` and `btf_distill_func_proto` have been touched, and we have a new `__get_arg_fmodel_flags`. Then, in `kernel/bpf/trampoline.c`, there's `bpf_tramp_arena_base`. The verifier is where things get really interesting, though. `add_subprogs` (renamed from `add_subprog_and_kfunc`) is updated, along with `add_kfuncs` (completely new). `is_kfunc_arg_arena` is also brand new, and I'll need to check the modified `get_kfunc_arg_type`, `check_kfunc_args`, `check_struct_ops_btf_id`, `bpf_check_attach_target`, and `bpf_check` functions. I need to make sure to check if any specific kernel configurations are required. BPF features usually need `CONFIG_BPF_SYSCALL` and `CONFIG_BPF_JIT`. Since BPF arena is new, it's very likely it needs `CONFIG_BPF_SYSCALL` at minimum. I'll need to double-check `kernel/bpf/Kconfig` or similar files. Wait, this *is* a new feature, so there might be a dedicated config for it. I'll search for `BPF_ARENA` or just `ARENA` in the Kconfig files. This will be my next step to confirm my setup for testing and fuzzing this patch. |
| 4/2 | 2026/08/08 01:22 | tool | grepper |
0mArgs:{
"Expression": "config BPF_",
"PathPrefix": ""
}Results: {
"Output": "drivers/media/rc/Kconfig=17=if RC_CORE\ndrivers/media/rc/Kconfig-18-\ndrivers/media/rc/Kconfig:19:config BPF_LIRC_MODE2\ndrivers/media/rc/Kconfig-20-\tbool \"Support for eBPF programs attached to lirc devices\"\n--\nkernel/Kconfig.locks=242=config QUEUED_SPINLOCKS\n--\nkernel/Kconfig.locks-245-\nkernel/Kconfig.locks:246:config BPF_ARCH_SPINLOCK\nkernel/Kconfig.locks-247-\tbool\n--\nkernel/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--\nkernel/bpf/preload/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nkernel/bpf/preload/Kconfig:2:menuconfig BPF_PRELOAD\nkernel/bpf/preload/Kconfig-3-\tbool \"Preload BPF file system with kernel specific program and map iterators\"\n--\nkernel/bpf/preload/Kconfig=14=if BPF_PRELOAD\nkernel/bpf/preload/Kconfig:15:config BPF_PRELOAD_UMD\nkernel/bpf/preload/Kconfig-16-\ttristate \"bpf_preload kernel module\"\n--\nkernel/trace/Kconfig=839=config EPROBE_EVENTS\n--\nkernel/trace/Kconfig-852-\nkernel/trace/Kconfig:853:config BPF_EVENTS\nkernel/trace/Kconfig-854-\tdepends on BPF_SYSCALL\n--\nkernel/trace/Kconfig=865=config PROBE_EVENTS\n--\nkernel/trace/Kconfig-867-\nkernel/trace/Kconfig:868:config BPF_KPROBE_OVERRIDE\nkernel/trace/Kconfig-869-\tbool \"Enable BPF programs to override a kprobed function\"\n--\nnet/Kconfig=354=config BQL\n--\nnet/Kconfig-360-\nnet/Kconfig:361:config BPF_STREAM_PARSER\nnet/Kconfig-362-\tbool \"enable BPF STREAM_PARSER\"\n"
} |
| 5/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28552 output: 27 thoughts: 30 Thoughts: Okay, here's my take on those thoughts, framed as my own reasoning: **Checking for Arena-Specific Configuration in a BPF Context** Right, so we're dealing with BPF, and we can assume the standard components like `BPF_SYSCALL` and the `BPF_JIT` are in play. No surprises there. But, I need to dig deeper, specifically regarding ARENA. I'm going to start by looking for any custom configuration or specific flags. There might be some optimization or behavior tied directly to the `ARENA` memory allocation scheme, or perhaps some interaction with the JIT. It's a key area to explore to understand any potentially non-standard behavior or unique characteristics. I'll need to scrutinize the config. |
| 6/2 | 2026/08/08 01:22 | tool | grepper |
0mArgs:{
"Expression": "ARENA",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/arena.c=86=u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena)\n--\nkernel/bpf/arena.c-92- * bpf_arena_map_kern_vm_start - kern_vm_start lookup by struct bpf_map *\nkernel/bpf/arena.c:93: * @map: a BPF_MAP_TYPE_ARENA map\nkernel/bpf/arena.c-94- *\n--\nkernel/bpf/arena.c=1049=__bpf_kfunc void *bpf_arena_alloc_pages(void *p__map, void *addr__ign, u32 page_cnt,\n--\nkernel/bpf/arena.c-1054-\nkernel/bpf/arena.c:1055:\tif (map-\u003emap_type != BPF_MAP_TYPE_ARENA || flags || !page_cnt)\nkernel/bpf/arena.c-1056-\t\treturn NULL;\n--\nkernel/bpf/arena.c=1061=void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 page_cnt,\n--\nkernel/bpf/arena.c-1066-\nkernel/bpf/arena.c:1067:\tif (map-\u003emap_type != BPF_MAP_TYPE_ARENA || flags || !page_cnt)\nkernel/bpf/arena.c-1068-\t\treturn NULL;\n--\nkernel/bpf/arena.c=1073=void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt,\n--\nkernel/bpf/arena.c-1078-\nkernel/bpf/arena.c:1079:\tif (map-\u003emap_type != BPF_MAP_TYPE_ARENA || flags || !page_cnt)\nkernel/bpf/arena.c-1080-\t\treturn NULL;\n--\nkernel/bpf/arena.c=1085=__bpf_kfunc void bpf_arena_free_pages(void *p__map, void *ptr__ign, u32 page_cnt)\n--\nkernel/bpf/arena.c-1089-\nkernel/bpf/arena.c:1090:\tif (map-\u003emap_type != BPF_MAP_TYPE_ARENA || !page_cnt || !ptr__ign)\nkernel/bpf/arena.c-1091-\t\treturn;\n--\nkernel/bpf/arena.c=1095=void bpf_arena_free_pages_non_sleepable(void *p__map, void *ptr__ign, u32 page_cnt)\n--\nkernel/bpf/arena.c-1099-\nkernel/bpf/arena.c:1100:\tif (map-\u003emap_type != BPF_MAP_TYPE_ARENA || !page_cnt || !ptr__ign)\nkernel/bpf/arena.c-1101-\t\treturn;\n--\nkernel/bpf/arena.c=1105=__bpf_kfunc int bpf_arena_reserve_pages(void *p__map, void *ptr__ign, u32 page_cnt)\n--\nkernel/bpf/arena.c-1109-\nkernel/bpf/arena.c:1110:\tif (map-\u003emap_type != BPF_MAP_TYPE_ARENA)\nkernel/bpf/arena.c-1111-\t\treturn -EINVAL;\n--\nkernel/bpf/arena.c=1120=BTF_KFUNCS_START(arena_kfuncs)\nkernel/bpf/arena.c:1121:BTF_ID_FLAGS(func, bpf_arena_alloc_pages, KF_ARENA_RET | KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\nkernel/bpf/arena.c:1122:BTF_ID_FLAGS(func, bpf_arena_free_pages, KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\nkernel/bpf/arena.c:1123:BTF_ID_FLAGS(func, bpf_arena_reserve_pages, KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\nkernel/bpf/arena.c-1124-BTF_KFUNCS_END(arena_kfuncs)\n--\nkernel/bpf/bpf_struct_ops.c=140=void bpf_struct_ops_image_free(void *image)\n--\nkernel/bpf/bpf_struct_ops.c-149-#define REFCOUNTED_SUFFIX \"__ref\"\nkernel/bpf/bpf_struct_ops.c:150:#define ARENA_SUFFIX \"__arena\"\nkernel/bpf/bpf_struct_ops.c:151:#define ARENA_MAYBE_NULL_SUFFIX \"__arena__nullable\"\nkernel/bpf/bpf_struct_ops.c-152-\n--\nkernel/bpf/bpf_struct_ops.c=173=static int prepare_arg_info(struct btf *btf,\n--\nkernel/bpf/bpf_struct_ops.c-238-\t\tis_arena_nullable = btf_param_match_suffix(btf, \u0026stub_args[arg_no],\nkernel/bpf/bpf_struct_ops.c:239:\t\t\t\t\t\t\t ARENA_MAYBE_NULL_SUFFIX);\nkernel/bpf/bpf_struct_ops.c:240:\t\tis_arena = btf_param_match_suffix(btf, \u0026stub_args[arg_no], ARENA_SUFFIX);\nkernel/bpf/bpf_struct_ops.c-241-\t\tis_nullable = !is_arena_nullable \u0026\u0026\n--\nkernel/bpf/bpf_struct_ops.c-246-\t\tif (is_arena_nullable)\nkernel/bpf/bpf_struct_ops.c:247:\t\t\tsuffix = ARENA_MAYBE_NULL_SUFFIX;\nkernel/bpf/bpf_struct_ops.c-248-\t\telse if (is_arena)\nkernel/bpf/bpf_struct_ops.c:249:\t\t\tsuffix = ARENA_SUFFIX;\nkernel/bpf/bpf_struct_ops.c-250-\t\telse if (is_nullable)\n--\nkernel/bpf/bpf_struct_ops.c-288-\t\t\t/*\nkernel/bpf/bpf_struct_ops.c:289:\t\t\t * Both types get PTR_TO_ARENA. In verifier state,\nkernel/bpf/bpf_struct_ops.c:290:\t\t\t * PTR_TO_ARENA encompasses potential NULL values, but\nkernel/bpf/bpf_struct_ops.c-291-\t\t\t * we do not force the program to check it, or maintain\n--\nkernel/bpf/bpf_struct_ops.c-293-\t\t\t */\nkernel/bpf/bpf_struct_ops.c:294:\t\t\tinfo-\u003ereg_type = PTR_TO_ARENA;\nkernel/bpf/bpf_struct_ops.c:295:\t\t\tmodel-\u003earg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;\nkernel/bpf/bpf_struct_ops.c-296-\t\t\tif (is_arena_nullable)\n--\nkernel/bpf/btf.c=6850=bool btf_ctx_access(int off, int size, enum bpf_access_type type,\n--\nkernel/bpf/btf.c-6967-\t * Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL or\nkernel/bpf/btf.c:6968:\t * PTR_TO_ARENA (both nullable and non-nullable cases).\nkernel/bpf/btf.c-6969-\t */\n--\nkernel/bpf/btf.c-6976-\t\tif (ctx_arg_info-\u003eoffset == off \u0026\u0026\nkernel/bpf/btf.c:6977:\t\t (type == PTR_TO_ARENA ||\nkernel/bpf/btf.c-6978-\t\t (type == PTR_TO_BUF \u0026\u0026 (flag \u0026 PTR_MAYBE_NULL)))) {\n--\nkernel/bpf/btf.c=7546=static u8 __get_arg_fmodel_flags(const struct btf *btf,\n--\nkernel/bpf/btf.c-7552-\tif (btf_param_match_suffix(btf, arg, \"__arena__nullable\"))\nkernel/bpf/btf.c:7553:\t\tflags |= BTF_FMODEL_ARENA_ARG | BTF_FMODEL_NULLABLE_ARG;\nkernel/bpf/btf.c-7554-\telse if (btf_param_match_suffix(btf, arg, \"__arena\"))\nkernel/bpf/btf.c:7555:\t\tflags |= BTF_FMODEL_ARENA_ARG;\nkernel/bpf/btf.c-7556-\telse if (btf_param_match_suffix(btf, arg, \"__nullable\"))\n--\nkernel/bpf/btf.c=7861=enum btf_arg_tag {\n--\nkernel/bpf/btf.c-7866-\tARG_TAG_NULLABLE = BIT_ULL(4),\nkernel/bpf/btf.c:7867:\tARG_TAG_ARENA\t = BIT_ULL(5),\nkernel/bpf/btf.c-7868-};\n--\nkernel/bpf/btf.c=7870=static int btf_scan_decl_tags(struct bpf_verifier_env *env,\n--\nkernel/bpf/btf.c-7885-\t\t{ \"nullable\", ARG_TAG_NULLABLE },\nkernel/bpf/btf.c:7886:\t\t{ \"arena\", ARG_TAG_ARENA },\nkernel/bpf/btf.c-7887-\t};\n--\nkernel/bpf/btf.c=7932=static int btf_scan_type_tags(struct bpf_verifier_env *env,\n--\nkernel/bpf/btf.c-7936-\tstatic const struct btf_type_tag_match func_type_tags[] = {\nkernel/bpf/btf.c:7937:\t\t{ \"arena\", ARG_TAG_ARENA },\nkernel/bpf/btf.c-7938-\t};\n--\nkernel/bpf/btf.c=7967=static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *btf,\n--\nkernel/bpf/btf.c-7983-\t */\nkernel/bpf/btf.c:7984:\tif (subprog \u0026\u0026 (tags \u0026 ARG_TAG_ARENA) \u0026\u0026 btf_type_is_ptr(t))\nkernel/bpf/btf.c-7985-\t\treturn 0;\n--\nkernel/bpf/btf.c=8002=int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)\n--\nkernel/bpf/btf.c-8177-\t\t}\nkernel/bpf/btf.c:8178:\t\tif (tags \u0026 ARG_TAG_ARENA) {\nkernel/bpf/btf.c:8179:\t\t\tif (tags \u0026 ~ARG_TAG_ARENA) {\nkernel/bpf/btf.c-8180-\t\t\t\tbpf_log(log, \"arg#%d arena cannot be combined with any other tags\\n\", i);\n--\nkernel/bpf/btf.c-8182-\t\t\t}\nkernel/bpf/btf.c:8183:\t\t\tsub-\u003eargs[i].arg_type = ARG_PTR_TO_ARENA;\nkernel/bpf/btf.c-8184-\t\t\tcontinue;\n--\nkernel/bpf/fixups.c=688=int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)\n--\nkernel/bpf/fixups.c-806-\t\t\t insn-\u003ecode == (BPF_STX | BPF_ATOMIC | BPF_DW)) \u0026\u0026\nkernel/bpf/fixups.c:807:\t\t\t env-\u003einsn_aux_data[i + delta].ptr_type == PTR_TO_ARENA) {\nkernel/bpf/fixups.c-808-\t\t\tinsn-\u003ecode = BPF_STX | BPF_PROBE_ATOMIC | BPF_SIZE(insn-\u003ecode);\n--\nkernel/bpf/fixups.c-889-\t\t\tcontinue;\nkernel/bpf/fixups.c:890:\t\tcase PTR_TO_ARENA:\nkernel/bpf/fixups.c-891-\t\t\tif (BPF_MODE(insn-\u003ecode) == BPF_MEMSX) {\n--\nkernel/bpf/log.c=399=const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type)\n--\nkernel/bpf/log.c-419-\t\t[PTR_TO_MEM]\t\t= \"mem\",\nkernel/bpf/log.c:420:\t\t[PTR_TO_ARENA]\t\t= \"arena\",\nkernel/bpf/log.c-421-\t\t[PTR_TO_BUF]\t\t= \"buf\",\n--\nkernel/bpf/log.c=635=static void print_reg_state(struct bpf_verifier_env *env,\n--\nkernel/bpf/log.c-650-\tverbose(env, \"%s\", reg_type_str(env, t));\nkernel/bpf/log.c:651:\tif (t == PTR_TO_ARENA)\nkernel/bpf/log.c-652-\t\treturn;\n--\nkernel/bpf/states.c=507=static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,\n--\nkernel/bpf/states.c-648-\t\treturn regs_exact(rold, rcur, idmap) \u0026\u0026 rold-\u003eframeno == rcur-\u003eframeno;\nkernel/bpf/states.c:649:\tcase PTR_TO_ARENA:\nkernel/bpf/states.c-650-\t\treturn true;\n--\nkernel/bpf/syscall.c=250=static int bpf_map_update_value(struct bpf_map *map, struct file *map_file,\n--\nkernel/bpf/syscall.c-258-\t} else if (map-\u003emap_type == BPF_MAP_TYPE_CPUMAP ||\nkernel/bpf/syscall.c:259:\t\t map-\u003emap_type == BPF_MAP_TYPE_ARENA ||\nkernel/bpf/syscall.c-260-\t\t map-\u003emap_type == BPF_MAP_TYPE_STRUCT_OPS) {\n--\nkernel/bpf/syscall.c=1370=static int map_create_alloc(union bpf_attr *attr, bpfptr_t uattr, struct bpf_verifier_log *log,\n--\nkernel/bpf/syscall.c-1407-\tif (attr-\u003emap_type != BPF_MAP_TYPE_BLOOM_FILTER \u0026\u0026\nkernel/bpf/syscall.c:1408:\t attr-\u003emap_type != BPF_MAP_TYPE_ARENA \u0026\u0026\nkernel/bpf/syscall.c-1409-\t attr-\u003emap_type != BPF_MAP_TYPE_RHASH \u0026\u0026\n--\nkernel/bpf/syscall.c-1503-\tcase BPF_MAP_TYPE_CPUMAP:\nkernel/bpf/syscall.c:1504:\tcase BPF_MAP_TYPE_ARENA:\nkernel/bpf/syscall.c-1505-\tcase BPF_MAP_TYPE_INSN_ARRAY:\n--\nkernel/bpf/trampoline.c=508=bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_arg)\n--\nkernel/bpf/trampoline.c-533- * The arena base against which save_args() converts the arguments marked\nkernel/bpf/trampoline.c:534: * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline\nkernel/bpf/trampoline.c-535- * converts: it dispatches to a single prog whose arena is known at\n--\nkernel/bpf/trampoline.c=538=u64 bpf_tramp_arena_base(const struct btf_func_model *m,\n--\nkernel/bpf/trampoline.c-548-\tfor (i = 0; i \u003c m-\u003enr_args; i++)\nkernel/bpf/trampoline.c:549:\t\tif (m-\u003earg_flags[i] \u0026 BTF_FMODEL_ARENA_ARG)\nkernel/bpf/trampoline.c-550-\t\t\tbreak;\n--\nkernel/bpf/verifier.c=4936=static bool is_arena_reg(struct bpf_verifier_env *env, int regno)\n--\nkernel/bpf/verifier.c-4939-\nkernel/bpf/verifier.c:4940:\treturn reg-\u003etype == PTR_TO_ARENA;\nkernel/bpf/verifier.c-4941-}\n--\nkernel/bpf/verifier.c=5102=static int check_ptr_alignment(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-5149-\t\tbreak;\nkernel/bpf/verifier.c:5150:\tcase PTR_TO_ARENA:\nkernel/bpf/verifier.c-5151-\t\treturn 0;\n--\nkernel/bpf/verifier.c=6211=static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct bpf_reg_state *reg, argno_t argno,\n--\nkernel/bpf/verifier.c-6450-\t\t\tmark_reg_unknown(env, regs, value_regno);\nkernel/bpf/verifier.c:6451:\t} else if (reg-\u003etype == PTR_TO_ARENA) {\nkernel/bpf/verifier.c-6452-\t\tif (t == BPF_READ \u0026\u0026 value_regno \u003e= 0)\n--\nkernel/bpf/verifier.c=6567=static int check_atomic_rmw(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-6646-\tif (is_arena_reg(env, insn-\u003edst_reg)) {\nkernel/bpf/verifier.c:6647:\t\terr = save_aux_ptr_type(env, PTR_TO_ARENA, false);\nkernel/bpf/verifier.c-6648-\t\tif (err)\n--\nkernel/bpf/verifier.c=8161=static int __check_func_arg_reg_off(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-8206-\tcase PTR_TO_BUF | MEM_RDONLY:\nkernel/bpf/verifier.c:8207:\tcase PTR_TO_ARENA:\nkernel/bpf/verifier.c-8208-\tcase SCALAR_VALUE:\n--\nkernel/bpf/verifier.c=9296=static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n--\nkernel/bpf/verifier.c-9363-\t\t\t}\nkernel/bpf/verifier.c:9364:\t\t} else if (base_type(arg-\u003earg_type) == ARG_PTR_TO_ARENA) {\nkernel/bpf/verifier.c-9365-\t\t\t/*\nkernel/bpf/verifier.c-9366-\t\t\t * Can pass any value and the kernel won't crash, but\nkernel/bpf/verifier.c:9367:\t\t\t * only PTR_TO_ARENA or SCALAR make sense. Everything\nkernel/bpf/verifier.c-9368-\t\t\t * else is a bug in the bpf program. Point it out to\n--\nkernel/bpf/verifier.c-9371-\t\t\t */\nkernel/bpf/verifier.c:9372:\t\t\tif (reg-\u003etype != PTR_TO_ARENA \u0026\u0026 reg-\u003etype != SCALAR_VALUE) {\nkernel/bpf/verifier.c-9373-\t\t\t\tbpf_log(log, \"%s is not a pointer to arena or scalar.\\n\",\n--\nkernel/bpf/verifier.c=11159=enum kfunc_ptr_arg_type {\n--\nkernel/bpf/verifier.c-11183-\tKF_ARG_PTR_TO_TASK_WORK,\nkernel/bpf/verifier.c:11184:\tKF_ARG_PTR_TO_ARENA,\nkernel/bpf/verifier.c-11185-};\n--\nkernel/bpf/verifier.c=11441=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-11539-\t\t */\nkernel/bpf/verifier.c:11540:\t\targ_type = KF_ARG_PTR_TO_ARENA;\nkernel/bpf/verifier.c-11541-\t} else if (arg + 1 \u003c nargs \u0026\u0026\n--\nkernel/bpf/verifier.c=12162=static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12268-\t\tcase KF_ARG_PTR_TO_RES_SPIN_LOCK:\nkernel/bpf/verifier.c:12269:\t\tcase KF_ARG_PTR_TO_ARENA:\nkernel/bpf/verifier.c-12270-\t\t\tbreak;\n--\nkernel/bpf/verifier.c-12335-\t\t\tbreak;\nkernel/bpf/verifier.c:12336:\t\tcase KF_ARG_PTR_TO_ARENA:\nkernel/bpf/verifier.c:12337:\t\t\tif (reg-\u003etype != PTR_TO_ARENA \u0026\u0026 reg-\u003etype != SCALAR_VALUE) {\nkernel/bpf/verifier.c-12338-\t\t\t\tverbose(env, \"%s is not a pointer to arena or scalar\\n\",\n--\nkernel/bpf/verifier.c=14944=static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-14961-\t/* Case where at least one operand is an arena. */\nkernel/bpf/verifier.c:14962:\tif (dst_reg-\u003etype == PTR_TO_ARENA || (src_reg \u0026\u0026 src_reg-\u003etype == PTR_TO_ARENA)) {\nkernel/bpf/verifier.c-14963-\t\tstruct bpf_insn_aux_data *aux = cur_aux(env);\nkernel/bpf/verifier.c-14964-\nkernel/bpf/verifier.c:14965:\t\tif (dst_reg-\u003etype != PTR_TO_ARENA)\nkernel/bpf/verifier.c-14966-\t\t\t*dst_reg = *src_reg;\n--\nkernel/bpf/verifier.c=15112=static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-15171-\t\t\t\t\tif (insn-\u003eimm == 1) { /* cast from as(1) to as(0) */\nkernel/bpf/verifier.c:15172:\t\t\t\t\t\tdst_reg-\u003etype = PTR_TO_ARENA;\nkernel/bpf/verifier.c:15173:\t\t\t\t\t\t/* PTR_TO_ARENA is 32-bit */\nkernel/bpf/verifier.c-15174-\t\t\t\t\t\tdst_reg-\u003esubreg_def = env-\u003einsn_idx + 1;\n--\nkernel/bpf/verifier.c=16379=static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-16450-\t insn-\u003esrc_reg == BPF_PSEUDO_MAP_IDX_VALUE) {\nkernel/bpf/verifier.c:16451:\t\tif (map-\u003emap_type == BPF_MAP_TYPE_ARENA) {\nkernel/bpf/verifier.c-16452-\t\t\t__mark_reg_unknown(env, dst_reg);\n--\nkernel/bpf/verifier.c=17174=static bool reg_type_mismatch_ok(enum bpf_reg_type type)\n--\nkernel/bpf/verifier.c-17182-\tcase PTR_TO_BTF_ID:\nkernel/bpf/verifier.c:17183:\tcase PTR_TO_ARENA:\nkernel/bpf/verifier.c-17184-\t\treturn false;\n--\nkernel/bpf/verifier.c=17935=static int check_map_prog_compatibility(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-18000-\t\tcase BPF_MAP_TYPE_STACK:\nkernel/bpf/verifier.c:18001:\t\tcase BPF_MAP_TYPE_ARENA:\nkernel/bpf/verifier.c-18002-\t\tcase BPF_MAP_TYPE_INSN_ARRAY:\n--\nkernel/bpf/verifier.c-18016-\nkernel/bpf/verifier.c:18017:\tif (map-\u003emap_type == BPF_MAP_TYPE_ARENA) {\nkernel/bpf/verifier.c-18018-\t\tif (env-\u003eprog-\u003eaux-\u003earena) {\n--\nkernel/bpf/verifier.c=18601=static int do_check_common(struct bpf_verifier_env *env, int subprog)\n--\nkernel/bpf/verifier.c-18697-\t\t\t\treg-\u003eid = ++env-\u003eid_gen;\nkernel/bpf/verifier.c:18698:\t\t\t} else if (base_type(arg-\u003earg_type) == ARG_PTR_TO_ARENA) {\nkernel/bpf/verifier.c:18699:\t\t\t\t/* caller can pass either PTR_TO_ARENA or SCALAR */\nkernel/bpf/verifier.c-18700-\t\t\t\tmark_reg_unknown(env, regs, i);\n--\nkernel/bpf/verifier.c=18874=static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-18962-\t\t\thas_refcounted_arg = true;\nkernel/bpf/verifier.c:18963:\t\tif (base_type(info-\u003ereg_type) == PTR_TO_ARENA) {\nkernel/bpf/verifier.c-18964-\t\t\tif (!bpf_jit_supports_arena_args()) {\n"
} |
| 7/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 35679 output: 45 thoughts: 1044 Thoughts: **Analyzing the Scope of Arena Argument Support in BPF** Okay, so I'm diving into this BPF patch related to arena arguments. My initial thought is that we're dealing with both `BPF_SYSCALL` and, crucially, `BPF_JIT`. I need to confirm if that's the full scope or if there are other relevant configurations. The patch itself seems to introduce `__arena` and `__arena__nullable` annotations. This points me directly to kfuncs and struct_ops, which are key areas to examine. Let's make a list of relevant functions I should pay close attention to as I analyze the code. Initially, I've got: * `emit_kfunc_arena_args` * `emit_arena_arg_conv` * `bpf_tramp_arena_base` * `prepare_arg_info` (in `kernel/bpf/bpf_struct_ops.c`) * `__get_arg_fmodel_flags` * `btf_distill_func_proto` * `add_kfuncs` * `get_kfunc_arg_type` * `check_kfunc_args` * `check_struct_ops_btf_id` Now, looking deeper, I see that both `emit_kfunc_arena_args` and `emit_arena_arg_conv` are actually located in `arch/x86/net/bpf_jit_comp.c`. That's an important detail to include. I will methodically go through these functions to understand what's changed and how. Okay, `prepare_arg_info` is static in `kernel/bpf/bpf_struct_ops.c`, `__get_arg_fmodel_flags` is static in `kernel/bpf/btf.c`, and I've got `bpf_tramp_arena_base` which is non-static in `kernel/bpf/trampoline.c`. From the verifier, I see `add_kfuncs`, `get_kfunc_arg_type`, `check_kfunc_args`, and `check_struct_ops_btf_id` are all static. My general preference in this analysis is to identify non-static API entrypoints. Those are the functions that other code will call, and which might be the true "hooks" for the arena argument changes. So, let me make another list, this time focused on functions that are *not* static, or functions that are public and exposed: * `bpf_tramp_arena_base` (in `kernel/bpf/trampoline.c`) * `bpf_prepare_btf_info` (in `kernel/bpf/check_btf.c`) * `btf_distill_func_proto` (in `kernel/bpf/btf.c`) * `bpf_struct_ops_desc_init` (in `kernel/bpf/bpf_struct_ops.c`) * `bpf_check_attach_target` (in `kernel/bpf/verifier.c`) * `bpf_check` (in `kernel/bpf/verifier.c`) * `bpf_jit_supports_arena_args` (in `arch/x86/net/bpf_jit_comp.c` and `kernel/bpf/core.c`) Now, let me refine this list a bit, keeping in mind the need for more focused entry points. `bpf_check` is far too generic. `bpf_struct_ops_desc_init`, `bpf_tramp_arena_base`, `btf_distill_func_proto`, and `bpf_prepare_btf_info` are all good, targeted functions. So, digging deeper, I see `emit_kfunc_arena_args` and `emit_arena_arg_conv` are static in `arch/x86/net/bpf_jit_comp.c`, but they're called from other static functions, such as `do_jit` and `save_args`. The real entry points for JIT compilation, however, are `bpf_int_jit_compile` and `arch_prepare_bpf_trampoline`. In the context of this arena argument work, `arch_prepare_bpf_trampoline` is a good candidate. I'll check the verifier entry points. `bpf_check` is the main entry point, but it's too general. |
| 8/2 | 2026/08/08 01:22 | 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=3199=static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog,\n--\narch/x86/net/bpf_jit_comp.c-3215-\t * bpf_tramp_run_ctx is already preserved by\narch/x86/net/bpf_jit_comp.c:3216:\t * arch_prepare_bpf_trampoline().\narch/x86/net/bpf_jit_comp.c-3217-\t *\n--\narch/x86/net/bpf_jit_comp.c=3337=static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,\n--\narch/x86/net/bpf_jit_comp.c-3438- */\narch/x86/net/bpf_jit_comp.c:3439:static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\narch/x86/net/bpf_jit_comp.c-3440-\t\t\t\t\t void *rw_image_end, void *image,\n--\narch/x86/net/bpf_jit_comp.c=3757=int arch_protect_bpf_trampoline(void *image, unsigned int size)\n--\narch/x86/net/bpf_jit_comp.c-3761-\narch/x86/net/bpf_jit_comp.c:3762:int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *image_end,\narch/x86/net/bpf_jit_comp.c-3763-\t\t\t\tconst struct btf_func_model *m, u32 flags,\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"
} |
| 9/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 38651 output: 44 thoughts: 68 Thoughts: Here's my summary, as though I'm thinking through this process: **Focusing on the BPF Trampoline Preparation** Okay, so `arch_prepare_bpf_trampoline` isn't static, which immediately makes it a potentially interesting entry point. Let's see...aha, the patch *specifically* touches `__arch_prepare_bpf_trampoline`. That definitely solidifies `arch_prepare_bpf_trampoline` as a worthwhile symbol to zero in on. Good. Now, where does it fit in? Let's take a look at the bigger picture, maybe checking what `bpf_int_jit_compile` does. It might give me some context. |
| 10/2 | 2026/08/08 01:22 | tool | grepper |
0mArgs:{
"Expression": "bpf_int_jit_compile",
"PathPrefix": "arch/x86/net/bpf_jit_comp.c"
}Results: {
"Output": "arch/x86/net/bpf_jit_comp.c=3931=struct x64_jit_data {\n--\narch/x86/net/bpf_jit_comp.c-3942-\narch/x86/net/bpf_jit_comp.c:3943:struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\narch/x86/net/bpf_jit_comp.c-3944-{\n"
} |
| 11/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 39063 output: 38 thoughts: 45 Thoughts: Here's my thought process, summarized as you requested: **Navigating the BPF Landscape** Okay, so I'm thinking about this `bpf_int_jit_compile` function. I *do* remember it being non-static, which is an important detail for how we might interact with it. Now, where was that `bpf_tramp_arena_base` variable located again? Right, it's tied to the trampoline mechanism. I need to make sure I understand the scope of how that variable is used, and how it relates to the JIT compilation process. Let me quickly double-check the `kernel/bpf/trampoline.c` file. Hopefully, that will refresh my memory. |
| 12/2 | 2026/08/08 01:22 | tool | grepper |
0mArgs:{
"Expression": "bpf_tramp_arena_base",
"PathPrefix": "kernel/bpf/trampoline.c"
}Results: {
"Output": "kernel/bpf/trampoline.c=508=bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_arg)\n--\nkernel/bpf/trampoline.c-537- */\nkernel/bpf/trampoline.c:538:u64 bpf_tramp_arena_base(const struct btf_func_model *m,\nkernel/bpf/trampoline.c-539-\t\t\t struct bpf_tramp_nodes *tnodes, u32 flags)\n"
} |
| 13/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 39448 output: 43 thoughts: 51 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing `bpf_tramp_arena_base` and `bpf_struct_ops_desc_init`** Alright, so the first thing that clicks is, yeah, `bpf_tramp_arena_base` isn't static. That's a good starting point, and I think I've seen some of the ramifications of that in past debugging sessions. Knowing that fact is essential. I'm already mentally filing that away in the "global variables" section of the BPF kernel module, because with the BPF subsystem, you have to know all the state. My next immediate thought is to go digging through `kernel/bpf/bpf_struct_ops.c`. Given that `bpf_tramp_arena_base` is non-static, I want to see how the kernel initializes and potentially manipulates `bpf_struct_ops_desc_init` related to it. I want to see exactly how and where that variable is initialized and any interdependencies it may have. It seems likely that I'll find its use there, given that's the canonical place for structure operations, so understanding the role of `bpf_struct_ops_desc_init` is going to be my key focus. I'm going to want to understand any potential synchronization issues related to `bpf_struct_ops_desc_init` and any corresponding dependencies. I'm expecting some interesting interactions related to memory layout, perhaps atomic operations, and definitely some locking considerations within that file. I can already start to make assumptions of the potential design choices that the developers may have made, so I'll be reading in anticipation. |
| 14/2 | 2026/08/08 01:22 | tool | grepper |
0mArgs:{
"Expression": "bpf_struct_ops_desc_init",
"PathPrefix": "kernel/bpf/bpf_struct_ops.c"
}Results: {
"Output": "kernel/bpf/bpf_struct_ops.c=351=int bpf_struct_ops_supported(const struct bpf_struct_ops *st_ops, u32 moff)\n--\nkernel/bpf/bpf_struct_ops.c-357-\nkernel/bpf/bpf_struct_ops.c:358:int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,\nkernel/bpf/bpf_struct_ops.c-359-\t\t\t struct btf *btf,\n"
} |
| 15/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40065 output: 38 thoughts: 47 Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as someone well-versed in this area: **Analyzing BPF Structure Operations and BTF Compilation** Alright, so the first thing on my mind is confirming that `bpf_struct_ops_desc_init` isn't declared as static. Good, that's what I expected. Now that that's confirmed, let's pivot to the `btf_distill_func_proto` function. I need to refresh my memory, so I'll quickly check in `kernel/bpf/btf.c` to see how it's defined and, more importantly, how it interacts with the rest of the BTF (BPF Type Format) process. I'm especially interested in how it handles function prototypes; understanding how that works is key to understanding how we can build structures for the function types. |
| 16/2 | 2026/08/08 01:22 | tool | grepper |
0mArgs:{
"Expression": "btf_distill_func_proto",
"PathPrefix": "kernel/bpf/btf.c"
}Results: {
"Output": "kernel/bpf/btf.c=7546=static u8 __get_arg_fmodel_flags(const struct btf *btf,\n--\nkernel/bpf/btf.c-7561-\nkernel/bpf/btf.c:7562:int btf_distill_func_proto(struct bpf_verifier_log *log,\nkernel/bpf/btf.c-7563-\t\t\t struct btf *btf,\n"
} |
| 17/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40468 output: 40 thoughts: 48 Thoughts: Okay, here's my interpretation of those thoughts, crafted with the perspective of someone knowledgeable in the field: **Internal BTF Analysis** Right, so `btf_distill_func_proto` *is* non-static. Good. That's an important piece of the puzzle. Now I need to see *how* it's being used and what context surrounds it. My next logical step is to delve into the `kernel/bpf/check_btf.c` file. Specifically, I'm going to focus on the `bpf_prepare_btf_info` function. That's where I expect to find the relevant usage and interactions with `btf_distill_func_proto`. I'm looking for clues about how function prototypes are handled within the BTF verification process. Understanding the flow from `bpf_prepare_btf_info` to `btf_distill_func_proto` will likely shed light on the design and potential issues in play. Time to get digging. |
| 18/2 | 2026/08/08 01:22 | tool | grepper |
0mArgs:{
"Expression": "bpf_prepare_btf_info",
"PathPrefix": "kernel/bpf/check_btf.c"
}Results: {
"Output": "kernel/bpf/check_btf.c=341=static int check_core_relo(struct bpf_verifier_env *env,\n--\nkernel/bpf/check_btf.c-409-\nkernel/bpf/check_btf.c:410:int bpf_prepare_btf_info(struct bpf_verifier_env *env,\nkernel/bpf/check_btf.c-411-\t\t\t const union bpf_attr *attr,\n"
} |
| 19/2 | 2026/08/08 01:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40907 output: 36 thoughts: 44 Thoughts: Here's my take on the thought process, summarized as you requested: **Navigating the BPF Landscape** Alright, so I'm thinking about this `bpf_prepare_btf_info` function. The key thing I'm noting is that it's *not* static, which is interesting. Now, I need to understand its relationship to the broader BPF verification process. I'm going to quickly check in `kernel/bpf/verifier.c` to see how it might be used. Specifically, I'm going to zero in on `bpf_check_attach_target`. I'll explore this section of the code and see if the functions are used in conjunction. |
| 20/2 | 2026/08/08 01:23 | tool | grepper |
0mArgs:{
"Expression": "bpf_check_attach_target",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=12131=static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-12142- * LSM hooks and iters (both sleepable and non-sleepable) are safe.\nkernel/bpf/verifier.c:12143: * Any sleepable progs are also safe since bpf_check_attach_target() enforce\nkernel/bpf/verifier.c-12144- * them can only be attached to some specific hook points.\n--\nkernel/bpf/verifier.c=19188=static bool attach_uses_trampoline_retval(enum bpf_attach_type type)\n--\nkernel/bpf/verifier.c-19201-\nkernel/bpf/verifier.c:19202:int bpf_check_attach_target(struct bpf_verifier_log *log,\nkernel/bpf/verifier.c-19203-\t\t\t const struct bpf_prog *prog,\n--\nkernel/bpf/verifier.c=19625=static int check_attach_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-19655-\nkernel/bpf/verifier.c:19656:\tret = bpf_check_attach_target(\u0026env-\u003elog, prog, tgt_prog, btf_id, \u0026tgt_info);\nkernel/bpf/verifier.c-19657-\tif (ret)\n"
} |
| 21/2 | 2026/08/08 01:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41538 output: 30 thoughts: 40 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Checking BPF Function Visibility** Right, so `bpf_check_attach_target` is definitely not a static function, which is what I suspected. Good. Now, I need to quickly confirm whether `bpf_jit_supports_arena_args` is also non-static. It's a key detail to understand the current architecture and its flexibility within the BPF system. This will help inform any design decisions I need to make down the line, given the non-static nature of these functions. Keeping a keen eye on this. |
| 22/2 | 2026/08/08 01:23 | tool | grepper |
0mArgs:{
"Expression": "bpf_jit_supports_arena_args",
"PathPrefix": ""
}Results: {
"Output": "arch/x86/net/bpf_jit_comp.c=4140=bool bpf_jit_supports_stack_args(void)\n--\narch/x86/net/bpf_jit_comp.c-4144-\narch/x86/net/bpf_jit_comp.c:4145:bool bpf_jit_supports_arena_args(void)\narch/x86/net/bpf_jit_comp.c-4146-{\n--\ninclude/linux/filter.h=1216=bool bpf_jit_supports_stack_args(void);\ninclude/linux/filter.h:1217:bool bpf_jit_supports_arena_args(void);\ninclude/linux/filter.h-1218-bool bpf_jit_supports_far_kfunc_call(void);\n--\nkernel/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--\nkernel/bpf/verifier.c=11441=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-11518-\telse if (is_kfunc_arg_arena(meta-\u003ebtf, \u0026args[arg])) {\nkernel/bpf/verifier.c:11519:\t\tif (!bpf_jit_supports_arena_args()) {\nkernel/bpf/verifier.c-11520-\t\t\tverbose(env, \"JIT does not support kfunc %s() with arena pointer arguments\\n\",\n--\nkernel/bpf/verifier.c=18874=static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-18963-\t\tif (base_type(info-\u003ereg_type) == PTR_TO_ARENA) {\nkernel/bpf/verifier.c:18964:\t\t\tif (!bpf_jit_supports_arena_args()) {\nkernel/bpf/verifier.c-18965-\t\t\t\tverbose(env, \"JIT does not support arena arguments\\n\");\n"
} |
| 23/2 | 2026/08/08 01:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 42368 output: 35 thoughts: 917 Thoughts: **Analyzing the BPF Arena Argument Patch** Okay, so I've been digging into this patch, and here's what I've got. `bpf_jit_supports_arena_args` is definitely non-static, so that's a key function to focus on. My initial list of symbols to watch is looking like this: `bpf_tramp_arena_base`, `bpf_struct_ops_desc_init`, `btf_distill_func_proto`, `bpf_check_attach_target`, `arch_prepare_bpf_trampoline`, and `bpf_jit_supports_arena_args`. Hold on, it appears that `bpf_prepare_btf_info` is just a rename of `bpf_check_btf_info_early`. No real change there, so I'll drop that from the important functions to review. I see a new static function, `add_kfuncs`, but I'll exclude it since it's static. Let me see: * `bpf_tramp_arena_base` is new. * `bpf_struct_ops_desc_init` has been modified, specifically to pass `&st_ops->func_models[i]` to `prepare_arg_info`. * `btf_distill_func_proto` also has changes, it is modified to call `__get_arg_fmodel_flags`. * `bpf_check_attach_target` is updated to reject attachments to targets with arena context arguments. * `arch_prepare_bpf_trampoline` is modified to handle arena arguments. * `bpf_jit_supports_arena_args` is a new function. Besides those, the relevant configs are `BPF_SYSCALL` and `BPF_JIT`. So, the gist is: this patch introduces support for `__arena` and `__arena__nullable` annotations for kfuncs and struct_ops arguments. This essentially enables BPF programs to pass arena pointers to those callbacks. The JIT compiler gets a modification to rebase those arena pointers to kernel addresses, while the verifier is beefed up to validate the arguments and ensure the program has the right arena associated. Finally, the struct_ops trampoline itself is being updated to convert arena pointers back to arena offsets. It seems `arch_prepare_bpf_trampoline` is the right non-static function since it calls `__arch_prepare_bpf_trampoline`. One last thing, `btf_ctx_access` in `kernel/bpf/btf.c` has also been modified. I will add it to the list. The essential pieces, in my view, are: * `bpf_tramp_arena_base` * `bpf_struct_ops_desc_init` * `btf_distill_func_proto` * `bpf_check_attach_target` * `arch_prepare_bpf_trampoline` * `bpf_jit_supports_arena_args` * `btf_ctx_access` And the configs to keep in mind: * `BPF_SYSCALL` * `BPF_JIT` Given the functional scope, and the changes in the JIT compiler and verifier, this absolutely warrants some solid fuzzing. It's a significant shift in the BPF subsystem. |
| 24/2 | 2026/08/08 01:23 | 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"
} |
| 25/2 | 2026/08/08 01:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 44284 output: 206 thoughts: 237 Thoughts: **My Symbol and Configuration Analysis** Okay, here's what I've figured out. `btf_ctx_access` is definitely non-static and exported, so I'm adding it to the focus symbols. Now, let's look at `kernel/bpf/verifier.c` and specifically `bpf_check`. It's non-static, alright, but it's the main entry point for the verifier, so it's probably too broad for my current goal. Not going to include that one. Next, I checked `kernel/bpf/core.c` for `bpf_jit_supports_arena_args`, and yep, it's non-static. Good. So, here's the final list of my focus symbols: * `bpf_tramp_arena_base` * `bpf_struct_ops_desc_init` * `btf_distill_func_proto` * `bpf_check_attach_target` * `arch_prepare_bpf_trampoline` * `bpf_jit_supports_arena_args` * `btf_ctx_access` And the configurations I'll need to keep in mind are: * `BPF_SYSCALL` * `BPF_JIT` Alright, time to call `set-results`. I've got what I need for now. |
| 26/2 | 2026/08/08 01:23 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_arena_base",
"bpf_struct_ops_desc_init",
"btf_distill_func_proto",
"bpf_check_attach_target",
"arch_prepare_bpf_trampoline",
"bpf_jit_supports_arena_args",
"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, struct_ops, and trampoline to support passing BPF arena pointers to kfuncs and struct_ops callbacks. This is a significant functional change in the BPF subsystem that should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"bpf_tramp_arena_base",
"bpf_struct_ops_desc_init",
"btf_distill_func_proto",
"bpf_check_attach_target",
"arch_prepare_bpf_trampoline",
"bpf_jit_supports_arena_args",
"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, struct_ops, and trampoline to support passing BPF arena pointers to kfuncs and struct_ops callbacks. This is a significant functional change in the BPF subsystem that should be fuzzed.",
"WorthFuzzing": true
} |
| 27/1 | 2026/08/08 01:23 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 28/2 | 2026/08/08 01:23 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series introduces support for `__arena` and `__arena__nullable` annotations in BPF kfuncs and struct_ops, modifying the BPF verifier and x86 JIT compiler to handle arena pointers. The changes primarily involve pointer arithmetic (truncation and rebasing using `kern_vm_start`) during JIT compilation and verifier type checking. \n\nThese changes do not introduce new kernel structures copied to user space, nor do they allocate complex data structures with potentially uninitialized fields. Any bugs introduced by incorrect JIT translation, pointer arithmetic, or verifier logic would result in out-of-bounds accesses, invalid pointer dereferences, or memory corruption. These types of bugs are reliably detected by KASAN and standard memory corruption checks. There is no specific risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit f860347ae84ce23eee0573b82d4ca4aabbbae0e8
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 8 01:22:09 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 021be6d93dfb0..1004eb0bec617 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
@@ -522,6 +559,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 88ed95b2eaa72..8dddb5d7af21b 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)
{
@@ -2588,6 +2632,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;
@@ -2998,11 +3048,39 @@ 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,
+ u64 arena_base)
{
int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
bool use_jmp = bpf_trampoline_use_jmp(flags);
+ int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
int i, j;
/* Store function arguments to stack.
@@ -3011,6 +3089,9 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
* mov QWORD PTR [rbp-0x8],rsi
*/
for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
+ bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+ bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
arg_regs = (m->arg_size[i] + 7) / 8;
/* According to the research of Yonghong, struct members
@@ -3034,16 +3115,19 @@ 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 (arena_arg)
+ emit_arena_arg_conv(prog, BPF_REG_0, nullable,
+ (u32)arena_base);
emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,
-stack_size);
@@ -3064,9 +3148,13 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
/* 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 (arena_arg) {
+ emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
+ src = BPF_REG_0;
+ }
+ emit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);
stack_size -= 8;
nr_regs++;
}
@@ -3362,6 +3450,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
void *orig_call = func_addr;
int cookie_off, cookie_cnt;
u8 **branches = NULL;
+ u64 arena_base;
u64 func_meta;
u8 *prog;
bool save_ret;
@@ -3374,6 +3463,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)));
+ arena_base = bpf_tramp_arena_base(m, tnodes, flags);
+
for (i = 0; i < m->nr_args; i++)
nr_regs += (m->arg_size[i] + 7) / 8 - 1;
@@ -3508,7 +3599,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
emit_store_stack_imm64(&prog, BPF_REG_0, -ip_off, (long)func_addr);
}
- save_args(m, &prog, regs_off, false, flags);
+ save_args(m, &prog, regs_off, false, flags, arena_base);
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* arg1: mov rdi, im */
@@ -3550,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, 0);
if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
/* Before calling the original function, load the
@@ -4051,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 d79bf7557ef68..b4a10c9878cf8 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1195,6 +1195,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;
@@ -1268,6 +1274,15 @@ struct bpf_tramp_nodes {
int nr_nodes;
};
+/*
+ * The arena base against which a struct_ops trampoline converts the
+ * arguments marked with BTF_FMODEL_ARENA_ARG while saving them into the BPF
+ * ctx, ctx[arg] = (u32)(kaddr - kern_vm_start). Zero when the trampoline
+ * converts nothing.
+ */
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+ struct bpf_tramp_nodes *tnodes, u32 flags);
+
struct bpf_tramp_run_ctx;
/* Different use cases for BPF trampoline:
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index a2a40caca0a0e..5c8a4d9b8fce7 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1177,8 +1177,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id)
*btf_id = key & 0x7FFFFFFF;
}
-int bpf_check_btf_info_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr, bpfptr_t uattr);
+int bpf_prepare_btf_info(struct bpf_verifier_env *env,
+ const union bpf_attr *attr, bpfptr_t uattr);
int bpf_check_btf_info(struct bpf_verifier_env *env,
const union bpf_attr *attr, bpfptr_t uattr);
@@ -1302,6 +1302,16 @@ static inline u32 type_flag(u32 type)
return type & ~BPF_BASE_TYPE_MASK;
}
+static inline 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;
+}
+
static inline enum bpf_prog_type resolve_prog_type(const struct bpf_prog *prog)
{
return (prog->type == BPF_PROG_TYPE_EXT && prog->aux->saved_dst_prog_type) ?
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 41b02d53e2228..4edba8182db1b 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1214,6 +1214,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..d7c3030bc63b0 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
@@ -172,10 +174,12 @@ static int prepare_arg_info(struct btf *btf,
const char *st_ops_name,
const char *member_name,
const struct btf_type *func_proto, void *stub_func_addr,
+ struct btf_func_model *model,
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;
@@ -225,27 +229,39 @@ static int prepare_arg_info(struct btf *btf,
/* Prepare info for every nullable argument */
info = info_buf;
for (arg_no = 0; arg_no < nargs; arg_no++) {
- /* Skip arguments that is not suffixed with
- * "__nullable or __ref".
+ bool ptr_to_arena, ptr_to_struct;
+
+ /*
+ * Skip arguments that are not suffixed with "__arena__nullable",
+ * "__arena", "__nullable", or "__ref".
*/
- is_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
- MAYBE_NULL_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);
+ is_nullable = !is_arena_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);
- if (is_nullable)
+ if (is_arena_nullable)
+ suffix = ARENA_MAYBE_NULL_SUFFIX;
+ else if (is_arena)
+ suffix = ARENA_SUFFIX;
+ else if (is_nullable)
suffix = MAYBE_NULL_SUFFIX;
else if (is_refcounted)
suffix = REFCOUNTED_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 or
+ * __arena__nullable.
+ */
+ pointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, &arg_btf_id);
+ ptr_to_arena = pointed_type && (is_arena || is_arena_nullable);
+ ptr_to_struct = pointed_type && btf_type_is_struct(pointed_type);
+ if (!ptr_to_arena && !ptr_to_struct) {
pr_warn("stub function %s has %s tagging to an unsupported type\n",
stub_fname, suffix);
goto err_out;
@@ -268,7 +284,18 @@ static int prepare_arg_info(struct btf *btf,
info->btf_id = arg_btf_id;
info->btf = btf;
info->offset = offset;
- if (is_nullable) {
+ 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;
+ model->arg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;
+ if (is_arena_nullable)
+ model->arg_flags[arg_no] |= BTF_FMODEL_NULLABLE_ARG;
+ } else if (is_nullable) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
} else if (is_refcounted) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID;
@@ -460,6 +487,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
err = prepare_arg_info(btf, st_ops->name, mname,
func_proto, stub_func_addr,
+ &st_ops->func_models[i],
arg_info + i);
if (err)
goto errout;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 42414633cf263..6606187ed4f43 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;
}
@@ -7539,6 +7543,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__nullable"))
+ flags |= BTF_FMODEL_ARENA_ARG | BTF_FMODEL_NULLABLE_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__arena"))
+ flags |= BTF_FMODEL_ARENA_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__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,
@@ -7604,7 +7624,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/check_btf.c b/kernel/bpf/check_btf.c
index 93bebe6fe12e7..0e8b3ccc7a5b9 100644
--- a/kernel/bpf/check_btf.c
+++ b/kernel/bpf/check_btf.c
@@ -28,9 +28,9 @@ static int check_abnormal_return(struct bpf_verifier_env *env)
#define MIN_BPF_FUNCINFO_SIZE 8
#define MAX_FUNCINFO_REC_SIZE 252
-static int check_btf_func_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+static int prepare_btf_func(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
u32 krec_size = sizeof(struct bpf_func_info);
const struct btf_type *type, *func_proto;
@@ -407,9 +407,9 @@ static int check_core_relo(struct bpf_verifier_env *env,
return err;
}
-int bpf_check_btf_info_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+int bpf_prepare_btf_info(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
struct btf *btf;
int err;
@@ -429,7 +429,7 @@ int bpf_check_btf_info_early(struct bpf_verifier_env *env,
}
env->prog->aux->btf = btf;
- err = check_btf_func_early(env, attr, uattr);
+ err = prepare_btf_func(env, attr, uattr);
if (err)
return err;
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..e07af35ed0402 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -529,6 +529,36 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
return tnodes;
}
+/*
+ * The arena base against which save_args() converts the arguments marked
+ * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
+ * converts: it dispatches to a single prog whose arena is known at
+ * generation time. Return 0 when there is nothing to convert.
+ */
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+ struct bpf_tramp_nodes *tnodes, u32 flags)
+{
+ const struct bpf_prog *prog;
+ int i;
+
+ if (!(flags & BPF_TRAMP_F_INDIRECT) ||
+ tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
+ return 0;
+
+ for (i = 0; i < m->nr_args; i++)
+ if (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG)
+ break;
+ if (i == m->nr_args)
+ return 0;
+
+ /* Verification rejects an arena argument without an arena. */
+ prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
+ if (WARN_ON_ONCE(!prog->aux->arena))
+ return 0;
+
+ return bpf_arena_get_kern_vm_start(prog->aux->arena);
+}
+
static void bpf_tramp_image_free(struct bpf_tramp_image *im)
{
bpf_image_ksym_del(&im->ksym);
@@ -920,6 +950,13 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,
int cnt = 0, i;
kind = bpf_attach_type_to_tramp(node->link->prog);
+ /*
+ * Arena ctx args are converted only by struct_ops indirect
+ * trampolines. They must never be attached to a generic trampoline.
+ */
+ if (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node->link->prog)))
+ return -ENOTSUPP;
+
if (tr->extension_prog)
/* cannot attach fentry/fexit if extension prog is attached.
* cannot overwrite extension prog either.
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c9533ea700ba2..4bb3102ae4333 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2842,7 +2842,7 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
return 0;
}
-static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
+static int add_subprogs(struct bpf_verifier_env *env)
{
struct bpf_subprog_info *subprog = env->subprog_info;
int i, ret, insn_cnt = env->prog->len, ex_cb_insn;
@@ -2854,8 +2854,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return ret;
for (i = 0; i < insn_cnt; i++, insn++) {
- if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn) &&
- !bpf_pseudo_kfunc_call(insn))
+ if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn))
continue;
if (!env->bpf_capable) {
@@ -2863,11 +2862,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return -EPERM;
}
- if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn))
- ret = add_subprog(env, i + insn->imm + 1);
- else
- ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
-
+ ret = add_subprog(env, i + insn->imm + 1);
if (ret < 0)
return ret;
}
@@ -2905,6 +2900,28 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return 0;
}
+static int add_kfuncs(struct bpf_verifier_env *env)
+{
+ struct bpf_insn *insn = env->prog->insnsi;
+ int i, ret, insn_cnt = env->prog->len;
+
+ for (i = 0; i < insn_cnt; i++, insn++) {
+ if (!bpf_pseudo_kfunc_call(insn))
+ continue;
+
+ if (!env->bpf_capable) {
+ verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ return -EPERM;
+ }
+
+ ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int check_subprogs(struct bpf_verifier_env *env)
{
int i, subprog_start, subprog_end, off, cur_subprog = 0;
@@ -10919,7 +10936,8 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
{
- return btf_param_match_suffix(btf, arg, "__nullable");
+ return btf_param_match_suffix(btf, arg, "__nullable") ||
+ btf_param_match_suffix(btf, arg, "__arena");
}
static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
@@ -10937,6 +10955,12 @@ 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__nullable") ||
+ btf_param_match_suffix(btf, arg, "__arena");
+}
+
static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
const struct btf_param *arg,
const char *name)
@@ -11157,6 +11181,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 {
@@ -11442,7 +11467,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);
@@ -11491,7 +11515,30 @@ 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 (arg + 1 < nargs &&
+ else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support kfunc %s() with arena pointer arguments\n",
+ meta->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 (reg_from_argno(argno) < 0) {
+ verbose(env, "%s arena pointer cannot be a stack argument\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ /*
+ * Both suffixes accept a constant zero. The function model determines
+ * whether the JIT rebases it to the arena base or preserves NULL.
+ * The common nullable path below records that verifier property.
+ */
+ 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]))) {
if (!btf_type_is_void(ref_t) && !btf_type_is_scalar(ref_t) &&
@@ -12166,7 +12213,7 @@ 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 (btf_type_is_ptr(t) && (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
- !is_kfunc_arg_nullable(meta->btf, &args[i])) {
+ !type_may_be_null(kf_arg_type)) {
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
reg_arg_name(env, argno));
return -EACCES;
@@ -12219,6 +12266,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
case KF_ARG_PTR_TO_TASK_WORK:
case KF_ARG_PTR_TO_IRQ_FLAG:
case KF_ARG_PTR_TO_RES_SPIN_LOCK:
+ case KF_ARG_PTR_TO_ARENA:
break;
case KF_ARG_PTR_TO_DYNPTR:
arg_type = ARG_PTR_TO_DYNPTR;
@@ -12285,6 +12333,13 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
meta->ret_btf_id = ret;
}
break;
+ case KF_ARG_PTR_TO_ARENA:
+ 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;
+ }
+ break;
case KF_ARG_PTR_TO_ALLOC_BTF_ID:
if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
@@ -18820,6 +18875,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;
@@ -18898,10 +18954,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;
+ }
}
}
@@ -18922,8 +18991,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_"
@@ -19190,6 +19258,16 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
bpf_log(log, "Subprog %s doesn't exist\n", tname);
return -EINVAL;
}
+ /*
+ * A struct_ops indirect trampoline converts arena arguments
+ * before invoking its program. A tracing or extension program
+ * attached to the main program would see the converted offset as a
+ * regular BTF pointer.
+ */
+ if (subprog == 0 && bpf_prog_has_arena_ctx_arg(tgt_prog)) {
+ bpf_log(log, "Cannot attach to a target with arena context arguments\n");
+ return -EOPNOTSUPP;
+ }
if (aux->func && aux->func[subprog]->aux->exception_cb) {
bpf_log(log,
"%s programs cannot attach to exception callback\n",
@@ -20325,11 +20403,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
INIT_LIST_HEAD(&env->explored_states[i]);
INIT_LIST_HEAD(&env->free_list);
- ret = bpf_check_btf_info_early(env, attr, uattr);
+ /* Prepare BTF and func_info needed to discover all subprograms. */
+ ret = bpf_prepare_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
- ret = add_subprog_and_kfunc(env);
+ /* Discover all subprograms before validating their layout and BTF. */
+ ret = add_subprogs(env);
if (ret < 0)
goto skip_full_check;
@@ -20337,14 +20417,21 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
+ /* Validate BTF against the complete subprogram layout and apply CO-RE. */
ret = bpf_check_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
+ /* Validate instructions and resolve the program's referenced resources. */
ret = check_and_resolve_insns(env);
if (ret < 0)
goto skip_full_check;
+ /* Build kfunc prototypes after resolving program resources. */
+ ret = add_kfuncs(env);
+ if (ret < 0)
+ goto skip_full_check;
+
if (bpf_prog_is_offloaded(env->prog->aux)) {
ret = bpf_prog_offload_verifier_prep(env->prog);
if (ret)
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..940ec2cda0d52
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
@@ -0,0 +1,128 @@
+// 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_attach.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);
+}
+
+static void arena_arg_attach_one(int target_fd, const char *prog_name)
+{
+ struct struct_ops_arena_attach *skel;
+ struct bpf_program *prog, *pos;
+ char log_buf[64 * 1024];
+ int err;
+
+ skel = struct_ops_arena_attach__open();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_arena_attach__open"))
+ return;
+
+ prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+ if (!ASSERT_OK_PTR(prog, prog_name))
+ goto out;
+
+ bpf_object__for_each_program(pos, skel->obj)
+ bpf_program__set_autoload(pos, pos == prog);
+
+ err = bpf_program__set_attach_target(prog, target_fd, "test_arena_cb");
+ if (!ASSERT_OK(err, "set_attach_target"))
+ goto out;
+
+ log_buf[0] = '\0';
+ bpf_program__set_log_buf(prog, log_buf, sizeof(log_buf));
+ err = struct_ops_arena_attach__load(skel);
+
+ ASSERT_EQ(err, -EOPNOTSUPP, prog_name);
+ ASSERT_HAS_SUBSTR(log_buf, "Cannot attach to a target with arena context arguments",
+ "verifier_log");
+
+out:
+ struct_ops_arena_attach__destroy(skel);
+}
+
+static void arena_arg_attach(void)
+{
+ struct struct_ops_arena *skel;
+ int target_fd;
+
+ skel = struct_ops_arena__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_arena__open_and_load"))
+ return;
+
+ target_fd = bpf_program__fd(skel->progs.test_arena_cb);
+ arena_arg_attach_one(target_fd, "fentry_test_arena");
+ arena_arg_attach_one(target_fd, "fexit_test_arena");
+ arena_arg_attach_one(target_fd, "freplace_test_arena");
+
+ struct_ops_arena__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();
+ if (test__start_subtest("arena_arg_attach"))
+ arena_arg_attach();
+#else
+ test__skip();
+#endif
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index b79bafca68f7a..e7fb3eec78011 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -2,6 +2,8 @@
#include <test_progs.h>
+#include "arena_kfunc.skel.h"
+#include "arena_kfunc_jit.skel.h"
#include "cap_helpers.h"
#include "verifier_align.skel.h"
#include "verifier_and.skel.h"
@@ -161,6 +163,10 @@ static void run_tests_aux(const char *skel_name,
#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)
+void test_arena_kfunc(void) { RUN_TESTS(arena_kfunc); }
+
+void test_arena_kfunc_jit(void) { RUN_TESTS(arena_kfunc_jit); }
+
void test_verifier_align(void) { RUN(verifier_align); }
void test_verifier_and(void) { RUN(verifier_and); }
void test_verifier_arena(void) { RUN(verifier_arena); }
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..cdcea889da586
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -0,0 +1,234 @@
+// 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_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..c9b9186626162
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
@@ -0,0 +1,98 @@
+// 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 L0")
+__jited(" addq %r12, %rdi")
+__jited("L0: callq {{.*}}")
+__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 L0")
+__jited(" addq %r12, %r8")
+__jited("L0: callq {{.*}}")
+__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_attach.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c
new file mode 100644
index 0000000000000..081a770307e53
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+SEC("fentry")
+int BPF_PROG(fentry_test_arena, unsigned long long *st_ops_ctx)
+{
+ return 0;
+}
+
+SEC("fexit")
+int BPF_PROG(fexit_test_arena, unsigned long long *st_ops_ctx, int ret)
+{
+ return 0;
+}
+
+SEC("freplace")
+int freplace_test_arena(unsigned long long *st_ops_ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
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 0585794606ed8..a6133f7521f34 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -237,6 +237,44 @@ __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_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 +385,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 +426,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 +828,12 @@ 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_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 +849,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..c4383acb53c11 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,15 @@ 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_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 +120,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;
|
| 29/3 | 2026/08/08 01:23 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26327 output: 208 thoughts: 1069 Thoughts: **KMSAN Fuzzing Session Justification: A Detailed Examination**
Okay, so I've got this patch series introducing `__arena` and `__arena__nullable` annotations for BPF kfuncs, and it's modifying the BPF JIT compiler and verifier, plus struct_ops to work with arena arguments. My immediate concern is, does this introduce any new uninitialized memory risks? That's the KMSAN trigger, right?
The core of the change allows BPF programs to pass arena pointers to kfuncs. The JIT rebases these pointers to kernel addresses. Now, the BPF arena is shared between userspace and the BPF program, and kfuncs are getting pointers into this arena. Kfuncs can read and write that memory. That's the crux of where things could get dicey with uninitialized memory.
The arena itself is allocated and the pages zeroed. The JIT adds `kern_vm_start` to the 32-bit offset. Now, the question is, if a BPF program passes an uninitialized *value* from the arena to a kfunc, will the kfunc read it? But... that's standard BPF behavior - BPF programs can read/write arena memory. KMSAN's meant to detect this scenario. But wait, is the arena memory considered uninitialized *by KMSAN*? Pages allocated for the arena are zeroed either by the page fault handler, or at allocation, so any reads after writes, or simply at allocation, will be initialized. If a BPF program writes to arena memory, it's initialized. If it *doesn't* write and passes a pointer to a kfunc that then reads, that's reading zeroed or initialized memory.
So, let's step through the KMSAN trigger checklist:
1. **Kernel structures sent to userspace:** Nope. Nothing new here that exposes uninitialized memory.
2. **Conditional logic depending on uninitialized variables:** No. Nothing new in that regard.
3. **Allocation or initialization of complex data structures:** Not in this patch.
4. **Bounds checks/lengths allowing access to uninitialized bytes:** The JIT *does* do truncation and addition, so if the offset is out-of-bounds, it *might* access other memory, but that's an out-of-bounds issue, and KASAN should catch that.
KMSAN rules say "THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN, LOCKDEP, OR OTHER STANDARD BUG DETECTORS." This patch is about BPF JIT and verifier handling arena pointers. It doesn't seem to introduce new kernel-to-user info leaks of uninitialized memory. No uninitialized stack or heap variables. Any out-of-bounds access due to incorrect JIT translation will be KASAN's problem.
Let me just double-check those JIT changes. Here we have some pointer arithmetic:
```c
+ /* 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));
```
And also here:
```c
+static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base_lo)
+{
...
+ /* sub eax, base_lo */
+ EMIT1_off32(0x2D, base_lo);
```
This is still just pointer arithmetic, which is within the existing KASAN scope. No new uninitialized memory risks. KASAN is sufficient.
Therefore, I'm setting `NeedsKMSAN = false`.
|
| 30/3 | 2026/08/08 01:23 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series introduces support for `__arena` and `__arena__nullable` annotations in BPF kfuncs and struct_ops, modifying the BPF verifier and x86 JIT compiler to handle arena pointers. The changes primarily involve pointer arithmetic (truncation and rebasing using `kern_vm_start`) during JIT compilation and verifier type checking. \n\nThese changes do not introduce new kernel structures copied to user space, nor do they allocate complex data structures with potentially uninitialized fields. Any bugs introduced by incorrect JIT translation, pointer arithmetic, or verifier logic would result in out-of-bounds accesses, invalid pointer dereferences, or memory corruption. These types of bugs are reliably detected by KASAN and standard memory corruption checks. There is no specific risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series introduces support for `__arena` and `__arena__nullable` annotations in BPF kfuncs and struct_ops, modifying the BPF verifier and x86 JIT compiler to handle arena pointers. The changes primarily involve pointer arithmetic (truncation and rebasing using `kern_vm_start`) during JIT compilation and verifier type checking. \n\nThese changes do not introduce new kernel structures copied to user space, nor do they allocate complex data structures with potentially uninitialized fields. Any bugs introduced by incorrect JIT translation, pointer arithmetic, or verifier logic would result in out-of-bounds accesses, invalid pointer dereferences, or memory corruption. These types of bugs are reliably detected by KASAN and standard memory corruption checks. There is no specific risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|