AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/05 22:26 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_tramp_arena_base",
    "bpf_prog_has_arena_ctx_arg",
    "bpf_jit_supports_arena_args",
    "bpf_struct_ops_desc_init",
    "btf_distill_func_proto",
    "btf_ctx_access",
    "bpf_check_attach_target"
  ],
  "Reasoning": "The patch introduces support for passing arena pointers to BPF kfuncs and struct_ops callbacks. It modifies the BPF verifier to recognize `__arena` and `__arena__nullable` annotations, and updates the x86 BPF JIT and trampoline generation to properly rebase these pointers to the kernel's arena base address. This introduces new code paths in the verifier, BTF parsing, and JIT compilation, which are reachable from user space via BPF program loading and attachment, making it highly relevant for fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/08/05 22:26 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 061523fe4714f9cfa42dc7b80a06875a6b964f96\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Aug 5 22:26:41 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst\nindex cbde86d082cce..c044659b76891 100644\n--- a/Documentation/bpf/kfuncs.rst\n+++ b/Documentation/bpf/kfuncs.rst\n@@ -278,6 +278,43 @@ An example is given below::\n                 ...\n         }\n \n+2.3.8 __arena and __arena__nullable Annotations\n+-----------------------------------------------\n+\n+Both annotations indicate that the pointer argument points into the\n+calling program's arena. The JIT rebases the value at the call site so\n+the kfunc receives a directly dereferenceable kernel address, subject to\n+the access rules described in :ref:`BPF_kfunc_arena_access` (at most\n+``GUARD_SZ / 2``, 32 KiB, past the pointer in a single unchecked access).\n+\n+With ``__arena`` the rebase is unconditional and the argument is never\n+NULL: a value whose lower 32 bits are zero arrives as the arena base\n+address (arena offset 0). The kfunc must not check the argument for NULL.\n+With ``__arena__nullable`` such a value arrives as NULL instead and the\n+kfunc must check before dereferencing.\n+\n+An example is given below::\n+\n+        __bpf_kfunc int bpf_process_item(struct item *item__arena)\n+        {\n+        ...\n+        }\n+\n+Calling such a kfunc requires the program to use an arena map and a JIT with\n+arena argument support (currently x86-64); verification fails otherwise. The\n+program can pass any value without compromising the kernel. A value that does\n+not point into the arena is a program bug.\n+\n+The suffixes have the same meaning on the arguments of struct_ops stub\n+functions, with the conversion running in the opposite direction. The\n+kernel caller passes the kernel arena address and the trampoline converts\n+it while saving the arguments, so the callback receives an arena pointer\n+it can dereference directly. With ``__arena`` the kernel caller must not\n+pass NULL. With ``__arena__nullable`` a NULL kernel pointer arrives as NULL.\n+However, there is no obligation to prove to the verifier that such a pointer is\n+non-NULL before use, in-line with existing semantics of arena pointers used in\n+a program (or obtained from any other source).\n+\n .. _BPF_kfunc_nodef:\n \n 2.4 Using an existing kernel function\n@@ -515,6 +552,8 @@ In order to accommodate such requirements, the verifier will enforce strict\n PTR_TO_BTF_ID type matching if two types have the exact same name, with one\n being suffixed with ``___init``.\n \n+.. _BPF_kfunc_arena_access:\n+\n 2.8 Accessing arena memory through kfunc arguments\n --------------------------------------------------\n \ndiff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c\nindex 01e7ce569c1ed..5c5c32aba2396 100644\n--- a/arch/x86/net/bpf_jit_comp.c\n+++ b/arch/x86/net/bpf_jit_comp.c\n@@ -1678,6 +1678,50 @@ static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip,\n \treturn 0;\n }\n \n+/*\n+ * Rebase the __arena args of a kfunc call to arena kernel addresses,\n+ * rN = kern_vm_start + (u32)rN, with R12 holding kern_vm_start. A nullable\n+ * arg preserves NULL by skipping the add, tested on the truncated value as\n+ * arena NULL is offset 0. Return the number of emitted bytes.\n+ */\n+static int emit_kfunc_arena_args(struct bpf_prog *bpf_prog,\n+\t\t\t\t const struct bpf_insn *insn, u8 **pprog)\n+{\n+\tconst struct btf_func_model *fm;\n+\tu8 *prog = *pprog;\n+\tu8 *start = prog;\n+\tint i;\n+\n+\tfm = bpf_jit_find_kfunc_model(bpf_prog, insn);\n+\tif (!fm)\n+\t\treturn -EINVAL;\n+\n+\tfor (i = 0; i \u003c min_t(int, fm-\u003enr_args, MAX_BPF_FUNC_REG_ARGS); i++) {\n+\t\tu8 flags = fm-\u003earg_flags[i];\n+\t\tu32 reg = BPF_REG_1 + i;\n+\n+\t\tif (!(flags \u0026 BTF_FMODEL_ARENA_ARG))\n+\t\t\tcontinue;\n+\t\tif (WARN_ON_ONCE(!bpf_prog-\u003eaux-\u003earena))\n+\t\t\treturn -EINVAL;\n+\n+\t\t/* mov eN, eN: truncate and clear the upper 32 bits */\n+\t\temit_mov_reg(\u0026prog, false, reg, reg);\n+\t\tif (flags \u0026 BTF_FMODEL_NULLABLE_ARG) {\n+\t\t\t/* test eN, eN; jz over the 3-byte add */\n+\t\t\tmaybe_emit_mod(\u0026prog, reg, reg, false);\n+\t\t\tEMIT2(0x85, add_2reg(0xC0, reg, reg));\n+\t\t\tEMIT2(X86_JE, 3);\n+\t\t}\n+\t\t/* add rN, r12 */\n+\t\tmaybe_emit_mod(\u0026prog, reg, X86_REG_R12, true);\n+\t\tEMIT2(0x01, add_2reg(0xC0, reg, X86_REG_R12));\n+\t}\n+\n+\t*pprog = prog;\n+\treturn prog - start;\n+}\n+\n static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,\n \t\t  u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)\n {\n@@ -2583,6 +2627,12 @@ st:\t\t\tinsn_off = insn-\u003eoff;\n \t\t\t}\n \t\t\tif (!imm32)\n \t\t\t\treturn -EINVAL;\n+\t\t\tif (src_reg == BPF_PSEUDO_KFUNC_CALL) {\n+\t\t\t\terr = emit_kfunc_arena_args(bpf_prog, insn, \u0026prog);\n+\t\t\t\tif (err \u003c 0)\n+\t\t\t\t\treturn err;\n+\t\t\t\tip += err;\n+\t\t\t}\n \t\t\tif (priv_frame_ptr) {\n \t\t\t\tpush_r9(\u0026prog);\n \t\t\t\tip += 2;\n@@ -2993,11 +3043,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@@ -3006,6 +3084,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@@ -3029,16 +3110,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@@ -3059,9 +3143,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@@ -3357,6 +3445,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@@ -3369,6 +3458,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@@ -3503,7 +3594,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@@ -3545,7 +3636,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@@ -4046,6 +4137,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 73bacfc6444df..68ff66787ae1d 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -1210,6 +1210,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@@ -1283,6 +1289,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:\n@@ -2352,6 +2367,7 @@ static inline int bpf_fsession_cookie_cnt(struct bpf_tramp_nodes *nodes)\n \n int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,\n \t\t\t       const struct bpf_ctx_arg_aux *info, u32 cnt);\n+bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog);\n \n #if defined(CONFIG_CGROUP_BPF) \u0026\u0026 defined(CONFIG_BPF_LSM)\n int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex a2a40caca0a0e..a9555d17fd8ea 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 \ndiff --git a/include/linux/filter.h b/include/linux/filter.h\nindex 32d5297c557e5..36ce3403fe599 100644\n--- a/include/linux/filter.h\n+++ b/include/linux/filter.h\n@@ -1183,6 +1183,7 @@ bool bpf_jit_supports_subprog_tailcalls(void);\n bool bpf_jit_supports_percpu_insn(void);\n bool bpf_jit_supports_kfunc_call(void);\n bool bpf_jit_supports_stack_args(void);\n+bool bpf_jit_supports_arena_args(void);\n bool bpf_jit_supports_far_kfunc_call(void);\n bool bpf_jit_supports_exceptions(void);\n bool bpf_jit_supports_ptr_xchg(void);\ndiff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c\nindex 4e7a48c02be5c..ced7b18246a96 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,35 @@ 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\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\tif (!pointed_type || (!is_arena \u0026\u0026 !is_arena_nullable \u0026\u0026 !btf_type_is_struct(pointed_type))) {\n \t\t\tpr_warn(\"stub function %s has %s tagging to an unsupported type\\n\",\n \t\t\t\tstub_fname, suffix);\n \t\t\tgoto err_out;\n@@ -268,7 +280,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 +483,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 09588b7b08b0a..83edf41c7fe9c 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@@ -10892,9 +10909,16 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf\n \treturn btf_param_match_suffix(btf, arg, \"__refcounted_kptr\");\n }\n \n+static bool is_kfunc_arg_arena_nullable(const struct btf *btf,\n+\t\t\t\t\tconst struct btf_param *arg)\n+{\n+\treturn btf_param_match_suffix(btf, arg, \"__arena__nullable\");\n+}\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 !is_kfunc_arg_arena_nullable(btf, arg) \u0026\u0026\n+\t       btf_param_match_suffix(btf, arg, \"__nullable\");\n }\n \n static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)\n@@ -10912,6 +10936,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 is_kfunc_arg_arena_nullable(btf, arg) ||\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@@ -11132,6 +11162,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@@ -11417,7 +11448,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@@ -11466,7 +11496,29 @@ 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 */\n+\t\targ_type = KF_ARG_PTR_TO_ARENA | PTR_MAYBE_NULL;\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@@ -12160,7 +12212,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@@ -12213,6 +12265,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@@ -12279,6 +12332,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@@ -18808,10 +18868,21 @@ int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,\n \treturn prog-\u003eaux-\u003ectx_arg_info ? 0 : -ENOMEM;\n }\n \n+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 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@@ -18890,10 +18961,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@@ -18914,8 +18998,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@@ -19179,6 +19262,17 @@ 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 program attached to the\n+\t\t * main program would see the converted offset as a regular BTF\n+\t\t * pointer.\n+\t\t */\n+\t\tif (prog_tracing \u0026\u0026 subprog == 0 \u0026\u0026\n+\t\t    bpf_prog_has_arena_ctx_arg(tgt_prog)) {\n+\t\t\tbpf_log(log, \"Cannot trace 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@@ -20269,11 +20363,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@@ -20281,14 +20377,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..323d707c543fa\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c\n@@ -0,0 +1,74 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+#include \u003ctest_progs.h\u003e\n+\n+#include \"struct_ops_arena.skel.h\"\n+#include \"struct_ops_arena_fail.skel.h\"\n+\n+#if defined(__x86_64__)\n+/*\n+ * Attach callbacks with __arena and __arena__nullable arguments and drive\n+ * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.\n+ */\n+static void arena_arg(void)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, topts);\n+\tstruct struct_ops_arena *skel;\n+\tstruct bpf_link *link = NULL;\n+\tint err;\n+\n+\tskel = struct_ops_arena__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"struct_ops_arena__open_and_load\"))\n+\t\treturn;\n+\n+\tlink = bpf_map__attach_struct_ops(skel-\u003emaps.testmod_arena);\n+\tif (!ASSERT_OK_PTR(link, \"attach_struct_ops\"))\n+\t\tgoto out;\n+\n+\terr = bpf_prog_test_run_opts(bpf_program__fd(skel-\u003eprogs.trigger),\n+\t\t\t\t     \u0026topts);\n+\tASSERT_OK(err, \"test_run\");\n+\tASSERT_EQ(topts.retval, 0, \"trigger_retval\");\n+\n+out:\n+\tbpf_link__destroy(link);\n+\tstruct_ops_arena__destroy(skel);\n+}\n+\n+/*\n+ * A program with no arena cannot attach to a member with an __arena\n+ * argument.\n+ */\n+static void arena_arg_fail(void)\n+{\n+\tstruct struct_ops_arena_fail *skel;\n+\n+\tskel = struct_ops_arena_fail__open_and_load();\n+\tif (ASSERT_ERR_PTR(skel, \"struct_ops_arena_fail__open_and_load\"))\n+\t\treturn;\n+\n+\tstruct_ops_arena_fail__destroy(skel);\n+}\n+#endif\n+\n+/*\n+ * Serialized because it attaches the singleton bpf_testmod_ops3, which\n+ * test_struct_ops_private_stack also attaches; registering it twice fails\n+ * with -EEXIST.\n+ */\n+void serial_test_struct_ops_arena(void)\n+{\n+\t/*\n+\t * Arena struct_ops arguments need JIT support, currently x86-64 only.\n+\t * Elsewhere verification fails with \"JIT does not support arena\n+\t * arguments\", so the programs cannot even load.\n+\t */\n+#if defined(__x86_64__)\n+\tif (test__start_subtest(\"arena_arg\"))\n+\t\tarena_arg();\n+\tif (test__start_subtest(\"arena_arg_fail\"))\n+\t\tarena_arg_fail();\n+#else\n+\ttest__skip();\n+#endif\n+}\ndiff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c\nindex b79bafca68f7a..110a33b6e1c25 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,15 @@ static void run_tests_aux(const char *skel_name,\n \n #define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)\n \n+/*\n+ * The test kfuncs live in bpf_testmod. Resolving kfuncs against module\n+ * BTFs needs CAP_SYS_ADMIN, so run with full capabilities instead of\n+ * through the verifier tests' capability-restricted runner.\n+ */\n+void test_arena_kfunc(void)                   { 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..d6c382ce81af6\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c\n@@ -0,0 +1,260 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+\n+#define BPF_NO_KFUNC_PROTOTYPES\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_misc.h\"\n+#include \"bpf_experimental.h\"\n+#include \u003cbpf_arena_common.h\u003e\n+#include \"../test_kmods/bpf_testmod_kfunc.h\"\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARENA);\n+\t__uint(map_flags, BPF_F_MMAPABLE);\n+\t/* page 0 hosts the arena global, page 1 is for allocations */\n+\t__uint(max_entries, 2);\n+} arena SEC(\".maps\");\n+\n+/*\n+ * Occupies page 0 so no allocation lands at arena offset 0, which the\n+ * nullable tests below must be able to tell apart from NULL.\n+ */\n+u64 __arena arena_pad;\n+\n+/* volatile to force the scalar reloads below */\n+volatile u64 stash;\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_forms(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\tu64 ret;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\t/* PTR_TO_ARENA argument */\n+\t*val = 41;\n+\tret = bpf_kfunc_arena_arg_test((u64 *)val);\n+\tif (ret != 41 || *val != 42)\n+\t\treturn 2;\n+\n+\t/* the low 32 bits as a scalar */\n+\tstash = (u32)(u64)val;\n+\tret = bpf_kfunc_arena_arg_test((u64 *)stash);\n+\tif (ret != 42 || *val != 43)\n+\t\treturn 3;\n+\n+\t/* the full user address as a scalar */\n+\tstash = (u64)val;\n+\tbpf_addr_space_cast(stash, 1, 0);\n+\tret = bpf_kfunc_arena_arg_test((u64 *)stash);\n+\tif (ret != 43 || *val != 44)\n+\t\treturn 4;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+/*\n+ * Pin the rebase semantics using the capture kfuncs, which return the raw\n+ * argument value: __arena rebases unconditionally, so zero low 32 bits\n+ * arrive as the arena kernel base, while __arena__nullable turns them into\n+ * NULL.\n+ */\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_rebase(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\tu64 base, off;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tbase = bpf_kfunc_arena_cap_test(NULL);\n+\tif (!base)\n+\t\treturn 2;\n+\n+\t/* only the low 32 bits contribute */\n+\tstash = 0xbadc0ffe00000000;\n+\tif (bpf_kfunc_arena_cap_test((u64 *)stash) != base)\n+\t\treturn 3;\n+\n+\toff = (u32)(u64)val;\n+\tif (bpf_kfunc_arena_cap_test((u64 *)val) != base + off)\n+\t\treturn 4;\n+\n+\tif (bpf_kfunc_arena_cap_nullable_test(NULL) != 0)\n+\t\treturn 5;\n+\n+\tstash = 0xbadc0ffe00000000;\n+\tif (bpf_kfunc_arena_cap_nullable_test((u64 *)stash) != 0)\n+\t\treturn 6;\n+\n+\tif (bpf_kfunc_arena_cap_nullable_test((u64 *)val) != base + off)\n+\t\treturn 7;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_nullable(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\tu64 ret;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\t*val = 41;\n+\tret = bpf_kfunc_arena_nullable_arg_test((u64 *)val);\n+\tif (ret != 41 || *val != 42)\n+\t\treturn 2;\n+\n+\tif (bpf_kfunc_arena_nullable_arg_test(NULL) != 0xdeadbeef)\n+\t\treturn 3;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_args5(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tval[0] = 1;\n+\tval[1] = 2;\n+\tval[2] = 4;\n+\tval[3] = 8;\n+\tval[4] = 16;\n+\n+\tif (bpf_kfunc_arena_args5_test((u64 *)\u0026val[0], (u64 *)\u0026val[1],\n+\t\t\t\t       (u64 *)\u0026val[2], (u64 *)\u0026val[3],\n+\t\t\t\t       (u64 *)\u0026val[4]) != 31)\n+\t\treturn 2;\n+\tif (bpf_kfunc_arena_args5_test((u64 *)\u0026val[0], (u64 *)\u0026val[1],\n+\t\t\t\t       (u64 *)\u0026val[2], (u64 *)\u0026val[3], NULL) != 15)\n+\t\treturn 3;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_mixed(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tval[0] = 7;\n+\tval[1] = 5;\n+\n+\tif (bpf_kfunc_arena_mixed_test((u64 *)\u0026val[0], NULL) != 7)\n+\t\treturn 2;\n+\n+\tif (bpf_kfunc_arena_mixed_test((u64 *)\u0026val[0], (u64 *)\u0026val[1]) != 12)\n+\t\treturn 3;\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+/* kernel-side faults on unpopulated pages recover via the scratch page */\n+SEC(\"syscall\")\n+__arch_x86_64\n+__success __retval(0)\n+int arena_arg_unpopulated(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\tu64 __arena *val;\n+\n+\tval = bpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tif (!val)\n+\t\treturn 1;\n+\n+\tstash = (u64)val + PAGE_SIZE;\n+\tbpf_kfunc_arena_arg_test((u64 *)stash);\n+\n+\tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__failure __msg(\"arena pointer requires a program with an associated arena\")\n+int arena_arg_no_arena(void *ctx)\n+{\n+\tbpf_kfunc_arena_arg_test((u64 *)1);\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+__arch_x86_64\n+__failure __msg(\"is not a pointer to arena or scalar\")\n+int arena_arg_bad_reg(void *ctx)\n+{\n+\tu64 buf = 0;\n+\n+\t/* use the arena so the program passes the arena presence check */\n+\tbpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tbpf_kfunc_arena_arg_test(\u0026buf);\n+\treturn 0;\n+}\n+\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) \u0026\u0026 \\\n+\tdefined(__BPF_FEATURE_STACK_ARGUMENT)\n+SEC(\"syscall\")\n+__arch_x86_64\n+__failure __msg(\"arena pointer cannot be a stack argument\")\n+int arena_arg_stack(void *ctx)\n+{\n+\tbpf_arena_alloc_pages(\u0026arena, NULL, 1, NUMA_NO_NODE, 0);\n+\tbpf_kfunc_arena_stack_arg_test(1, 2, 3, 4, 5, (u64 *)1);\n+\treturn 0;\n+}\n+#else\n+SEC(\"syscall\")\n+__arch_x86_64\n+__description(\"arena_arg_stack: not supported, dummy test\")\n+__success\n+int arena_arg_stack(void *ctx)\n+{\n+\treturn 0;\n+}\n+#endif\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c\nnew file mode 100644\nindex 0000000000000..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_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c\nnew file mode 100644\nindex 0000000000000..1c0ec727d6374\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c\n@@ -0,0 +1,20 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"../test_kmods/bpf_testmod.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+/* No arena in the program: attaching to test_arena must be rejected. */\n+SEC(\"struct_ops/test_arena\")\n+int test_arena_no_arena(unsigned long long *ctx)\n+{\n+\treturn 0;\n+}\n+\n+SEC(\".struct_ops.link\")\n+struct bpf_testmod_ops3 testmod_arena_fail = {\n+\t.test_arena = (void *)test_arena_no_arena,\n+};\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\nindex eb0f9b5e18d85..2963c29f96ba5 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n@@ -237,6 +237,56 @@ __bpf_kfunc void bpf_kfunc_common_test(void)\n {\n }\n \n+__bpf_kfunc u64 bpf_kfunc_arena_arg_test(u64 *val__arena)\n+{\n+\tu64 old;\n+\n+\told = *val__arena;\n+\t*val__arena = old + 1;\n+\treturn old;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_nullable_arg_test(u64 *val__arena__nullable)\n+{\n+\tu64 old;\n+\n+\tif (!val__arena__nullable)\n+\t\treturn 0xdeadbeef;\n+\n+\told = *val__arena__nullable;\n+\t*val__arena__nullable = old + 1;\n+\treturn old;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_cap_test(u64 *val__arena)\n+{\n+\treturn (u64)val__arena;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_cap_nullable_test(u64 *val__arena__nullable)\n+{\n+\treturn (u64)val__arena__nullable;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_args5_test(u64 *a__arena, u64 *b__arena,\n+\t\t\t\t\t   u64 *c__arena, u64 *d__arena,\n+\t\t\t\t\t   u64 *e__arena__nullable)\n+{\n+\treturn *a__arena + *b__arena + *c__arena + *d__arena +\n+\t       (e__arena__nullable ? *e__arena__nullable : 0);\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_stack_arg_test(u64 a, u64 b, u64 c, u64 d, u64 e,\n+\t\t\t\t\t\tu64 *f__arena)\n+{\n+\treturn a + b + c + d + e + *f__arena;\n+}\n+\n+__bpf_kfunc u64 bpf_kfunc_arena_mixed_test(u64 *a__arena, u64 *b__arena__nullable)\n+{\n+\treturn *a__arena + (b__arena__nullable ? *b__arena__nullable : 0);\n+}\n+\n __bpf_kfunc void bpf_kfunc_dynptr_test(struct bpf_dynptr *ptr,\n \t\t\t\t       struct bpf_dynptr *ptr__nullable)\n {\n@@ -347,9 +397,29 @@ static int bpf_testmod_test_4(void)\n \treturn 0;\n }\n \n+static int bpf_testmod_ops3__test_arena(u64 *ptr__arena)\n+{\n+\treturn 0;\n+}\n+\n+static int bpf_testmod_ops3__test_arena_nullable(u64 *ptr__arena__nullable)\n+{\n+\treturn 0;\n+}\n+\n+static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,\n+\t\t\t\t\t      u64 e, u64 f, u64 g, u64 h,\n+\t\t\t\t\t      u64 *ptr__arena)\n+{\n+\treturn 0;\n+}\n+\n static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {\n \t.test_1 = bpf_testmod_test_3,\n \t.test_2 = bpf_testmod_test_4,\n+\t.test_arena = bpf_testmod_ops3__test_arena,\n+\t.test_arena_nullable = bpf_testmod_ops3__test_arena_nullable,\n+\t.test_arena_stack = bpf_testmod_ops3__test_arena_stack,\n };\n \n static void bpf_testmod_test_struct_ops3(void)\n@@ -368,6 +438,21 @@ __bpf_kfunc void bpf_testmod_ops3_call_test_2(void)\n \tst_ops3-\u003etest_2();\n }\n \n+__bpf_kfunc int bpf_testmod_ops3_call_test_arena(u64 *ptr__arena)\n+{\n+\treturn st_ops3-\u003etest_arena(ptr__arena);\n+}\n+\n+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena__nullable)\n+{\n+\treturn st_ops3-\u003etest_arena_nullable(ptr__arena__nullable);\n+}\n+\n+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_stack(u64 *ptr__arena)\n+{\n+\treturn st_ops3-\u003etest_arena_stack(1, 2, 3, 4, 5, 6, 7, 8, ptr__arena);\n+}\n+\n struct bpf_testmod_btf_type_tag_1 {\n \tint a;\n };\n@@ -755,6 +840,13 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)\n BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)\n BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)\n BTF_ID_FLAGS(func, bpf_kfunc_common_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_arg_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_nullable_arg_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_nullable_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_args5_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_stack_arg_test)\n+BTF_ID_FLAGS(func, bpf_kfunc_arena_mixed_test)\n BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)\n BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)\n BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)\n@@ -770,6 +862,9 @@ BTF_ID_FLAGS(func, bpf_testmod_ctx_create, KF_ACQUIRE | KF_RET_NULL)\n BTF_ID_FLAGS(func, bpf_testmod_ctx_release, KF_RELEASE)\n BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)\n BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)\n+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)\n+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)\n+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)\n BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);\n BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);\n BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\nindex 863fd10f16199..33f2af5b70857 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\n@@ -106,6 +106,12 @@ struct bpf_testmod_ops2 {\n struct bpf_testmod_ops3 {\n \tint (*test_1)(void);\n \tint (*test_2)(void);\n+\t/* Used to test arena pointer arguments. */\n+\tint (*test_arena)(u64 *ptr);\n+\tint (*test_arena_nullable)(u64 *ptr);\n+\t/* enough leading args to force @ptr onto the stack on x86 and arm64 */\n+\tint (*test_arena_stack)(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,\n+\t\t\t\tu64 g, u64 h, u64 *ptr);\n };\n \n struct st_ops_args {\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h\nindex c36bb911defa7..dc7c40bd12993 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h\n@@ -98,6 +98,16 @@ void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym;\n void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym;\n \n void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym;\n+__u64 bpf_kfunc_arena_arg_test(__u64 *val__arena) __ksym;\n+__u64 bpf_kfunc_arena_nullable_arg_test(__u64 *val__arena__nullable) __ksym;\n+__u64 bpf_kfunc_arena_cap_test(__u64 *val__arena) __ksym;\n+__u64 bpf_kfunc_arena_cap_nullable_test(__u64 *val__arena__nullable) __ksym;\n+__u64 bpf_kfunc_arena_args5_test(__u64 *a__arena, __u64 *b__arena,\n+\t\t\t\t __u64 *c__arena, __u64 *d__arena,\n+\t\t\t\t __u64 *e__arena__nullable) __ksym;\n+__u64 bpf_kfunc_arena_stack_arg_test(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,\n+\t\t\t\t     __u64 *f__arena) __ksym;\n+__u64 bpf_kfunc_arena_mixed_test(__u64 *a__arena, __u64 *b__arena__nullable) __ksym;\n int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym;\n int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;\n int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;\n@@ -111,6 +121,9 @@ u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym;\n #endif\n \n void bpf_testmod_test_mod_kfunc(int i) __ksym;\n+int bpf_testmod_ops3_call_test_arena(__u64 *ptr__arena) __ksym;\n+int bpf_testmod_ops3_call_test_arena_nullable(__u64 *ptr__arena__nullable) __ksym;\n+int bpf_testmod_ops3_call_test_arena_stack(__u64 *ptr__arena) __ksym;\n \n __u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,\n \t\t\t\t__u32 c, __u64 d) __ksym;\n"
}

2/1 2026/08/05 22:26 agent patch-evaluator
1m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_tramp_arena_base",
    "bpf_prog_has_arena_ctx_arg",
    "bpf_jit_supports_arena_args",
    "bpf_struct_ops_desc_init",
    "btf_distill_func_proto",
    "btf_ctx_access",
    "bpf_check_attach_target"
  ],
  "Reasoning": "The patch introduces support for passing arena pointers to BPF kfuncs and struct_ops callbacks. It modifies the BPF verifier to recognize `__arena` and `__arena__nullable` annotations, and updates the x86 BPF JIT and trampoline generation to properly rebase these pointers to the kernel's arena base address. This introduces new code paths in the verifier, BTF parsing, and JIT compilation, which are reachable from user space via BPF program loading and attachment, making it highly relevant for fuzzing.",
  "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 061523fe4714f9cfa42dc7b80a06875a6b964f96
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Aug 5 22:26:41 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index cbde86d082cce..c044659b76891 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -278,6 +278,43 @@ An example is given below::
                 ...
         }
 
+2.3.8 __arena and __arena__nullable Annotations
+-----------------------------------------------
+
+Both annotations indicate that the pointer argument points into the
+calling program's arena. The JIT rebases the value at the call site so
+the kfunc receives a directly dereferenceable kernel address, subject to
+the access rules described in :ref:`BPF_kfunc_arena_access` (at most
+``GUARD_SZ / 2``, 32 KiB, past the pointer in a single unchecked access).
+
+With ``__arena`` the rebase is unconditional and the argument is never
+NULL: a value whose lower 32 bits are zero arrives as the arena base
+address (arena offset 0). The kfunc must not check the argument for NULL.
+With ``__arena__nullable`` such a value arrives as NULL instead and the
+kfunc must check before dereferencing.
+
+An example is given below::
+
+        __bpf_kfunc int bpf_process_item(struct item *item__arena)
+        {
+        ...
+        }
+
+Calling such a kfunc requires the program to use an arena map and a JIT with
+arena argument support (currently x86-64); verification fails otherwise. The
+program can pass any value without compromising the kernel. A value that does
+not point into the arena is a program bug.
+
+The suffixes have the same meaning on the arguments of struct_ops stub
+functions, with the conversion running in the opposite direction. The
+kernel caller passes the kernel arena address and the trampoline converts
+it while saving the arguments, so the callback receives an arena pointer
+it can dereference directly. With ``__arena`` the kernel caller must not
+pass NULL. With ``__arena__nullable`` a NULL kernel pointer arrives as NULL.
+However, there is no obligation to prove to the verifier that such a pointer is
+non-NULL before use, in-line with existing semantics of arena pointers used in
+a program (or obtained from any other source).
+
 .. _BPF_kfunc_nodef:
 
 2.4 Using an existing kernel function
@@ -515,6 +552,8 @@ In order to accommodate such requirements, the verifier will enforce strict
 PTR_TO_BTF_ID type matching if two types have the exact same name, with one
 being suffixed with ``___init``.
 
+.. _BPF_kfunc_arena_access:
+
 2.8 Accessing arena memory through kfunc arguments
 --------------------------------------------------
 
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 01e7ce569c1ed..5c5c32aba2396 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1678,6 +1678,50 @@ static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip,
 	return 0;
 }
 
+/*
+ * Rebase the __arena args of a kfunc call to arena kernel addresses,
+ * rN = kern_vm_start + (u32)rN, with R12 holding kern_vm_start. A nullable
+ * arg preserves NULL by skipping the add, tested on the truncated value as
+ * arena NULL is offset 0. Return the number of emitted bytes.
+ */
+static int emit_kfunc_arena_args(struct bpf_prog *bpf_prog,
+				 const struct bpf_insn *insn, u8 **pprog)
+{
+	const struct btf_func_model *fm;
+	u8 *prog = *pprog;
+	u8 *start = prog;
+	int i;
+
+	fm = bpf_jit_find_kfunc_model(bpf_prog, insn);
+	if (!fm)
+		return -EINVAL;
+
+	for (i = 0; i < min_t(int, fm->nr_args, MAX_BPF_FUNC_REG_ARGS); i++) {
+		u8 flags = fm->arg_flags[i];
+		u32 reg = BPF_REG_1 + i;
+
+		if (!(flags & BTF_FMODEL_ARENA_ARG))
+			continue;
+		if (WARN_ON_ONCE(!bpf_prog->aux->arena))
+			return -EINVAL;
+
+		/* mov eN, eN: truncate and clear the upper 32 bits */
+		emit_mov_reg(&prog, false, reg, reg);
+		if (flags & BTF_FMODEL_NULLABLE_ARG) {
+			/* test eN, eN; jz over the 3-byte add */
+			maybe_emit_mod(&prog, reg, reg, false);
+			EMIT2(0x85, add_2reg(0xC0, reg, reg));
+			EMIT2(X86_JE, 3);
+		}
+		/* add rN, r12 */
+		maybe_emit_mod(&prog, reg, X86_REG_R12, true);
+		EMIT2(0x01, add_2reg(0xC0, reg, X86_REG_R12));
+	}
+
+	*pprog = prog;
+	return prog - start;
+}
+
 static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
 		  u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
 {
@@ -2583,6 +2627,12 @@ st:			insn_off = insn->off;
 			}
 			if (!imm32)
 				return -EINVAL;
+			if (src_reg == BPF_PSEUDO_KFUNC_CALL) {
+				err = emit_kfunc_arena_args(bpf_prog, insn, &prog);
+				if (err < 0)
+					return err;
+				ip += err;
+			}
 			if (priv_frame_ptr) {
 				push_r9(&prog);
 				ip += 2;
@@ -2993,11 +3043,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.
@@ -3006,6 +3084,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
@@ -3029,16 +3110,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);
 
@@ -3059,9 +3143,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++;
 			}
@@ -3357,6 +3445,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;
@@ -3369,6 +3458,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;
 
@@ -3503,7 +3594,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 */
@@ -3545,7 +3636,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
@@ -4046,6 +4137,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 73bacfc6444df..68ff66787ae1d 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1210,6 +1210,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;
@@ -1283,6 +1289,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:
@@ -2352,6 +2367,7 @@ static inline int bpf_fsession_cookie_cnt(struct bpf_tramp_nodes *nodes)
 
 int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,
 			       const struct bpf_ctx_arg_aux *info, u32 cnt);
+bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog);
 
 #if defined(CONFIG_CGROUP_BPF) && defined(CONFIG_BPF_LSM)
 int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index a2a40caca0a0e..a9555d17fd8ea 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);
 
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 32d5297c557e5..36ce3403fe599 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1183,6 +1183,7 @@ bool bpf_jit_supports_subprog_tailcalls(void);
 bool bpf_jit_supports_percpu_insn(void);
 bool bpf_jit_supports_kfunc_call(void);
 bool bpf_jit_supports_stack_args(void);
+bool bpf_jit_supports_arena_args(void);
 bool bpf_jit_supports_far_kfunc_call(void);
 bool bpf_jit_supports_exceptions(void);
 bool bpf_jit_supports_ptr_xchg(void);
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 4e7a48c02be5c..ced7b18246a96 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,35 @@ 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".
+		/*
+		 * 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);
+		if (!pointed_type || (!is_arena && !is_arena_nullable && !btf_type_is_struct(pointed_type))) {
 			pr_warn("stub function %s has %s tagging to an unsupported type\n",
 				stub_fname, suffix);
 			goto err_out;
@@ -268,7 +280,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 +483,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 09588b7b08b0a..83edf41c7fe9c 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;
@@ -10892,9 +10909,16 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
 	return btf_param_match_suffix(btf, arg, "__refcounted_kptr");
 }
 
+static bool is_kfunc_arg_arena_nullable(const struct btf *btf,
+					const struct btf_param *arg)
+{
+	return btf_param_match_suffix(btf, arg, "__arena__nullable");
+}
+
 static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
 {
-	return btf_param_match_suffix(btf, arg, "__nullable");
+	return !is_kfunc_arg_arena_nullable(btf, arg) &&
+	       btf_param_match_suffix(btf, arg, "__nullable");
 }
 
 static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
@@ -10912,6 +10936,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 is_kfunc_arg_arena_nullable(btf, arg) ||
+	       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)
@@ -11132,6 +11162,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 {
@@ -11417,7 +11448,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);
 
@@ -11466,7 +11496,29 @@ 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.
+		 */
+		arg_type = KF_ARG_PTR_TO_ARENA | PTR_MAYBE_NULL;
+	} 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) &&
@@ -12160,7 +12212,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;
@@ -12213,6 +12265,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;
@@ -12279,6 +12332,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)) {
@@ -18808,10 +18868,21 @@ int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,
 	return prog->aux->ctx_arg_info ? 0 : -ENOMEM;
 }
 
+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 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;
@@ -18890,10 +18961,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;
+			}
 		}
 	}
 
@@ -18914,8 +18998,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_"
 
@@ -19179,6 +19262,17 @@ 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 program attached to the
+		 * main program would see the converted offset as a regular BTF
+		 * pointer.
+		 */
+		if (prog_tracing && subprog == 0 &&
+		    bpf_prog_has_arena_ctx_arg(tgt_prog)) {
+			bpf_log(log, "Cannot trace 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",
@@ -20269,11 +20363,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;
 
@@ -20281,14 +20377,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..323d707c543fa
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <test_progs.h>
+
+#include "struct_ops_arena.skel.h"
+#include "struct_ops_arena_fail.skel.h"
+
+#if defined(__x86_64__)
+/*
+ * Attach callbacks with __arena and __arena__nullable arguments and drive
+ * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.
+ */
+static void arena_arg(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	struct struct_ops_arena *skel;
+	struct bpf_link *link = NULL;
+	int err;
+
+	skel = struct_ops_arena__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "struct_ops_arena__open_and_load"))
+		return;
+
+	link = bpf_map__attach_struct_ops(skel->maps.testmod_arena);
+	if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
+		goto out;
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.trigger),
+				     &topts);
+	ASSERT_OK(err, "test_run");
+	ASSERT_EQ(topts.retval, 0, "trigger_retval");
+
+out:
+	bpf_link__destroy(link);
+	struct_ops_arena__destroy(skel);
+}
+
+/*
+ * A program with no arena cannot attach to a member with an __arena
+ * argument.
+ */
+static void arena_arg_fail(void)
+{
+	struct struct_ops_arena_fail *skel;
+
+	skel = struct_ops_arena_fail__open_and_load();
+	if (ASSERT_ERR_PTR(skel, "struct_ops_arena_fail__open_and_load"))
+		return;
+
+	struct_ops_arena_fail__destroy(skel);
+}
+#endif
+
+/*
+ * Serialized because it attaches the singleton bpf_testmod_ops3, which
+ * test_struct_ops_private_stack also attaches; registering it twice fails
+ * with -EEXIST.
+ */
+void serial_test_struct_ops_arena(void)
+{
+	/*
+	 * Arena struct_ops arguments need JIT support, currently x86-64 only.
+	 * Elsewhere verification fails with "JIT does not support arena
+	 * arguments", so the programs cannot even load.
+	 */
+#if defined(__x86_64__)
+	if (test__start_subtest("arena_arg"))
+		arena_arg();
+	if (test__start_subtest("arena_arg_fail"))
+		arena_arg_fail();
+#else
+	test__skip();
+#endif
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index b79bafca68f7a..110a33b6e1c25 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,15 @@ static void run_tests_aux(const char *skel_name,
 
 #define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)
 
+/*
+ * The test kfuncs live in bpf_testmod. Resolving kfuncs against module
+ * BTFs needs CAP_SYS_ADMIN, so run with full capabilities instead of
+ * through the verifier tests' capability-restricted runner.
+ */
+void test_arena_kfunc(void)                   { RUN_TESTS(arena_kfunc); }
+
+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..d6c382ce81af6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARENA);
+	__uint(map_flags, BPF_F_MMAPABLE);
+	/* page 0 hosts the arena global, page 1 is for allocations */
+	__uint(max_entries, 2);
+} arena SEC(".maps");
+
+/*
+ * Occupies page 0 so no allocation lands at arena offset 0, which the
+ * nullable tests below must be able to tell apart from NULL.
+ */
+u64 __arena arena_pad;
+
+/* volatile to force the scalar reloads below */
+volatile u64 stash;
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_forms(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+	u64 __arena *val;
+	u64 ret;
+
+	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	if (!val)
+		return 1;
+
+	/* PTR_TO_ARENA argument */
+	*val = 41;
+	ret = bpf_kfunc_arena_arg_test((u64 *)val);
+	if (ret != 41 || *val != 42)
+		return 2;
+
+	/* the low 32 bits as a scalar */
+	stash = (u32)(u64)val;
+	ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+	if (ret != 42 || *val != 43)
+		return 3;
+
+	/* the full user address as a scalar */
+	stash = (u64)val;
+	bpf_addr_space_cast(stash, 1, 0);
+	ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+	if (ret != 43 || *val != 44)
+		return 4;
+
+	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+	return 0;
+}
+
+/*
+ * Pin the rebase semantics using the capture kfuncs, which return the raw
+ * argument value: __arena rebases unconditionally, so zero low 32 bits
+ * arrive as the arena kernel base, while __arena__nullable turns them into
+ * NULL.
+ */
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_rebase(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+	u64 __arena *val;
+	u64 base, off;
+
+	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	if (!val)
+		return 1;
+
+	base = bpf_kfunc_arena_cap_test(NULL);
+	if (!base)
+		return 2;
+
+	/* only the low 32 bits contribute */
+	stash = 0xbadc0ffe00000000;
+	if (bpf_kfunc_arena_cap_test((u64 *)stash) != base)
+		return 3;
+
+	off = (u32)(u64)val;
+	if (bpf_kfunc_arena_cap_test((u64 *)val) != base + off)
+		return 4;
+
+	if (bpf_kfunc_arena_cap_nullable_test(NULL) != 0)
+		return 5;
+
+	stash = 0xbadc0ffe00000000;
+	if (bpf_kfunc_arena_cap_nullable_test((u64 *)stash) != 0)
+		return 6;
+
+	if (bpf_kfunc_arena_cap_nullable_test((u64 *)val) != base + off)
+		return 7;
+
+	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+	return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_nullable(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+	u64 __arena *val;
+	u64 ret;
+
+	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	if (!val)
+		return 1;
+
+	*val = 41;
+	ret = bpf_kfunc_arena_nullable_arg_test((u64 *)val);
+	if (ret != 41 || *val != 42)
+		return 2;
+
+	if (bpf_kfunc_arena_nullable_arg_test(NULL) != 0xdeadbeef)
+		return 3;
+
+	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+	return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_args5(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+	u64 __arena *val;
+
+	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	if (!val)
+		return 1;
+
+	val[0] = 1;
+	val[1] = 2;
+	val[2] = 4;
+	val[3] = 8;
+	val[4] = 16;
+
+	if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+				       (u64 *)&val[2], (u64 *)&val[3],
+				       (u64 *)&val[4]) != 31)
+		return 2;
+	if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+				       (u64 *)&val[2], (u64 *)&val[3], NULL) != 15)
+		return 3;
+
+	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+	return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_mixed(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+	u64 __arena *val;
+
+	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	if (!val)
+		return 1;
+
+	val[0] = 7;
+	val[1] = 5;
+
+	if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], NULL) != 7)
+		return 2;
+
+	if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], (u64 *)&val[1]) != 12)
+		return 3;
+
+	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+	return 0;
+}
+
+/* kernel-side faults on unpopulated pages recover via the scratch page */
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_unpopulated(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+	u64 __arena *val;
+
+	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	if (!val)
+		return 1;
+
+	stash = (u64)val + PAGE_SIZE;
+	bpf_kfunc_arena_arg_test((u64 *)stash);
+
+	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+	return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__failure __msg("arena pointer requires a program with an associated arena")
+int arena_arg_no_arena(void *ctx)
+{
+	bpf_kfunc_arena_arg_test((u64 *)1);
+	return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__failure __msg("is not a pointer to arena or scalar")
+int arena_arg_bad_reg(void *ctx)
+{
+	u64 buf = 0;
+
+	/* use the arena so the program passes the arena presence check */
+	bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	bpf_kfunc_arena_arg_test(&buf);
+	return 0;
+}
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
+	defined(__BPF_FEATURE_STACK_ARGUMENT)
+SEC("syscall")
+__arch_x86_64
+__failure __msg("arena pointer cannot be a stack argument")
+int arena_arg_stack(void *ctx)
+{
+	bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	bpf_kfunc_arena_stack_arg_test(1, 2, 3, 4, 5, (u64 *)1);
+	return 0;
+}
+#else
+SEC("syscall")
+__arch_x86_64
+__description("arena_arg_stack: not supported, dummy test")
+__success
+int arena_arg_stack(void *ctx)
+{
+	return 0;
+}
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
new file mode 100644
index 0000000000000..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_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
new file mode 100644
index 0000000000000..1c0ec727d6374
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* No arena in the program: attaching to test_arena must be rejected. */
+SEC("struct_ops/test_arena")
+int test_arena_no_arena(unsigned long long *ctx)
+{
+	return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_arena_fail = {
+	.test_arena = (void *)test_arena_no_arena,
+};
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index eb0f9b5e18d85..2963c29f96ba5 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -237,6 +237,56 @@ __bpf_kfunc void bpf_kfunc_common_test(void)
 {
 }
 
+__bpf_kfunc u64 bpf_kfunc_arena_arg_test(u64 *val__arena)
+{
+	u64 old;
+
+	old = *val__arena;
+	*val__arena = old + 1;
+	return old;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_nullable_arg_test(u64 *val__arena__nullable)
+{
+	u64 old;
+
+	if (!val__arena__nullable)
+		return 0xdeadbeef;
+
+	old = *val__arena__nullable;
+	*val__arena__nullable = old + 1;
+	return old;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_cap_test(u64 *val__arena)
+{
+	return (u64)val__arena;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_cap_nullable_test(u64 *val__arena__nullable)
+{
+	return (u64)val__arena__nullable;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_args5_test(u64 *a__arena, u64 *b__arena,
+					   u64 *c__arena, u64 *d__arena,
+					   u64 *e__arena__nullable)
+{
+	return *a__arena + *b__arena + *c__arena + *d__arena +
+	       (e__arena__nullable ? *e__arena__nullable : 0);
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_stack_arg_test(u64 a, u64 b, u64 c, u64 d, u64 e,
+						u64 *f__arena)
+{
+	return a + b + c + d + e + *f__arena;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_mixed_test(u64 *a__arena, u64 *b__arena__nullable)
+{
+	return *a__arena + (b__arena__nullable ? *b__arena__nullable : 0);
+}
+
 __bpf_kfunc void bpf_kfunc_dynptr_test(struct bpf_dynptr *ptr,
 				       struct bpf_dynptr *ptr__nullable)
 {
@@ -347,9 +397,29 @@ static int bpf_testmod_test_4(void)
 	return 0;
 }
 
+static int bpf_testmod_ops3__test_arena(u64 *ptr__arena)
+{
+	return 0;
+}
+
+static int bpf_testmod_ops3__test_arena_nullable(u64 *ptr__arena__nullable)
+{
+	return 0;
+}
+
+static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,
+					      u64 e, u64 f, u64 g, u64 h,
+					      u64 *ptr__arena)
+{
+	return 0;
+}
+
 static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {
 	.test_1 = bpf_testmod_test_3,
 	.test_2 = bpf_testmod_test_4,
+	.test_arena = bpf_testmod_ops3__test_arena,
+	.test_arena_nullable = bpf_testmod_ops3__test_arena_nullable,
+	.test_arena_stack = bpf_testmod_ops3__test_arena_stack,
 };
 
 static void bpf_testmod_test_struct_ops3(void)
@@ -368,6 +438,21 @@ __bpf_kfunc void bpf_testmod_ops3_call_test_2(void)
 	st_ops3->test_2();
 }
 
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena(u64 *ptr__arena)
+{
+	return st_ops3->test_arena(ptr__arena);
+}
+
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena__nullable)
+{
+	return st_ops3->test_arena_nullable(ptr__arena__nullable);
+}
+
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_stack(u64 *ptr__arena)
+{
+	return st_ops3->test_arena_stack(1, 2, 3, 4, 5, 6, 7, 8, ptr__arena);
+}
+
 struct bpf_testmod_btf_type_tag_1 {
 	int a;
 };
@@ -755,6 +840,13 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)
 BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)
 BTF_ID_FLAGS(func, bpf_kfunc_common_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_nullable_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_nullable_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_args5_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_stack_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_mixed_test)
 BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
 BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)
 BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)
@@ -770,6 +862,9 @@ BTF_ID_FLAGS(func, bpf_testmod_ctx_create, KF_ACQUIRE | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_testmod_ctx_release, KF_RELEASE)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)
 BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
 BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
 BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
index 863fd10f16199..33f2af5b70857 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -106,6 +106,12 @@ struct bpf_testmod_ops2 {
 struct bpf_testmod_ops3 {
 	int (*test_1)(void);
 	int (*test_2)(void);
+	/* Used to test arena pointer arguments. */
+	int (*test_arena)(u64 *ptr);
+	int (*test_arena_nullable)(u64 *ptr);
+	/* enough leading args to force @ptr onto the stack on x86 and arm64 */
+	int (*test_arena_stack)(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,
+				u64 g, u64 h, u64 *ptr);
 };
 
 struct st_ops_args {
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index c36bb911defa7..dc7c40bd12993 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -98,6 +98,16 @@ void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym;
 void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym;
 
 void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym;
+__u64 bpf_kfunc_arena_arg_test(__u64 *val__arena) __ksym;
+__u64 bpf_kfunc_arena_nullable_arg_test(__u64 *val__arena__nullable) __ksym;
+__u64 bpf_kfunc_arena_cap_test(__u64 *val__arena) __ksym;
+__u64 bpf_kfunc_arena_cap_nullable_test(__u64 *val__arena__nullable) __ksym;
+__u64 bpf_kfunc_arena_args5_test(__u64 *a__arena, __u64 *b__arena,
+				 __u64 *c__arena, __u64 *d__arena,
+				 __u64 *e__arena__nullable) __ksym;
+__u64 bpf_kfunc_arena_stack_arg_test(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
+				     __u64 *f__arena) __ksym;
+__u64 bpf_kfunc_arena_mixed_test(__u64 *a__arena, __u64 *b__arena__nullable) __ksym;
 int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym;
 int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;
 int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;
@@ -111,6 +121,9 @@ u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym;
 #endif
 
 void bpf_testmod_test_mod_kfunc(int i) __ksym;
+int bpf_testmod_ops3_call_test_arena(__u64 *ptr__arena) __ksym;
+int bpf_testmod_ops3_call_test_arena_nullable(__u64 *ptr__arena__nullable) __ksym;
+int bpf_testmod_ops3_call_test_arena_stack(__u64 *ptr__arena) __ksym;
 
 __u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,
 				__u32 c, __u64 d) __ksym;

3/2 2026/08/05 22:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26003
							output: 34
							thoughts: 1155
						

Thoughts:
**Analyzing the BPF Arena Pointer Patch**

Okay, so I've got this patch adding `__arena` and `__arena__nullable` annotations to BPF kfuncs and `struct_ops`, which is pretty interesting. It's touching the x86 BPF JIT, plus some core BPF components like the verifier, BTF handling, struct ops, and even the trampoline logic. Given the verifier and JIT are involved, this absolutely screams for fuzzing.

My initial thought is to identify key symbols impacted. I'm seeing additions and modifications across a range of functions – some static, some global. The static ones are less of a direct concern initially, but I need to understand their context. I'll need to pay close attention to the non-static ones: `emit_kfunc_arena_args`, `emit_arena_arg_conv`, `save_args`, `__arch_prepare_bpf_trampoline`, `bpf_jit_supports_arena_args`, `bpf_tramp_arena_base`, `prepare_arg_info`, `btf_ctx_access`, `__get_arg_fmodel_flags`, `btf_distill_func_proto`, `bpf_prepare_btf_info`, `bpf_trampoline_link_prog`, `add_subprogs`, `add_kfuncs`, `is_kfunc_arg_arena_nullable`, `is_kfunc_arg_arena`, `get_kfunc_arg_type`, `check_kfunc_args`, `bpf_prog_has_arena_ctx_arg`, `check_struct_ops_btf_id`, `bpf_check_attach_target`, `bpf_check`

I'm thinking the focus should be on non-static, non-inlined API entrypoints – anything that's a potential boundary or interaction point. I'm zeroing in on a few candidates: `bpf_tramp_arena_base`, `bpf_prog_has_arena_ctx_arg`, `bpf_prepare_btf_info`, `bpf_jit_supports_arena_args`. Let's throw in `bpf_check_attach_target`, `bpf_trampoline_link_prog`, `btf_distill_func_proto`, `btf_ctx_access` because these are involved. I'll need to check the calls to `prepare_arg_info` in `bpf_struct_ops_desc_init`, too.

Wait, `bpf_check` is way too broad. Let's filter that out. I'll need to check `bpf_struct_ops_desc_init` in `kernel/bpf/bpf_struct_ops.c`. I will also check `bpf_trampoline_link_prog` in `kernel/bpf/trampoline.c`, `btf_ctx_access` in `kernel/bpf/btf.c`, `btf_distill_func_proto` in `kernel/bpf/btf.c`, and `bpf_check_attach_target` in `kernel/bpf/verifier.c`

I'm going to work with `bpf_tramp_arena_base`, `bpf_prog_has_arena_ctx_arg`, `bpf_prepare_btf_info`, and `bpf_jit_supports_arena_args`.

Now, I need to understand the Kconfig dependencies to guide my testing and determine what the patch activates. `kernel/bpf/` points to `CONFIG_BPF_SYSCALL`. The JIT part in `arch/x86/net/bpf_jit_comp.c` uses `CONFIG_BPF_JIT`. The `struct_ops` side is likely under both `CONFIG_BPF_JIT` and `CONFIG_BPF_SYSCALL`. I'll need to double-check `kernel/bpf/Kconfig` to be sure I'm not missing anything.



4/2 2026/08/05 22:26 tool grepper
0m Args:
{
  "Expression": "config BPF_SYSCALL",
  "PathPrefix": "kernel/bpf/Kconfig"
}

Results:
{
  "Output": "kernel/bpf/Kconfig=25=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-26-\nkernel/bpf/Kconfig:27:config BPF_SYSCALL\nkernel/bpf/Kconfig-28-\tbool \"Enable bpf() system call\"\n"
}

5/2 2026/08/05 22:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28164
							output: 34
							thoughts: 21
						

Thoughts:
Here's my thought process, as if I were considering this myself:

**Investigating BPF_JIT Configuration**

Okay, so I need to figure out where the `BPF_JIT` configuration option is defined within the kernel build system. The logical place to start would be the architecture-specific Kconfig files, particularly the x86 one, given this is likely x86 related. Therefore, let's drill down into `arch/x86/Kconfig`. That's where I expect to find the relevant option, alongside other architecture-specific settings. I'll need to examine that file closely to understand its dependencies, default values, and any relevant relationships to other features.



6/2 2026/08/05 22:26 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config BPF_JIT",
  "PathPrefix": "arch/x86/Kconfig"
}

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

7/2 2026/08/05 22:26 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28381
							output: 33
							thoughts: 20
						

Thoughts:
Here's my thought process, as if I were considering this myself:

**Locating the Relevant Configuration Options**

Okay, so I need to find where the configuration options for this are located, and my initial hunch is that they'll be in one of the core configuration files. The kernel configuration is pretty well-organized, so the most likely candidates are `kernel/bpf/Kconfig` (since this is BPF-related) or possibly `net/Kconfig` (because BPF is heavily used in networking contexts). I'll start by checking those files directly. It's a pretty standard convention, so I'm reasonably confident I'll find what I'm looking for there, or at least be pointed in the right direction.



8/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "config BPF_JIT",
  "PathPrefix": "kernel/bpf/Kconfig"
}

Results:
{
  "Output": "kernel/bpf/Kconfig=27=config BPF_SYSCALL\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"
}

9/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28816
							output: 26
							thoughts: 39
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating BPF Configuration Dependencies**

Alright, so we're dealing with BPF, and it looks like we need both `BPF_SYSCALL` and `BPF_JIT` enabled. That's pretty standard, generally indicating we're planning to leverage the system call interface and, of course, the JIT compiler for BPF bytecode. Makes sense. But before I go any further, I want to make *absolutely* sure there aren't any other subtly related configurations. I need to take a quick look to see if there are any specific options or flags related to `bpf_arena`. I'll cross-reference the documentation and any related kernel modules to be fully informed before I proceed. Don't want to get tripped up by a hidden dependency.



10/2 2026/08/05 22:27 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config BPF_ARENA",
  "PathPrefix": ""
}

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

11/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29081
							output: 34
							thoughts: 61
						

Thoughts:
Okay, here's my interpretation of those thoughts, presented as my own mental process:

**Diving into BPF Compilation**

Hmm, there's no `CONFIG_BPF_ARENA` defined. That's a bit odd; I could have sworn I'd seen it somewhere. Wait, hold on. Is it `CONFIG_BPF_EVENTS` instead? Perhaps it's a newer, more streamlined option, or maybe it's related, just named differently. Right, need to verify that.

To figure out how the `bpf_struct_ops.c` and `trampoline.c` files are being handled, I should examine `kernel/bpf/Makefile`. That should reveal how those specific sources are built and which configuration options are relevant to them. It's time to trace the build process and see how the dependencies are arranged.



12/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "bpf_struct_ops.o",
  "PathPrefix": "kernel/bpf/Makefile"
}

Results:
{
  "Output": "kernel/bpf/Makefile=43=ifeq ($(CONFIG_BPF_JIT),y)\nkernel/bpf/Makefile:44:obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o\nkernel/bpf/Makefile-45-obj-$(CONFIG_BPF_SYSCALL) += cpumask.o\n"
}

13/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29457
							output: 30
							thoughts: 44
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience:

**BPF Verification and JIT Compilation Check**

Alright, it looks like just enabling `CONFIG_BPF_SYSCALL` and `CONFIG_BPF_JIT` should give me everything I need for this investigation. That's a good starting point and hopefully simplifies things.

Now, let's take a closer look at the `trampoline.o` object file. My next step is to examine its role within the `kernel/bpf/Makefile`. Understanding its build process will provide valuable insight into how the BPF trampolines are created and integrated, which is key to understanding the underlying mechanism.



14/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "trampoline.o",
  "PathPrefix": "kernel/bpf/Makefile"
}

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

15/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29787
							output: 35
							thoughts: 156
						

Thoughts:
**My Initial Assessment of the BPF JIT and Trampolines**

Okay, so it looks like `CONFIG_BPF_JIT` is a hard requirement for `trampoline.o` – that's the first thing I need to remember. Now, let's take a look at the key symbols related to this. Based on my quick scan, these are the ones I should really focus on:

*   `bpf_tramp_arena_base`
*   `bpf_prog_has_arena_ctx_arg`
*   `bpf_prepare_btf_info`
*   `bpf_jit_supports_arena_args`
*   `btf_distill_func_proto`
*   `btf_ctx_access`
*   `bpf_struct_ops_desc_init`
*   `bpf_check_attach_target`

Now, I'm especially curious about `bpf_struct_ops_desc_init`. I need to quickly determine whether it's exported or globally accessible because that will determine how easy it is to interact with its functionality.



16/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "bpf_struct_ops_desc_init",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/bpf_struct_ops.c=347=int bpf_struct_ops_supported(const struct bpf_struct_ops *st_ops, u32 moff)\n--\nkernel/bpf/bpf_struct_ops.c-353-\nkernel/bpf/bpf_struct_ops.c:354:int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,\nkernel/bpf/bpf_struct_ops.c-355-\t\t\t     struct btf *btf,\n--\nkernel/bpf/btf.c=9889=btf_add_struct_ops(struct btf *btf, struct bpf_struct_ops *st_ops,\n--\nkernel/bpf/btf.c-9920-\nkernel/bpf/btf.c:9921:\terr = bpf_struct_ops_desc_init(\u0026tab-\u003eops[btf-\u003estruct_ops_tab-\u003ecnt], btf, log);\nkernel/bpf/btf.c-9922-\tif (err)\n"
}

17/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30517
							output: 33
							thoughts: 21
						

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

**Analyzing the Scope & Method**

Right, so we're dealing with something that's confirmed to be global in nature. Good, important to establish that at the outset. Now, with that established I need to look into `bpf_check_attach_target`. That's where I need to focus to understand the specifics of how this global influence manifests, and how it's being managed or constrained. Need to dive deep into that function to see what's going on there.



18/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "bpf_check_attach_target",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/btf.c=6850=bool btf_ctx_access(int off, int size, enum bpf_access_type type,\n--\nkernel/bpf/btf.c-7031-\t\t\treturn false;\nkernel/bpf/btf.c:7032:\t\t/* Checked by bpf_check_attach_target */\nkernel/bpf/btf.c-7033-\t\ttname += sizeof(\"btf_trace_\") - 1;\n--\nkernel/bpf/syscall.c=3624=static int bpf_tracing_prog_attach(struct bpf_prog *prog,\n--\nkernel/bpf/syscall.c-3756-\nkernel/bpf/syscall.c:3757:\t\terr = bpf_check_attach_target(NULL, prog, tgt_prog, btf_id,\nkernel/bpf/syscall.c-3758-\t\t\t\t\t      \u0026tgt_info);\n--\nkernel/bpf/trampoline.c=1123=int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,\n--\nkernel/bpf/trampoline.c-1133-\nkernel/bpf/trampoline.c:1134:\terr = bpf_check_attach_target(NULL, prog, NULL,\nkernel/bpf/trampoline.c-1135-\t\t\t\t      prog-\u003eaux-\u003eattach_btf_id,\n--\nkernel/bpf/verifier.c=12130=static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-12141- * LSM hooks and iters (both sleepable and non-sleepable) are safe.\nkernel/bpf/verifier.c:12142: * Any sleepable progs are also safe since bpf_check_attach_target() enforce\nkernel/bpf/verifier.c-12143- * them can only be attached to some specific hook points.\n--\nkernel/bpf/verifier.c=19192=static bool attach_uses_trampoline_retval(enum bpf_attach_type type)\n--\nkernel/bpf/verifier.c-19205-\nkernel/bpf/verifier.c:19206:int bpf_check_attach_target(struct bpf_verifier_log *log,\nkernel/bpf/verifier.c-19207-\t\t\t    const struct bpf_prog *prog,\n--\nkernel/bpf/verifier.c=19630=static int check_attach_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-19660-\nkernel/bpf/verifier.c:19661:\tret = bpf_check_attach_target(\u0026env-\u003elog, prog, tgt_prog, btf_id, \u0026tgt_info);\nkernel/bpf/verifier.c-19662-\tif (ret)\n"
}

19/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31453
							output: 34
							thoughts: 22
						

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

**Global Scope Check and Targeted Function Review**

Okay, so the scope... yeah, I'm thinking this is definitely global. Need to confirm that.

Alright, now let's drill down and check the `btf_distill_func_proto` function. I need to take a look at that implementation. Gotta check how that's working within the global context, and ensure its handling of whatever it is concerned with aligns with what I'm expecting. It's a key piece in this, so let's check.



20/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "btf_distill_func_proto",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/bpf_struct_ops.c=354=int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,\n--\nkernel/bpf/bpf_struct_ops.c-461-\nkernel/bpf/bpf_struct_ops.c:462:\t\tif (btf_distill_func_proto(log, btf,\nkernel/bpf/bpf_struct_ops.c-463-\t\t\t\t\t   func_proto, mname,\n--\nkernel/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--\nkernel/bpf/verifier.c=2728=int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)\n--\nkernel/bpf/verifier.c-2811-\nkernel/bpf/verifier.c:2812:\terr = btf_distill_func_proto(\u0026env-\u003elog, kfunc.btf, kfunc.proto, kfunc.name, \u0026func_model);\nkernel/bpf/verifier.c-2813-\tif (err)\n--\nkernel/bpf/verifier.c=19206=int bpf_check_attach_target(struct bpf_verifier_log *log,\n--\nkernel/bpf/verifier.c-19433-\t\t\treturn -EINVAL;\nkernel/bpf/verifier.c:19434:\t\tret = btf_distill_func_proto(log, btf, t, tname, \u0026tgt_info-\u003efmodel);\nkernel/bpf/verifier.c-19435-\t\tif (ret)\n--\nkernel/bpf/verifier.c-19476-\nkernel/bpf/verifier.c:19477:\t\tret = btf_distill_func_proto(log, btf, t, tname, \u0026tgt_info-\u003efmodel);\nkernel/bpf/verifier.c-19478-\t\tif (ret \u003c 0)\n--\nkernel/bpf/verifier.c=19730=int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 btf_id,\n--\nkernel/bpf/verifier.c-19759-\t\treturn -EINVAL;\nkernel/bpf/verifier.c:19760:\terr = btf_distill_func_proto(NULL, btf, t, tname, \u0026tgt_info-\u003efmodel);\nkernel/bpf/verifier.c-19761-\tif (err \u003c 0)\n"
}

21/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32414
							output: 31
							thoughts: 19
						

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

**Global Scope Check and Contextual Access**

Right, so the initial thought is clear: we're dealing with a global situation here. That's the baseline assumption. Now, given that global nature, I need to immediately consider how this impacts specific access patterns and potential side effects across the entire system.

Therefore, the logical next step is to examine `btf_ctx_access`. I need to understand the behavior of this function with global variables or global state. Are there potential race conditions I need to understand? Are there dependencies to unpack? I need to know: what's the access granularity? What are the implications of the context within which this access occurs, especially if that context is global, or influenced by global state? The goal is to fully understand its behaviour in the global context, as understanding this function's behaviour is the key to understanding this.



22/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "btf_ctx_access",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/bpf_lsm.c=416=const struct bpf_verifier_ops lsm_verifier_ops = {\nkernel/bpf/bpf_lsm.c-417-\t.get_func_proto = bpf_lsm_func_proto,\nkernel/bpf/bpf_lsm.c:418:\t.is_valid_access = btf_ctx_access,\nkernel/bpf/bpf_lsm.c-419-};\n--\nkernel/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"
}

23/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32984
							output: 34
							thoughts: 22
						

Thoughts:
Okay, here's my summarized thought process, presented as if I were thinking it:

**Global Scope and BTF Preparation**

Right, so we're dealing with something that has a global impact. I need to keep that in mind as I approach this issue.

Now, let's dive into the specifics. I need to understand what's happening at the core. The first thing that comes to mind is `bpf_prepare_btf_info`. That's where I need to start my investigation. I need to understand what's happening there to find the root cause of this global issue.



24/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "bpf_prepare_btf_info",
  "PathPrefix": "kernel/bpf/"
}

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--\nkernel/bpf/verifier.c=20258=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-20366-\t/* Prepare BTF and func_info needed to discover all subprograms. */\nkernel/bpf/verifier.c:20367:\tret = bpf_prepare_btf_info(env, attr, uattr);\nkernel/bpf/verifier.c-20368-\tif (ret \u003c 0)\n"
}

25/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33444
							output: 37
							thoughts: 25
						

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

**Analyzing the Scope & Context**

Right, so we're dealing with something that's unequivocally global. This immediately raises flags and prompts a thorough examination of its implications across the entire system. Before proceeding, I'll need to drill down into the specifics.

My next step is to examine `bpf_prog_has_arena_ctx_arg`. This is crucial. This will tell me whether we have arena context arguments. This could have a global effect, and might be important in understanding the full scope of the initial assertion. The outcome here will steer the subsequent line of inquiry.



26/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "bpf_prog_has_arena_ctx_arg",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/trampoline.c=942=static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,\n--\nkernel/bpf/trampoline.c-956-\t */\nkernel/bpf/trampoline.c:957:\tif (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node-\u003elink-\u003eprog)))\nkernel/bpf/trampoline.c-958-\t\treturn -ENOTSUPP;\n--\nkernel/bpf/verifier.c=18862=int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,\n--\nkernel/bpf/verifier.c-18870-\nkernel/bpf/verifier.c:18871:bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)\nkernel/bpf/verifier.c-18872-{\n--\nkernel/bpf/verifier.c=19206=int bpf_check_attach_target(struct bpf_verifier_log *log,\n--\nkernel/bpf/verifier.c-19271-\t\tif (prog_tracing \u0026\u0026 subprog == 0 \u0026\u0026\nkernel/bpf/verifier.c:19272:\t\t    bpf_prog_has_arena_ctx_arg(tgt_prog)) {\nkernel/bpf/verifier.c-19273-\t\t\tbpf_log(log, \"Cannot trace a target with arena context arguments\\n\");\n"
}

27/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34038
							output: 34
							thoughts: 22
						

Thoughts:
Okay, here's my summary, tailored for an expert audience, as if it's a direct stream of my thoughts:

**Addressing Global Scope in eBPF Context**

Right, I'm thinking about the scope here. The situation... Yes, it's definitely global in nature, not isolated. It's impacting more than just one specific local area.

Now, let's drill down. I need to take a look at `bpf_tramp_arena_base`. That's where I need to start. I'm going to look at that variable. That's the key point of investigation here.



28/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "bpf_tramp_arena_base",
  "PathPrefix": "kernel/bpf/"
}

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"
}

29/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34376
							output: 35
							thoughts: 23
						

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

**Global Scope and JIT Arena Arguments**

Alright, that observation about the situation at hand... yep, it's definitely something that's impacting the system globally. No localized issue here; we're dealing with a system-wide effect.

Now, given that scope, and with my experience in these low-level contexts, my next immediate thought is to check a specific function. We need to ascertain the support level. Specifically, I need to check the status of `bpf_jit_supports_arena_args`. I have a feeling the support level is going to dictate the appropriate course of action, and it would be a critical dependency for a few different potential solutions that I am mulling over. Let's see what that flag says.



30/2 2026/08/05 22:27 tool grepper
0m Args:
{
  "Expression": "bpf_jit_supports_arena_args",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/core.c=3306=bool __weak bpf_jit_supports_stack_args(void)\n--\nkernel/bpf/core.c-3310-\nkernel/bpf/core.c:3311:bool __weak bpf_jit_supports_arena_args(void)\nkernel/bpf/core.c-3312-{\n--\nkernel/bpf/verifier.c=11422=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-11499-\telse if (is_kfunc_arg_arena(meta-\u003ebtf, \u0026args[arg])) {\nkernel/bpf/verifier.c:11500:\t\tif (!bpf_jit_supports_arena_args()) {\nkernel/bpf/verifier.c-11501-\t\t\tverbose(env, \"JIT does not support kfunc %s() with arena pointer arguments\\n\",\n--\nkernel/bpf/verifier.c=18881=static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-18970-\t\tif (base_type(info-\u003ereg_type) == PTR_TO_ARENA) {\nkernel/bpf/verifier.c:18971:\t\t\tif (!bpf_jit_supports_arena_args()) {\nkernel/bpf/verifier.c-18972-\t\t\t\tverbose(env, \"JIT does not support arena arguments\\n\");\n"
}

31/2 2026/08/05 22:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35019
							output: 241
							thoughts: 1103
						

Thoughts:
**Analyzing the BPF Arena Pointer Patch**

Okay, so this patch is global, which means it potentially touches a lot of different areas. My initial pass has identified several key symbols. Let's see... I've got:

*   `bpf_tramp_arena_base`
*   `bpf_prog_has_arena_ctx_arg`
*   `bpf_jit_supports_arena_args`
*   `bpf_struct_ops_desc_init`
*   `btf_distill_func_proto`
*   `btf_ctx_access`
*   `bpf_check_attach_target`

Wait, `bpf_prepare_btf_info` isn't a new feature, just a rename. I'll drop that. And, looking at the others, `btf_distill_func_proto`, `btf_ctx_access`, `bpf_struct_ops_desc_init` and `bpf_check_attach_target` are modified, but they are relatively generic functions, so I'll keep them as I don't want to exclude any potential areas.

Now, I'll need to check for any other functions. Hmm, is `bpf_arena_get_kern_vm_start` affected? No, that looks untouched by this patch.

Right, let's refine that focus list one more time:

*   `bpf_tramp_arena_base`
*   `bpf_prog_has_arena_ctx_arg`
*   `bpf_jit_supports_arena_args`
*   `bpf_struct_ops_desc_init`
*   `btf_distill_func_proto`
*   `btf_ctx_access`
*   `bpf_check_attach_target`

Next, I need to check the relevant configuration options. `CONFIG_BPF_SYSCALL` and `CONFIG_BPF_JIT` seem like obvious candidates. The patch also touches `arch/x86/net/bpf_jit_comp.c`, which implies `CONFIG_X86` is needed. However, since `X86` is an architecture config, and not usually specified in `EnableConfigs` I will just specify `BPF_SYSCALL` and `BPF_JIT`.

I should probably double-check if `BPF_JIT_ALWAYS_ON` is necessary. No, `BPF_JIT` alone is sufficient.

Okay, let's craft the reasoning for this. This patch enables passing arena pointers to BPF kfuncs and `struct_ops` callbacks. It changes the BPF verifier to handle `__arena` and `__arena__nullable` annotations, and updates the x86 BPF JIT and trampoline generation to correctly rebase these pointers to the kernel's arena base address. This adds new code paths in the verifier, BTF parsing, and JIT compilation, all reachable through user-space interaction via BPF program loading and attachment, which makes it high-priority for fuzzing.

Therefore, my focused symbols are:

*   `bpf_tramp_arena_base`
*   `bpf_prog_has_arena_ctx_arg`
*   `bpf_jit_supports_arena_args`
*   `bpf_struct_ops_desc_init`
*   `btf_distill_func_proto`
*   `btf_ctx_access`
*   `bpf_check_attach_target`

and the necessary configs are:

*   `BPF_SYSCALL`
*   `BPF_JIT`

Now, let's just make sure those symbols are indeed what I think they are. `bpf_tramp_arena_base` looks correct; it's a new function in `kernel/bpf/trampoline.c`. Good. `bpf_prog_has_arena_ctx_arg` is new, found in `kernel/bpf/verifier.c`. Perfect. And `bpf_jit_supports_arena_args` is newly added in `kernel/bpf/core.c` and `arch/x86/net/bpf_jit_comp.c`. Checked. Looks like the other 4 are modifications to files such as `kernel/bpf/bpf_struct_ops.c` and `kernel/bpf/btf.c`. All good.

Seems like everything checks out.



32/2 2026/08/05 22:28 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_tramp_arena_base",
    "bpf_prog_has_arena_ctx_arg",
    "bpf_jit_supports_arena_args",
    "bpf_struct_ops_desc_init",
    "btf_distill_func_proto",
    "btf_ctx_access",
    "bpf_check_attach_target"
  ],
  "Reasoning": "The patch introduces support for passing arena pointers to BPF kfuncs and struct_ops callbacks. It modifies the BPF verifier to recognize `__arena` and `__arena__nullable` annotations, and updates the x86 BPF JIT and trampoline generation to properly rebase these pointers to the kernel's arena base address. This introduces new code paths in the verifier, BTF parsing, and JIT compilation, which are reachable from user space via BPF program loading and attachment, making it highly relevant for fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_tramp_arena_base",
    "bpf_prog_has_arena_ctx_arg",
    "bpf_jit_supports_arena_args",
    "bpf_struct_ops_desc_init",
    "btf_distill_func_proto",
    "btf_ctx_access",
    "bpf_check_attach_target"
  ],
  "Reasoning": "The patch introduces support for passing arena pointers to BPF kfuncs and struct_ops callbacks. It modifies the BPF verifier to recognize `__arena` and `__arena__nullable` annotations, and updates the x86 BPF JIT and trampoline generation to properly rebase these pointers to the kernel's arena base address. This introduces new code paths in the verifier, BTF parsing, and JIT compilation, which are reachable from user space via BPF program loading and attachment, making it highly relevant for fuzzing.",
  "WorthFuzzing": true
}

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