AI Trajectory


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

Results:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "aarch64_insn_gen_add_sub_extended_reg",
    "bpf_jit_supports_arena_args",
    "arch_prepare_bpf_trampoline"
  ],
  "KMSANReasoning": "The patch modifies the ARM64 BPF JIT compiler to support arena arguments. It adds new instruction generation functions and updates the trampoline generation logic to emit instructions that rebase arena pointers. The changes are confined to generating machine code and do not introduce new kernel structures copied to user space, nor do they add complex allocations or logic that could expose uninitialized memory in the kernel's C code. Any bugs in the JIT compiler would likely result in invalid memory accesses (e.g., out-of-bounds or NULL pointer dereferences) or arbitrary code execution, which are effectively caught by standard KASAN and other bug detectors. There is no specific risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch adds support for BPF arena arguments in the ARM64 JIT compiler. It modifies the BPF JIT compiler and trampoline generation to handle arena arguments, which are reachable when loading and executing BPF programs that use arena arguments. This is a significant functional change in the core kernel/BPF subsystem.",
  "WorthFuzzing": true
}

1/1 2026/08/10 22:45 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 03c08c9d505790f3a5a72b6a35a089721c14fde1\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Aug 10 22:45:01 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst\nindex 1004eb0bec617..d9cc2ab1cf018 100644\n--- a/Documentation/bpf/kfuncs.rst\n+++ b/Documentation/bpf/kfuncs.rst\n@@ -301,9 +301,9 @@ An example is given below::\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+arena argument support (currently x86-64 and arm64); verification fails\n+otherwise. The program can pass any value without compromising the kernel. A\n+value that does 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\ndiff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h\nindex cc0702fa64a79..4548e8015808d 100644\n--- a/arch/arm64/include/asm/insn.h\n+++ b/arch/arm64/include/asm/insn.h\n@@ -205,6 +205,18 @@ enum aarch64_insn_adsb_type {\n \tAARCH64_INSN_ADSB_SUB_SETFLAGS\n };\n \n+/* option field of add/sub (extended register) */\n+enum aarch64_insn_extend_type {\n+\tAARCH64_INSN_EXTEND_UXTB,\n+\tAARCH64_INSN_EXTEND_UXTH,\n+\tAARCH64_INSN_EXTEND_UXTW,\n+\tAARCH64_INSN_EXTEND_UXTX,\n+\tAARCH64_INSN_EXTEND_SXTB,\n+\tAARCH64_INSN_EXTEND_SXTH,\n+\tAARCH64_INSN_EXTEND_SXTW,\n+\tAARCH64_INSN_EXTEND_SXTX,\n+};\n+\n enum aarch64_insn_movewide_type {\n \tAARCH64_INSN_MOVEWIDE_ZERO,\n \tAARCH64_INSN_MOVEWIDE_KEEP,\n@@ -378,6 +390,10 @@ __AARCH64_INSN_FUNCS(add,\t0x7F200000, 0x0B000000)\n __AARCH64_INSN_FUNCS(adds,\t0x7F200000, 0x2B000000)\n __AARCH64_INSN_FUNCS(sub,\t0x7F200000, 0x4B000000)\n __AARCH64_INSN_FUNCS(subs,\t0x7F200000, 0x6B000000)\n+__AARCH64_INSN_FUNCS(add_ext,\t0x7F200000, 0x0B200000)\n+__AARCH64_INSN_FUNCS(adds_ext,\t0x7F200000, 0x2B200000)\n+__AARCH64_INSN_FUNCS(sub_ext,\t0x7F200000, 0x4B200000)\n+__AARCH64_INSN_FUNCS(subs_ext,\t0x7F200000, 0x6B200000)\n __AARCH64_INSN_FUNCS(madd,\t0x7FE08000, 0x1B000000)\n __AARCH64_INSN_FUNCS(msub,\t0x7FE08000, 0x1B008000)\n __AARCH64_INSN_FUNCS(udiv,\t0x7FE0FC00, 0x1AC00800)\n@@ -637,6 +653,13 @@ u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,\n \t\t\t\t\t int shift,\n \t\t\t\t\t enum aarch64_insn_variant variant,\n \t\t\t\t\t enum aarch64_insn_adsb_type type);\n+u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,\n+\t\t\t\t\t  enum aarch64_insn_register src,\n+\t\t\t\t\t  enum aarch64_insn_register reg,\n+\t\t\t\t\t  enum aarch64_insn_extend_type extend,\n+\t\t\t\t\t  int shift,\n+\t\t\t\t\t  enum aarch64_insn_variant variant,\n+\t\t\t\t\t  enum aarch64_insn_adsb_type type);\n u32 aarch64_insn_gen_data1(enum aarch64_insn_register dst,\n \t\t\t   enum aarch64_insn_register src,\n \t\t\t   enum aarch64_insn_variant variant,\ndiff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c\nindex 37ce75f7f1f08..e70ac02385153 100644\n--- a/arch/arm64/lib/insn.c\n+++ b/arch/arm64/lib/insn.c\n@@ -986,6 +986,66 @@ u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,\n \treturn aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn, shift);\n }\n \n+/*\n+ * Unlike the shifted-register form, register 31 is not XZR everywhere here:\n+ * it encodes SP for @src, and for @dst too unless @type sets the flags. Only\n+ * @reg keeps the XZR meaning.\n+ */\n+u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,\n+\t\t\t\t\t  enum aarch64_insn_register src,\n+\t\t\t\t\t  enum aarch64_insn_register reg,\n+\t\t\t\t\t  enum aarch64_insn_extend_type extend,\n+\t\t\t\t\t  int shift,\n+\t\t\t\t\t  enum aarch64_insn_variant variant,\n+\t\t\t\t\t  enum aarch64_insn_adsb_type type)\n+{\n+\tu32 insn;\n+\n+\tswitch (type) {\n+\tcase AARCH64_INSN_ADSB_ADD:\n+\t\tinsn = aarch64_insn_get_add_ext_value();\n+\t\tbreak;\n+\tcase AARCH64_INSN_ADSB_SUB:\n+\t\tinsn = aarch64_insn_get_sub_ext_value();\n+\t\tbreak;\n+\tcase AARCH64_INSN_ADSB_ADD_SETFLAGS:\n+\t\tinsn = aarch64_insn_get_adds_ext_value();\n+\t\tbreak;\n+\tcase AARCH64_INSN_ADSB_SUB_SETFLAGS:\n+\t\tinsn = aarch64_insn_get_subs_ext_value();\n+\t\tbreak;\n+\tdefault:\n+\t\tpr_err(\"%s: unknown add/sub encoding %d\\n\", __func__, type);\n+\t\treturn AARCH64_BREAK_FAULT;\n+\t}\n+\n+\tswitch (variant) {\n+\tcase AARCH64_INSN_VARIANT_32BIT:\n+\t\tbreak;\n+\tcase AARCH64_INSN_VARIANT_64BIT:\n+\t\tinsn |= AARCH64_INSN_SF_BIT;\n+\t\tbreak;\n+\tdefault:\n+\t\tpr_err(\"%s: unknown variant encoding %d\\n\", __func__, variant);\n+\t\treturn AARCH64_BREAK_FAULT;\n+\t}\n+\n+\tif (shift \u003c 0 || shift \u003e 4) {\n+\t\tpr_err(\"%s: invalid shift encoding %d\\n\", __func__, shift);\n+\t\treturn AARCH64_BREAK_FAULT;\n+\t}\n+\n+\tinsn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RD, insn, dst);\n+\n+\tinsn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RN, insn, src);\n+\n+\tinsn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RM, insn, reg);\n+\n+\t/* option in bits [15:13] and imm3 in [12:10] together fill IMM_6 */\n+\treturn aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn,\n+\t\t\t\t\t     (extend \u003c\u003c 3) | shift);\n+}\n+\n u32 aarch64_insn_gen_data1(enum aarch64_insn_register dst,\n \t\t\t   enum aarch64_insn_register src,\n \t\t\t   enum aarch64_insn_variant variant,\ndiff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h\nindex d13de4222cfb7..b2fe6e6dcf447 100644\n--- a/arch/arm64/net/bpf_jit.h\n+++ b/arch/arm64/net/bpf_jit.h\n@@ -243,6 +243,17 @@\n /* Rn - Rm; set condition flags */\n #define A64_CMP(sf, Rn, Rm) A64_SUBS(sf, A64_ZR, Rn, Rm)\n \n+/* Add/subtract (extended register) */\n+#define A64_ADDSUB_EREG(sf, Rd, Rn, Rm, ext, shift, type) \\\n+\taarch64_insn_gen_add_sub_extended_reg(Rd, Rn, Rm, \\\n+\t\tAARCH64_INSN_EXTEND_##ext, shift, A64_VARIANT(sf), \\\n+\t\tAARCH64_INSN_ADSB_##type)\n+/* Rd = Rn + (EXT(Rm) \u003c\u003c shift) */\n+#define A64_ADD_EXT(sf, Rd, Rn, Rm, ext, shift) \\\n+\tA64_ADDSUB_EREG(sf, Rd, Rn, Rm, ext, shift, ADD)\n+/* Rd = Rn + (u32)Rm */\n+#define A64_ADD_UXTW(Rd, Rn, Rm) A64_ADD_EXT(1, Rd, Rn, Rm, UXTW, 0)\n+\n /* Data-processing (1 source) */\n #define A64_DATA1(sf, Rd, Rn, type) aarch64_insn_gen_data1(Rd, Rn, \\\n \tA64_VARIANT(sf), AARCH64_INSN_DATA1_##type)\ndiff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c\nindex d14d297ebb967..1cd327d213e34 100644\n--- a/arch/arm64/net/bpf_jit_comp.c\n+++ b/arch/arm64/net/bpf_jit_comp.c\n@@ -1249,6 +1249,43 @@ static void emit_stack_arg_store_imm(s32 imm, s16 bpf_off, const u8 tmp, struct\n \t}\n }\n \n+/*\n+ * Rebase the __arena args of a kfunc call to arena kernel addresses,\n+ * xN = kern_vm_start + (u32)xN, with the arena base register holding\n+ * kern_vm_start. A nullable arg preserves NULL by skipping the add, tested\n+ * on the truncated value as arena NULL is offset 0.\n+ */\n+static int emit_kfunc_arena_args(struct jit_ctx *ctx, const struct bpf_insn *insn)\n+{\n+\tconst u8 arena_vm_base = bpf2a64[ARENA_VM_START];\n+\tconst struct btf_func_model *fm;\n+\tint i;\n+\n+\tfm = bpf_jit_find_kfunc_model(ctx-\u003eprog, 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\tconst u8 reg = bpf2a64[BPF_REG_1 + i];\n+\t\tu8 flags = fm-\u003earg_flags[i];\n+\n+\t\tif (!(flags \u0026 BTF_FMODEL_ARENA_ARG))\n+\t\t\tcontinue;\n+\t\tif (WARN_ON_ONCE(!ctx-\u003earena_vm_start))\n+\t\t\treturn -EINVAL;\n+\n+\t\tif (flags \u0026 BTF_FMODEL_NULLABLE_ARG) {\n+\t\t\t/* 32-bit mov clears the upper 32 bits */\n+\t\t\temit(A64_MOV(0, reg, reg), ctx);\n+\t\t\t/* skip the add so that NULL stays NULL */\n+\t\t\temit(A64_CBZ(0, reg, 2), ctx);\n+\t\t}\n+\t\temit(A64_ADD_UXTW(reg, arena_vm_base, reg), ctx);\n+\t}\n+\n+\treturn 0;\n+}\n+\n /* JITs an eBPF instruction.\n  * Returns:\n  * 0  - successfully JITed an 8-byte eBPF instruction.\n@@ -1671,6 +1708,11 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn\n \t\t\t\t\t    \u0026func_addr, \u0026func_addr_fixed);\n \t\tif (ret \u003c 0)\n \t\t\treturn ret;\n+\t\tif (insn-\u003esrc_reg == BPF_PSEUDO_KFUNC_CALL) {\n+\t\t\tret = emit_kfunc_arena_args(ctx, insn);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\treturn ret;\n+\t\t}\n \t\temit_call(func_addr, ctx);\n \t\t/*\n \t\t * Call to arch_bpf_timed_may_goto() is emitted by the\n@@ -2335,6 +2377,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 (!aarch64_insn_copy(dst, src, len))\n@@ -2508,34 +2555,76 @@ static void clear_garbage(struct jit_ctx *ctx, int reg, int effective_bytes)\n \t}\n }\n \n+/*\n+ * Convert an arena kernel address into the arena pointer form on its way into\n+ * the BPF ctx, dst = (u32)(src - kern_vm_start), with @base_lo holding the low\n+ * 32 bits of kern_vm_start. A nullable arg preserves NULL, tested on the full\n+ * 64-bit kernel pointer. The 32-bit subtraction both truncates and clears the\n+ * upper half, so the stored value satisfies the JIT invariant for arena\n+ * pointer registers.\n+ */\n+static void emit_arena_arg_conv(struct jit_ctx *ctx, u8 dst, u8 src, bool nullable, u8 base_lo)\n+{\n+\tif (nullable) {\n+\t\tif (dst != src)\n+\t\t\temit(A64_MOV(1, dst, src), ctx);\n+\t\t/* skip the subtraction so that NULL stays NULL */\n+\t\temit(A64_CBZ(1, dst, 2), ctx);\n+\t\tsrc = dst;\n+\t}\n+\temit(A64_SUB(0, dst, src, base_lo), ctx);\n+}\n+\n static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,\n-\t\t      const struct btf_func_model *m,\n-\t\t      const struct arg_aux *a,\n-\t\t      bool for_call_origin)\n+\t\t      const struct btf_func_model *m, const struct arg_aux *a,\n+\t\t      bool for_call_origin, bool is_struct_ops, u64 arena_base)\n {\n-\tint i;\n-\tint reg;\n-\tint doff;\n-\tint soff;\n-\tint slots;\n \tu8 tmp = bpf2a64[TMP_REG_1];\n+\tu8 base_lo = bpf2a64[TMP_REG_2];\n+\tint i, reg, doff, soff, slots;\n+\n+\t/* only the low 32 bits of the base take part in the subtraction */\n+\tif (arena_base)\n+\t\temit_a64_mov_i(0, base_lo, (s32)(u32)arena_base, ctx);\n \n \t/* store arguments to the stack for the bpf program, or restore\n \t * arguments from stack for the original function\n \t */\n-\tfor (reg = 0; reg \u003c a-\u003eregs_for_args; reg++) {\n-\t\temit(for_call_origin ?\n-\t\t     A64_LDR64I(reg, A64_SP, bargs_off) :\n-\t\t     A64_STR64I(reg, A64_SP, bargs_off),\n-\t\t     ctx);\n-\t\tbargs_off += 8;\n+\tfor (i = 0, reg = 0; i \u003c a-\u003eargs_in_regs; 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\tslots = (m-\u003earg_size[i] + 7) / 8;\n+\t\twhile (slots-- \u003e 0) {\n+\t\t\tif (for_call_origin) {\n+\t\t\t\temit(A64_LDR64I(reg, A64_SP, bargs_off), ctx);\n+\t\t\t} else if (arena_arg) {\n+\t\t\t\temit_arena_arg_conv(ctx, tmp, reg, nullable, base_lo);\n+\t\t\t\temit(A64_STR64I(tmp, A64_SP, bargs_off), ctx);\n+\t\t\t} else {\n+\t\t\t\temit(A64_STR64I(reg, A64_SP, bargs_off), ctx);\n+\t\t\t}\n+\t\t\treg++;\n+\t\t\tbargs_off += 8;\n+\t\t}\n \t}\n \n-\tsoff = 32; /* on stack arguments start from FP + 32 */\n+\t/*\n+\t * On-stack arguments start above the frame(s) pushed by the trampoline\n+\t * prologue. Entered through the fentry call from a traced function, the\n+\t * prologue saves both the parent (FP/x9) and the traced function\n+\t * (FP/LR) frames, so the arguments start at FP + 32. A struct_ops\n+\t * callback is called indirectly and only the FP/LR frame is saved, so\n+\t * they start at FP + 16.\n+\t */\n+\tsoff = is_struct_ops ? 16 : 32;\n \tdoff = (for_call_origin ? oargs_off : bargs_off);\n \n \t/* save on stack arguments */\n \tfor (i = a-\u003eargs_in_regs; i \u003c m-\u003enr_args; i++) {\n+\t\tbool arena_arg = arena_base \u0026\u0026 (m-\u003earg_flags[i] \u0026 BTF_FMODEL_ARENA_ARG);\n+\t\tbool nullable = m-\u003earg_flags[i] \u0026 BTF_FMODEL_NULLABLE_ARG;\n+\n \t\tslots = (m-\u003earg_size[i] + 7) / 8;\n \t\t/* verifier ensures arg_size \u003c= 16, so slots equals 1 or 2 */\n \t\twhile (slots-- \u003e 0) {\n@@ -2545,6 +2634,8 @@ static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,\n \t\t\t */\n \t\t\tif (slots == 0 \u0026\u0026 !for_call_origin)\n \t\t\t\tclear_garbage(ctx, tmp, m-\u003earg_size[i] % 8);\n+\t\t\tif (arena_arg)\n+\t\t\t\temit_arena_arg_conv(ctx, tmp, tmp, nullable, base_lo);\n \t\t\temit(A64_STR64I(tmp, A64_SP, doff), ctx);\n \t\t\tsoff += 8;\n \t\t\tdoff += 8;\n@@ -2604,8 +2695,21 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,\n \tbool is_struct_ops = is_struct_ops_tramp(fentry);\n \tint cookie_off, cookie_cnt, cookie_bargs_off;\n \tint fsession_cnt = bpf_fsession_cnt(tnodes);\n+\tu64 arena_base;\n \tu64 func_meta;\n \n+\t/*\n+\t * F_INDIRECT is only compatible with F_RET_FENTRY_RET, it is explicitly\n+\t * incompatible with F_CALL_ORIG | F_SKIP_FRAME | F_IP_ARG because\n+\t * @func_addr. Arena conversion relies on this: bpf_tramp_arena_base()\n+\t * only returns a base for the indirect trampoline, which therefore\n+\t * never calls the original function with converted arguments.\n+\t */\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 \t/* trampoline stack layout:\n \t *                    [ parent ip         ]\n \t *                    [ FP                ]\n@@ -2721,7 +2825,7 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,\n \tstore_func_meta(ctx, func_meta, func_meta_off);\n \n \t/* save args for bpf */\n-\tsave_args(ctx, bargs_off, oargs_off, m, a, false);\n+\tsave_args(ctx, bargs_off, oargs_off, m, a, false, is_struct_ops, arena_base);\n \n \t/* save callee saved registers */\n \temit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);\n@@ -2769,8 +2873,8 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,\n \t}\n \n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n-\t\t/* save args for original func */\n-\t\tsave_args(ctx, bargs_off, oargs_off, m, a, true);\n+\t\t/* the original func takes kernel addresses, never converted ones */\n+\t\tsave_args(ctx, bargs_off, oargs_off, m, a, true, is_struct_ops, 0);\n \t\t/* call original func */\n \t\temit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);\n \t\temit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx);\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\nindex 940ec2cda0d52..7f9f54ba3fbe5 100644\n--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c\n+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c\n@@ -6,7 +6,7 @@\n #include \"struct_ops_arena_attach.skel.h\"\n #include \"struct_ops_arena_fail.skel.h\"\n \n-#if defined(__x86_64__)\n+#if defined(__x86_64__) || defined(__aarch64__)\n /*\n  * Attach callbacks with __arena and __arena__nullable arguments and drive\n  * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.\n@@ -111,11 +111,11 @@ static void arena_arg_attach(void)\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 * Arena struct_ops arguments need JIT support, currently x86-64 and\n+\t * arm64 only. Elsewhere verification fails with \"JIT does not support\n+\t * arena arguments\", so the programs cannot even load.\n \t */\n-#if defined(__x86_64__)\n+#if defined(__x86_64__) || defined(__aarch64__)\n \tif (test__start_subtest(\"arena_arg\"))\n \t\tarena_arg();\n \tif (test__start_subtest(\"arena_arg_fail\"))\ndiff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c\nindex cdcea889da586..bf0d304e0e59c 100644\n--- a/tools/testing/selftests/bpf/progs/arena_kfunc.c\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c\n@@ -27,6 +27,7 @@ volatile u64 stash;\n \n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __success __retval(0)\n int arena_arg_forms(void *ctx)\n {\n@@ -70,6 +71,7 @@ int arena_arg_forms(void *ctx)\n  */\n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __success __retval(0)\n int arena_arg_rebase(void *ctx)\n {\n@@ -111,6 +113,7 @@ int arena_arg_rebase(void *ctx)\n \n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __success __retval(0)\n int arena_args5(void *ctx)\n {\n@@ -142,6 +145,7 @@ int arena_args5(void *ctx)\n \n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __success __retval(0)\n int arena_arg_mixed(void *ctx)\n {\n@@ -169,6 +173,7 @@ int arena_arg_mixed(void *ctx)\n /* kernel-side faults on unpopulated pages recover via the scratch page */\n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __success __retval(0)\n int arena_arg_unpopulated(void *ctx)\n {\n@@ -189,6 +194,7 @@ int arena_arg_unpopulated(void *ctx)\n \n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __failure __msg(\"arena pointer requires a program with an associated arena\")\n int arena_arg_no_arena(void *ctx)\n {\n@@ -198,6 +204,7 @@ int arena_arg_no_arena(void *ctx)\n \n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __failure __msg(\"is not a pointer to arena or scalar\")\n int arena_arg_bad_reg(void *ctx)\n {\n@@ -213,6 +220,7 @@ int arena_arg_bad_reg(void *ctx)\n \tdefined(__BPF_FEATURE_STACK_ARGUMENT)\n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __failure __msg(\"arena pointer cannot be a stack argument\")\n int arena_arg_stack(void *ctx)\n {\n@@ -223,6 +231,7 @@ int arena_arg_stack(void *ctx)\n #else\n SEC(\"syscall\")\n __arch_x86_64\n+__arch_arm64\n __description(\"arena_arg_stack: not supported, dummy test\")\n __success\n int arena_arg_stack(void *ctx)\ndiff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c\nindex c9b9186626162..b5a01cbc33a73 100644\n--- a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c\n@@ -32,6 +32,10 @@ __jited(\"\tmovl\t%edi, %edi\")\n __jited(\"\taddq\t%r12, %rdi\")\n __jited(\"...\")\n __jited(\"\tcallq\t{{.*}}\")\n+__arch_arm64\n+__jited(\"...\")\n+__jited(\"\tadd\tx0, x28, w0, uxtw\")\n+__jited(\"\t{{(bl|mov)\t.*}}\")\n __success\n int arena_arg_jit_rebase(void *ctx)\n {\n@@ -48,6 +52,12 @@ __jited(\"\ttestl\t%edi, %edi\")\n __jited(\"\tje\tL0\")\n __jited(\"\taddq\t%r12, %rdi\")\n __jited(\"L0:\tcallq\t{{.*}}\")\n+__arch_arm64\n+__jited(\"...\")\n+__jited(\"\tmov\tw0, w0\")\n+__jited(\"\tcbz\tw0, L0\")\n+__jited(\"\tadd\tx0, x28, w0, uxtw\")\n+__jited(\"L0:\t{{.*}}\")\n __success\n int arena_arg_jit_nullable(void *ctx)\n {\n@@ -72,6 +82,16 @@ __jited(\"\ttestl\t%r8d, %r8d\")\n __jited(\"\tje\tL0\")\n __jited(\"\taddq\t%r12, %r8\")\n __jited(\"L0:\tcallq\t{{.*}}\")\n+__arch_arm64\n+__jited(\"...\")\n+__jited(\"\tadd\tx0, x28, w0, uxtw\")\n+__jited(\"\tadd\tx1, x28, w1, uxtw\")\n+__jited(\"\tadd\tx2, x28, w2, uxtw\")\n+__jited(\"\tadd\tx3, x28, w3, uxtw\")\n+__jited(\"\tmov\tw4, w4\")\n+__jited(\"\tcbz\tw4, L0\")\n+__jited(\"\tadd\tx4, x28, w4, uxtw\")\n+__jited(\"L0:\t{{.*}}\")\n __success\n int arena_arg_jit_args5(void *ctx)\n {\ndiff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c\nindex ba04c73d8d967..8aa8639df91f0 100644\n--- a/tools/testing/selftests/bpf/progs/struct_ops_arena.c\n+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c\n@@ -59,11 +59,28 @@ int test_arena_stack_cb(unsigned long long *ctx)\n \treturn 0;\n }\n \n+SEC(\"struct_ops/test_arena_multislot\")\n+int test_arena_multislot_cb(unsigned long long *ctx)\n+{\n+\tu64 __arena *ptr = (u64 __arena *)ctx[2];\n+\n+\tarena_touch++;\n+\t/*\n+\t * The 16-byte struct occupies ctx[0] and ctx[1], so @ptr is argument\n+\t * one but slot two. Getting that wrong hands the callback a scalar.\n+\t */\n+\tif (ctx[0] != 11 || ctx[1] != 22)\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+\t.test_arena_multislot = (void *)test_arena_multislot_cb,\n };\n \n SEC(\"syscall\")\n@@ -109,6 +126,13 @@ int trigger(void *ctx)\n \tif (*val != 44)\n \t\treturn 9;\n \n+\t/* a multi-slot arg precedes the arena pointer here */\n+\tret = bpf_testmod_ops3_call_test_arena_multislot((u64 *)val);\n+\tif (ret)\n+\t\treturn 10;\n+\tif (*val != 45)\n+\t\treturn 11;\n+\n \tbpf_arena_free_pages(\u0026arena, (void __arena *)val, 1);\n #endif\n \treturn 0;\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\nindex a6133f7521f34..9366a3c578f13 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n@@ -402,12 +402,19 @@ static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,\n \treturn 0;\n }\n \n+static int bpf_testmod_ops3__test_arena_multislot(struct bpf_testmod_arena_pair p,\n+\t\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+\t.test_arena_multislot = bpf_testmod_ops3__test_arena_multislot,\n };\n \n static void bpf_testmod_test_struct_ops3(void)\n@@ -441,6 +448,13 @@ __bpf_kfunc int bpf_testmod_ops3_call_test_arena_stack(u64 *ptr__arena)\n \treturn st_ops3-\u003etest_arena_stack(1, 2, 3, 4, 5, 6, 7, 8, ptr__arena);\n }\n \n+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_multislot(u64 *ptr__arena)\n+{\n+\tstruct bpf_testmod_arena_pair p = { .a = 11, .b = 22 };\n+\n+\treturn st_ops3-\u003etest_arena_multislot(p, ptr__arena);\n+}\n+\n struct bpf_testmod_btf_type_tag_1 {\n \tint a;\n };\n@@ -852,6 +866,7 @@ 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_testmod_ops3_call_test_arena_multislot)\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 33f2af5b70857..210b919290cc2 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h\n@@ -103,6 +103,12 @@ struct bpf_testmod_ops2 {\n \tint (*test_1)(void);\n };\n \n+/* 16 bytes, so it takes two argument slots when passed by value */\n+struct bpf_testmod_arena_pair {\n+\tu64 a;\n+\tu64 b;\n+};\n+\n struct bpf_testmod_ops3 {\n \tint (*test_1)(void);\n \tint (*test_2)(void);\n@@ -112,6 +118,8 @@ struct bpf_testmod_ops3 {\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+\t/* a multi-slot leading arg, so @ptr is not at the slot its arg index suggests */\n+\tint (*test_arena_multislot)(struct bpf_testmod_arena_pair p, 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 c4383acb53c11..7d81070eefe7f 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@@ -123,6 +123,7 @@ 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+int bpf_testmod_ops3_call_test_arena_multislot(__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/10 22:45 agent patch-evaluator
1m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "aarch64_insn_gen_add_sub_extended_reg",
    "bpf_jit_supports_arena_args",
    "arch_prepare_bpf_trampoline"
  ],
  "Reasoning": "The patch adds support for BPF arena arguments in the ARM64 JIT compiler. It modifies the BPF JIT compiler and trampoline generation to handle arena arguments, which are reachable when loading and executing BPF programs that use arena arguments. This is a significant functional change in the core kernel/BPF subsystem.",
  "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 03c08c9d505790f3a5a72b6a35a089721c14fde1
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Aug 10 22:45:01 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 1004eb0bec617..d9cc2ab1cf018 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -301,9 +301,9 @@ An example is given below::
         }
 
 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.
+arena argument support (currently x86-64 and arm64); 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
diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
index cc0702fa64a79..4548e8015808d 100644
--- a/arch/arm64/include/asm/insn.h
+++ b/arch/arm64/include/asm/insn.h
@@ -205,6 +205,18 @@ enum aarch64_insn_adsb_type {
 	AARCH64_INSN_ADSB_SUB_SETFLAGS
 };
 
+/* option field of add/sub (extended register) */
+enum aarch64_insn_extend_type {
+	AARCH64_INSN_EXTEND_UXTB,
+	AARCH64_INSN_EXTEND_UXTH,
+	AARCH64_INSN_EXTEND_UXTW,
+	AARCH64_INSN_EXTEND_UXTX,
+	AARCH64_INSN_EXTEND_SXTB,
+	AARCH64_INSN_EXTEND_SXTH,
+	AARCH64_INSN_EXTEND_SXTW,
+	AARCH64_INSN_EXTEND_SXTX,
+};
+
 enum aarch64_insn_movewide_type {
 	AARCH64_INSN_MOVEWIDE_ZERO,
 	AARCH64_INSN_MOVEWIDE_KEEP,
@@ -378,6 +390,10 @@ __AARCH64_INSN_FUNCS(add,	0x7F200000, 0x0B000000)
 __AARCH64_INSN_FUNCS(adds,	0x7F200000, 0x2B000000)
 __AARCH64_INSN_FUNCS(sub,	0x7F200000, 0x4B000000)
 __AARCH64_INSN_FUNCS(subs,	0x7F200000, 0x6B000000)
+__AARCH64_INSN_FUNCS(add_ext,	0x7F200000, 0x0B200000)
+__AARCH64_INSN_FUNCS(adds_ext,	0x7F200000, 0x2B200000)
+__AARCH64_INSN_FUNCS(sub_ext,	0x7F200000, 0x4B200000)
+__AARCH64_INSN_FUNCS(subs_ext,	0x7F200000, 0x6B200000)
 __AARCH64_INSN_FUNCS(madd,	0x7FE08000, 0x1B000000)
 __AARCH64_INSN_FUNCS(msub,	0x7FE08000, 0x1B008000)
 __AARCH64_INSN_FUNCS(udiv,	0x7FE0FC00, 0x1AC00800)
@@ -637,6 +653,13 @@ u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,
 					 int shift,
 					 enum aarch64_insn_variant variant,
 					 enum aarch64_insn_adsb_type type);
+u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,
+					  enum aarch64_insn_register src,
+					  enum aarch64_insn_register reg,
+					  enum aarch64_insn_extend_type extend,
+					  int shift,
+					  enum aarch64_insn_variant variant,
+					  enum aarch64_insn_adsb_type type);
 u32 aarch64_insn_gen_data1(enum aarch64_insn_register dst,
 			   enum aarch64_insn_register src,
 			   enum aarch64_insn_variant variant,
diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
index 37ce75f7f1f08..e70ac02385153 100644
--- a/arch/arm64/lib/insn.c
+++ b/arch/arm64/lib/insn.c
@@ -986,6 +986,66 @@ u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,
 	return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn, shift);
 }
 
+/*
+ * Unlike the shifted-register form, register 31 is not XZR everywhere here:
+ * it encodes SP for @src, and for @dst too unless @type sets the flags. Only
+ * @reg keeps the XZR meaning.
+ */
+u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,
+					  enum aarch64_insn_register src,
+					  enum aarch64_insn_register reg,
+					  enum aarch64_insn_extend_type extend,
+					  int shift,
+					  enum aarch64_insn_variant variant,
+					  enum aarch64_insn_adsb_type type)
+{
+	u32 insn;
+
+	switch (type) {
+	case AARCH64_INSN_ADSB_ADD:
+		insn = aarch64_insn_get_add_ext_value();
+		break;
+	case AARCH64_INSN_ADSB_SUB:
+		insn = aarch64_insn_get_sub_ext_value();
+		break;
+	case AARCH64_INSN_ADSB_ADD_SETFLAGS:
+		insn = aarch64_insn_get_adds_ext_value();
+		break;
+	case AARCH64_INSN_ADSB_SUB_SETFLAGS:
+		insn = aarch64_insn_get_subs_ext_value();
+		break;
+	default:
+		pr_err("%s: unknown add/sub encoding %d\n", __func__, type);
+		return AARCH64_BREAK_FAULT;
+	}
+
+	switch (variant) {
+	case AARCH64_INSN_VARIANT_32BIT:
+		break;
+	case AARCH64_INSN_VARIANT_64BIT:
+		insn |= AARCH64_INSN_SF_BIT;
+		break;
+	default:
+		pr_err("%s: unknown variant encoding %d\n", __func__, variant);
+		return AARCH64_BREAK_FAULT;
+	}
+
+	if (shift < 0 || shift > 4) {
+		pr_err("%s: invalid shift encoding %d\n", __func__, shift);
+		return AARCH64_BREAK_FAULT;
+	}
+
+	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RD, insn, dst);
+
+	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RN, insn, src);
+
+	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RM, insn, reg);
+
+	/* option in bits [15:13] and imm3 in [12:10] together fill IMM_6 */
+	return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn,
+					     (extend << 3) | shift);
+}
+
 u32 aarch64_insn_gen_data1(enum aarch64_insn_register dst,
 			   enum aarch64_insn_register src,
 			   enum aarch64_insn_variant variant,
diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h
index d13de4222cfb7..b2fe6e6dcf447 100644
--- a/arch/arm64/net/bpf_jit.h
+++ b/arch/arm64/net/bpf_jit.h
@@ -243,6 +243,17 @@
 /* Rn - Rm; set condition flags */
 #define A64_CMP(sf, Rn, Rm) A64_SUBS(sf, A64_ZR, Rn, Rm)
 
+/* Add/subtract (extended register) */
+#define A64_ADDSUB_EREG(sf, Rd, Rn, Rm, ext, shift, type) \
+	aarch64_insn_gen_add_sub_extended_reg(Rd, Rn, Rm, \
+		AARCH64_INSN_EXTEND_##ext, shift, A64_VARIANT(sf), \
+		AARCH64_INSN_ADSB_##type)
+/* Rd = Rn + (EXT(Rm) << shift) */
+#define A64_ADD_EXT(sf, Rd, Rn, Rm, ext, shift) \
+	A64_ADDSUB_EREG(sf, Rd, Rn, Rm, ext, shift, ADD)
+/* Rd = Rn + (u32)Rm */
+#define A64_ADD_UXTW(Rd, Rn, Rm) A64_ADD_EXT(1, Rd, Rn, Rm, UXTW, 0)
+
 /* Data-processing (1 source) */
 #define A64_DATA1(sf, Rd, Rn, type) aarch64_insn_gen_data1(Rd, Rn, \
 	A64_VARIANT(sf), AARCH64_INSN_DATA1_##type)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index d14d297ebb967..1cd327d213e34 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -1249,6 +1249,43 @@ static void emit_stack_arg_store_imm(s32 imm, s16 bpf_off, const u8 tmp, struct
 	}
 }
 
+/*
+ * Rebase the __arena args of a kfunc call to arena kernel addresses,
+ * xN = kern_vm_start + (u32)xN, with the arena base register holding
+ * kern_vm_start. A nullable arg preserves NULL by skipping the add, tested
+ * on the truncated value as arena NULL is offset 0.
+ */
+static int emit_kfunc_arena_args(struct jit_ctx *ctx, const struct bpf_insn *insn)
+{
+	const u8 arena_vm_base = bpf2a64[ARENA_VM_START];
+	const struct btf_func_model *fm;
+	int i;
+
+	fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
+	if (!fm)
+		return -EINVAL;
+
+	for (i = 0; i < min_t(int, fm->nr_args, MAX_BPF_FUNC_REG_ARGS); i++) {
+		const u8 reg = bpf2a64[BPF_REG_1 + i];
+		u8 flags = fm->arg_flags[i];
+
+		if (!(flags & BTF_FMODEL_ARENA_ARG))
+			continue;
+		if (WARN_ON_ONCE(!ctx->arena_vm_start))
+			return -EINVAL;
+
+		if (flags & BTF_FMODEL_NULLABLE_ARG) {
+			/* 32-bit mov clears the upper 32 bits */
+			emit(A64_MOV(0, reg, reg), ctx);
+			/* skip the add so that NULL stays NULL */
+			emit(A64_CBZ(0, reg, 2), ctx);
+		}
+		emit(A64_ADD_UXTW(reg, arena_vm_base, reg), ctx);
+	}
+
+	return 0;
+}
+
 /* JITs an eBPF instruction.
  * Returns:
  * 0  - successfully JITed an 8-byte eBPF instruction.
@@ -1671,6 +1708,11 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 					    &func_addr, &func_addr_fixed);
 		if (ret < 0)
 			return ret;
+		if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
+			ret = emit_kfunc_arena_args(ctx, insn);
+			if (ret < 0)
+				return ret;
+		}
 		emit_call(func_addr, ctx);
 		/*
 		 * Call to arch_bpf_timed_may_goto() is emitted by the
@@ -2335,6 +2377,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 (!aarch64_insn_copy(dst, src, len))
@@ -2508,34 +2555,76 @@ static void clear_garbage(struct jit_ctx *ctx, int reg, int effective_bytes)
 	}
 }
 
+/*
+ * Convert an arena kernel address into the arena pointer form on its way into
+ * the BPF ctx, dst = (u32)(src - kern_vm_start), with @base_lo holding the low
+ * 32 bits of 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(struct jit_ctx *ctx, u8 dst, u8 src, bool nullable, u8 base_lo)
+{
+	if (nullable) {
+		if (dst != src)
+			emit(A64_MOV(1, dst, src), ctx);
+		/* skip the subtraction so that NULL stays NULL */
+		emit(A64_CBZ(1, dst, 2), ctx);
+		src = dst;
+	}
+	emit(A64_SUB(0, dst, src, base_lo), ctx);
+}
+
 static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,
-		      const struct btf_func_model *m,
-		      const struct arg_aux *a,
-		      bool for_call_origin)
+		      const struct btf_func_model *m, const struct arg_aux *a,
+		      bool for_call_origin, bool is_struct_ops, u64 arena_base)
 {
-	int i;
-	int reg;
-	int doff;
-	int soff;
-	int slots;
 	u8 tmp = bpf2a64[TMP_REG_1];
+	u8 base_lo = bpf2a64[TMP_REG_2];
+	int i, reg, doff, soff, slots;
+
+	/* only the low 32 bits of the base take part in the subtraction */
+	if (arena_base)
+		emit_a64_mov_i(0, base_lo, (s32)(u32)arena_base, ctx);
 
 	/* store arguments to the stack for the bpf program, or restore
 	 * arguments from stack for the original function
 	 */
-	for (reg = 0; reg < a->regs_for_args; reg++) {
-		emit(for_call_origin ?
-		     A64_LDR64I(reg, A64_SP, bargs_off) :
-		     A64_STR64I(reg, A64_SP, bargs_off),
-		     ctx);
-		bargs_off += 8;
+	for (i = 0, reg = 0; i < a->args_in_regs; i++) {
+		bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+		bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
+		slots = (m->arg_size[i] + 7) / 8;
+		while (slots-- > 0) {
+			if (for_call_origin) {
+				emit(A64_LDR64I(reg, A64_SP, bargs_off), ctx);
+			} else if (arena_arg) {
+				emit_arena_arg_conv(ctx, tmp, reg, nullable, base_lo);
+				emit(A64_STR64I(tmp, A64_SP, bargs_off), ctx);
+			} else {
+				emit(A64_STR64I(reg, A64_SP, bargs_off), ctx);
+			}
+			reg++;
+			bargs_off += 8;
+		}
 	}
 
-	soff = 32; /* on stack arguments start from FP + 32 */
+	/*
+	 * On-stack arguments start above the frame(s) pushed by the trampoline
+	 * prologue. Entered through the fentry call from a traced function, the
+	 * prologue saves both the parent (FP/x9) and the traced function
+	 * (FP/LR) frames, so the arguments start at FP + 32. A struct_ops
+	 * callback is called indirectly and only the FP/LR frame is saved, so
+	 * they start at FP + 16.
+	 */
+	soff = is_struct_ops ? 16 : 32;
 	doff = (for_call_origin ? oargs_off : bargs_off);
 
 	/* save on stack arguments */
 	for (i = a->args_in_regs; i < m->nr_args; i++) {
+		bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+		bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
 		slots = (m->arg_size[i] + 7) / 8;
 		/* verifier ensures arg_size <= 16, so slots equals 1 or 2 */
 		while (slots-- > 0) {
@@ -2545,6 +2634,8 @@ static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,
 			 */
 			if (slots == 0 && !for_call_origin)
 				clear_garbage(ctx, tmp, m->arg_size[i] % 8);
+			if (arena_arg)
+				emit_arena_arg_conv(ctx, tmp, tmp, nullable, base_lo);
 			emit(A64_STR64I(tmp, A64_SP, doff), ctx);
 			soff += 8;
 			doff += 8;
@@ -2604,8 +2695,21 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
 	bool is_struct_ops = is_struct_ops_tramp(fentry);
 	int cookie_off, cookie_cnt, cookie_bargs_off;
 	int fsession_cnt = bpf_fsession_cnt(tnodes);
+	u64 arena_base;
 	u64 func_meta;
 
+	/*
+	 * F_INDIRECT is only compatible with F_RET_FENTRY_RET, it is explicitly
+	 * incompatible with F_CALL_ORIG | F_SKIP_FRAME | F_IP_ARG because
+	 * @func_addr. Arena conversion relies on this: bpf_tramp_arena_base()
+	 * only returns a base for the indirect trampoline, which therefore
+	 * never calls the original function with converted arguments.
+	 */
+	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);
+
 	/* trampoline stack layout:
 	 *                    [ parent ip         ]
 	 *                    [ FP                ]
@@ -2721,7 +2825,7 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
 	store_func_meta(ctx, func_meta, func_meta_off);
 
 	/* save args for bpf */
-	save_args(ctx, bargs_off, oargs_off, m, a, false);
+	save_args(ctx, bargs_off, oargs_off, m, a, false, is_struct_ops, arena_base);
 
 	/* save callee saved registers */
 	emit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);
@@ -2769,8 +2873,8 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
 	}
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
-		/* save args for original func */
-		save_args(ctx, bargs_off, oargs_off, m, a, true);
+		/* the original func takes kernel addresses, never converted ones */
+		save_args(ctx, bargs_off, oargs_off, m, a, true, is_struct_ops, 0);
 		/* call original func */
 		emit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);
 		emit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx);
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
index 940ec2cda0d52..7f9f54ba3fbe5 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
@@ -6,7 +6,7 @@
 #include "struct_ops_arena_attach.skel.h"
 #include "struct_ops_arena_fail.skel.h"
 
-#if defined(__x86_64__)
+#if defined(__x86_64__) || defined(__aarch64__)
 /*
  * Attach callbacks with __arena and __arena__nullable arguments and drive
  * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.
@@ -111,11 +111,11 @@ static void arena_arg_attach(void)
 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.
+	 * Arena struct_ops arguments need JIT support, currently x86-64 and
+	 * arm64 only. Elsewhere verification fails with "JIT does not support
+	 * arena arguments", so the programs cannot even load.
 	 */
-#if defined(__x86_64__)
+#if defined(__x86_64__) || defined(__aarch64__)
 	if (test__start_subtest("arena_arg"))
 		arena_arg();
 	if (test__start_subtest("arena_arg_fail"))
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c
index cdcea889da586..bf0d304e0e59c 100644
--- a/tools/testing/selftests/bpf/progs/arena_kfunc.c
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -27,6 +27,7 @@ volatile u64 stash;
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_forms(void *ctx)
 {
@@ -70,6 +71,7 @@ int arena_arg_forms(void *ctx)
  */
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_rebase(void *ctx)
 {
@@ -111,6 +113,7 @@ int arena_arg_rebase(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_args5(void *ctx)
 {
@@ -142,6 +145,7 @@ int arena_args5(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_mixed(void *ctx)
 {
@@ -169,6 +173,7 @@ int arena_arg_mixed(void *ctx)
 /* kernel-side faults on unpopulated pages recover via the scratch page */
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_unpopulated(void *ctx)
 {
@@ -189,6 +194,7 @@ int arena_arg_unpopulated(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __failure __msg("arena pointer requires a program with an associated arena")
 int arena_arg_no_arena(void *ctx)
 {
@@ -198,6 +204,7 @@ int arena_arg_no_arena(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __failure __msg("is not a pointer to arena or scalar")
 int arena_arg_bad_reg(void *ctx)
 {
@@ -213,6 +220,7 @@ int arena_arg_bad_reg(void *ctx)
 	defined(__BPF_FEATURE_STACK_ARGUMENT)
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __failure __msg("arena pointer cannot be a stack argument")
 int arena_arg_stack(void *ctx)
 {
@@ -223,6 +231,7 @@ int arena_arg_stack(void *ctx)
 #else
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __description("arena_arg_stack: not supported, dummy test")
 __success
 int arena_arg_stack(void *ctx)
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
index c9b9186626162..b5a01cbc33a73 100644
--- a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
@@ -32,6 +32,10 @@ __jited("	movl	%edi, %edi")
 __jited("	addq	%r12, %rdi")
 __jited("...")
 __jited("	callq	{{.*}}")
+__arch_arm64
+__jited("...")
+__jited("	add	x0, x28, w0, uxtw")
+__jited("	{{(bl|mov)	.*}}")
 __success
 int arena_arg_jit_rebase(void *ctx)
 {
@@ -48,6 +52,12 @@ __jited("	testl	%edi, %edi")
 __jited("	je	L0")
 __jited("	addq	%r12, %rdi")
 __jited("L0:	callq	{{.*}}")
+__arch_arm64
+__jited("...")
+__jited("	mov	w0, w0")
+__jited("	cbz	w0, L0")
+__jited("	add	x0, x28, w0, uxtw")
+__jited("L0:	{{.*}}")
 __success
 int arena_arg_jit_nullable(void *ctx)
 {
@@ -72,6 +82,16 @@ __jited("	testl	%r8d, %r8d")
 __jited("	je	L0")
 __jited("	addq	%r12, %r8")
 __jited("L0:	callq	{{.*}}")
+__arch_arm64
+__jited("...")
+__jited("	add	x0, x28, w0, uxtw")
+__jited("	add	x1, x28, w1, uxtw")
+__jited("	add	x2, x28, w2, uxtw")
+__jited("	add	x3, x28, w3, uxtw")
+__jited("	mov	w4, w4")
+__jited("	cbz	w4, L0")
+__jited("	add	x4, x28, w4, uxtw")
+__jited("L0:	{{.*}}")
 __success
 int arena_arg_jit_args5(void *ctx)
 {
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
index ba04c73d8d967..8aa8639df91f0 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_arena.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
@@ -59,11 +59,28 @@ int test_arena_stack_cb(unsigned long long *ctx)
 	return 0;
 }
 
+SEC("struct_ops/test_arena_multislot")
+int test_arena_multislot_cb(unsigned long long *ctx)
+{
+	u64 __arena *ptr = (u64 __arena *)ctx[2];
+
+	arena_touch++;
+	/*
+	 * The 16-byte struct occupies ctx[0] and ctx[1], so @ptr is argument
+	 * one but slot two. Getting that wrong hands the callback a scalar.
+	 */
+	if (ctx[0] != 11 || ctx[1] != 22)
+		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,
+	.test_arena_multislot = (void *)test_arena_multislot_cb,
 };
 
 SEC("syscall")
@@ -109,6 +126,13 @@ int trigger(void *ctx)
 	if (*val != 44)
 		return 9;
 
+	/* a multi-slot arg precedes the arena pointer here */
+	ret = bpf_testmod_ops3_call_test_arena_multislot((u64 *)val);
+	if (ret)
+		return 10;
+	if (*val != 45)
+		return 11;
+
 	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
 #endif
 	return 0;
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index a6133f7521f34..9366a3c578f13 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -402,12 +402,19 @@ static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,
 	return 0;
 }
 
+static int bpf_testmod_ops3__test_arena_multislot(struct bpf_testmod_arena_pair p,
+						  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,
+	.test_arena_multislot = bpf_testmod_ops3__test_arena_multislot,
 };
 
 static void bpf_testmod_test_struct_ops3(void)
@@ -441,6 +448,13 @@ __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);
 }
 
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_multislot(u64 *ptr__arena)
+{
+	struct bpf_testmod_arena_pair p = { .a = 11, .b = 22 };
+
+	return st_ops3->test_arena_multislot(p, ptr__arena);
+}
+
 struct bpf_testmod_btf_type_tag_1 {
 	int a;
 };
@@ -852,6 +866,7 @@ 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_testmod_ops3_call_test_arena_multislot)
 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 33f2af5b70857..210b919290cc2 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -103,6 +103,12 @@ struct bpf_testmod_ops2 {
 	int (*test_1)(void);
 };
 
+/* 16 bytes, so it takes two argument slots when passed by value */
+struct bpf_testmod_arena_pair {
+	u64 a;
+	u64 b;
+};
+
 struct bpf_testmod_ops3 {
 	int (*test_1)(void);
 	int (*test_2)(void);
@@ -112,6 +118,8 @@ struct bpf_testmod_ops3 {
 	/* 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);
+	/* a multi-slot leading arg, so @ptr is not at the slot its arg index suggests */
+	int (*test_arena_multislot)(struct bpf_testmod_arena_pair p, 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 c4383acb53c11..7d81070eefe7f 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -123,6 +123,7 @@ 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;
+int bpf_testmod_ops3_call_test_arena_multislot(__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/10 22:45 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12326
							output: 31
							thoughts: 651
						

Thoughts:
**Fuzzing Target Identification: BPF Arena Arguments in ARM64 JIT**

Okay, so this patch introduces BPF arena argument support within the ARM64 JIT compiler. I need to figure out if this is a worthwhile target for fuzzing and pinpoint the most relevant functions. The changes are concentrated in `arch/arm64/net/bpf_jit_comp.c`, `arch/arm64/lib/insn.c`, and `arch/arm64/include/asm/insn.h`, and there are some tests added in `tools/testing/selftests/bpf/`.

The core logic revolves around `emit_kfunc_arena_args` and `emit_arena_arg_conv`, which are called during the BPF JIT compilation process. This makes it definitely reachable and a good candidate for fuzzing.

Now, let's drill down to specific functions. In `arch/arm64/net/bpf_jit_comp.c`, I see a few options: `bpf_int_jit_compile` looks generic, and `build_insn` seems too broad. However, `prepare_trampoline` is modified to handle arena arguments, and then there are the static functions `emit_kfunc_arena_args`, `emit_arena_arg_conv`, and `save_args`.

Looking at `arch/arm64/lib/insn.c`, I see `aarch64_insn_gen_add_sub_extended_reg`. Importantly, this one isn't static. After a quick scan back at `arch/arm64/net/bpf_jit_comp.c`, I also see `bpf_jit_supports_arena_args` is newly added and non-static.

Okay, the JIT compiler functions are mostly static internally, so the main entry points are likely `bpf_int_jit_compile` and `arch_prepare_bpf_trampoline`. `arch_prepare_bpf_trampoline` calls `prepare_trampoline`, so that could be interesting.

Based on that, `aarch64_insn_gen_add_sub_extended_reg` looks like a good focus, as it's a new instruction generation function. `bpf_jit_supports_arena_args` is a new function as well. I should check the config options. `CONFIG_BPF_JIT`, `CONFIG_BPF_SYSCALL`, and `CONFIG_ARM64` are required.

To confirm the scope of this new function, I'll run a quick grep to locate the definition of `aarch64_insn_gen_add_sub_extended_reg`.



4/2 2026/08/10 22:45 tool grepper
0m Args:
{
  "Expression": "aarch64_insn_gen_add_sub_extended_reg"
}

Results:
{
  "Output": "arch/arm64/include/asm/insn.h=650=u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,\n--\narch/arm64/include/asm/insn.h-655-\t\t\t\t\t enum aarch64_insn_adsb_type type);\narch/arm64/include/asm/insn.h:656:u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,\narch/arm64/include/asm/insn.h-657-\t\t\t\t\t  enum aarch64_insn_register src,\n--\narch/arm64/lib/insn.c=931=u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,\n--\narch/arm64/lib/insn.c-993- */\narch/arm64/lib/insn.c:994:u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,\narch/arm64/lib/insn.c-995-\t\t\t\t\t  enum aarch64_insn_register src,\n--\narch/arm64/net/bpf_jit.h-247-#define A64_ADDSUB_EREG(sf, Rd, Rn, Rm, ext, shift, type) \\\narch/arm64/net/bpf_jit.h:248:\taarch64_insn_gen_add_sub_extended_reg(Rd, Rn, Rm, \\\narch/arm64/net/bpf_jit.h-249-\t\tAARCH64_INSN_EXTEND_##ext, shift, A64_VARIANT(sf), \\\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 14087
							output: 45
							thoughts: 227
						

Thoughts:
**My Assessment of the ARM64 BPF JIT Compiler Patch**

Okay, so this patch is introducing BPF arena argument support within the ARM64 JIT compiler. That's a functionally significant change, which means I need to pay close attention. From what I can gather, the key modifications are concentrated in `arch/arm64/net/bpf_jit_comp.c` and `arch/arm64/lib/insn.c`.

Specifically, I need to understand the implications of these new functions: `aarch64_insn_gen_add_sub_extended_reg` and `bpf_jit_supports_arena_args`. I'll want to dive deep into their implementations to see how they're handling arena arguments within the instruction generation and feature detection, respectively.

And of course, given this is a JIT compiler change, fuzzing is essential. Any change like this is a prime target for fuzzing. I'll need to recommend appropriate fuzzing strategies.

The configuration options that matter here are `BPF_JIT` and `BPF_SYSCALL`, confirming these are prerequisites for the new functionality.

I should also investigate `arch_prepare_bpf_trampoline`. The text says it's being modified as it calls `prepare_trampoline`, and I need to see how the changes cascade through the trampoline setup. Let's see... I'll check if `arch_prepare_bpf_trampoline` is defined in `arch/arm64/net/bpf_jit_comp.c`. I need to ensure the trampoline setup is handled correctly and is compatible with the arena argument mechanism.

In summary, this requires a careful review of the new instruction generation, the feature detection logic, and the trampoline setup, with a strong emphasis on fuzzing to validate the changes.



6/2 2026/08/10 22:45 tool grepper
0m Args:
{
  "Expression": "arch_prepare_bpf_trampoline",
  "PathPrefix": "arch/arm64/net/bpf_jit_comp.c"
}

Results:
{
  "Output": "arch/arm64/net/bpf_jit_comp.c=2656=static void store_func_meta(struct jit_ctx *ctx, u64 func_meta, int func_meta_off)\n--\narch/arm64/net/bpf_jit_comp.c-2661-\narch/arm64/net/bpf_jit_comp.c:2662:/* Based on the x86's implementation of arch_prepare_bpf_trampoline().\narch/arm64/net/bpf_jit_comp.c-2663- *\n--\narch/arm64/net/bpf_jit_comp.c=2997=int arch_protect_bpf_trampoline(void *image, unsigned int size)\n--\narch/arm64/net/bpf_jit_comp.c-3001-\narch/arm64/net/bpf_jit_comp.c:3002:int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *ro_image,\narch/arm64/net/bpf_jit_comp.c-3003-\t\t\t\tvoid *ro_image_end, const struct btf_func_model *m,\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 15074
							output: 44
							thoughts: 35
						

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

**Checking BPF Trampoline and JIT Compilation Readiness**

Alright, so `arch_prepare_bpf_trampoline` is definitely present, good. That's one key piece in place. Now, I need to confirm the presence of `bpf_int_jit_compile`. This is the big one, the core of whether we can even think about JIT compilation for BPF code on this architecture. If that function exists and is accessible, we’re in a much better position to proceed. Without it, we're stuck interpreting, and we all know how that impacts performance. So, let’s see if that hook is available... that's the crucial question.



8/2 2026/08/10 22:45 tool grepper
0m Args:
{
  "Expression": "bpf_int_jit_compile",
  "PathPrefix": "arch/arm64/net/bpf_jit_comp.c"
}

Results:
{
  "Output": "arch/arm64/net/bpf_jit_comp.c=2133=struct arm64_jit_data {\n--\narch/arm64/net/bpf_jit_comp.c-2139-\narch/arm64/net/bpf_jit_comp.c:2140:struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\narch/arm64/net/bpf_jit_comp.c-2141-{\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 15466
							output: 20
							thoughts: 136
						

Thoughts:
Okay, let me lay out my thinking here.

**Deconstructing BPF Arena Configuration**

Right, so I'm trying to pin down the dependencies for this BPF arena stuff. I've got a few focus symbols to consider: `aarch64_insn_gen_add_sub_extended_reg`, `bpf_jit_supports_arena_args`, `arch_prepare_bpf_trampoline`, and `bpf_int_jit_compile`. These seem like the key functions or features that relate directly to the arena functionality.

Now, as for the configs, I'm pretty sure `BPF_JIT` and `BPF_SYSCALL` are involved, as they often are with BPF-related features. But I need to be absolutely certain I haven't missed anything, especially something specific to the arena mechanism itself.

Therefore, I'm going to quickly check the Kconfig files. A `grep ARENA` search in `init/Kconfig` and `kernel/bpf/Kconfig` should give me any arena-specific configs I might have overlooked. That should give me a complete picture of the dependencies.



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

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

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

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

Args:
null

Results:
null

Tokens:
							input: 15885
							output: 29
							thoughts: 16
						

Thoughts:
Okay, here's my interpretation of those thoughts, crafted with the understanding that I'm talking to a fellow expert:

**Investigating ARENA in the Configuration Space**

Alright, I need to get a handle on how `ARENA` is used within our configuration ecosystem. My immediate instinct is to jump into the `Kconfig` files – that's where the heart of feature definition and dependencies lives. My thought is to run a quick `grep` for the string `ARENA`. It's a fundamental move – quick, dirty, but often highly effective for an initial exploration. This should give me a map of where `ARENA` is defined, which features depend on it, and hopefully some context around its purpose within the configuration options. It's the first step in understanding the broader context of ARENA, and I can start from there.



12/2 2026/08/10 22:46 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "ARENA",
  "PathPrefix": "kernel/bpf/Kconfig"
}

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

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

Args:
null

Results:
null

Tokens:
							input: 16122
							output: 26
							thoughts: 44
						

Thoughts:
Okay, let's see... my initial assumption about a separate configuration for the BPF arena might be incorrect. It's possible that the arena's behavior is intertwined with the broader `BPF_SYSCALL` mechanism, rather than having its own dedicated configuration.

To confirm this suspicion, I should immediately dive into the kernel source. The most logical place to start digging is in `kernel/bpf/arena.c`. If the arena has its own dedicated source file, that would provide a pretty definitive answer, and a place to find more information, on whether it's got a unique config. If it doesn't exist, well then that narrows down the options significantly. Let's get to it.



14/2 2026/08/10 22:46 tool grepper
0m Args:
{
  "Expression": "arena",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 1129 lines.\nUse more precise expression if possible.\n\nkernel/bpf/Makefile=19=ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\nkernel/bpf/Makefile:20:obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\nkernel/bpf/Makefile-21-endif\n--\nkernel/bpf/arena.c-16-/*\nkernel/bpf/arena.c:17: * bpf_arena is a sparsely populated shared memory region between bpf program and\nkernel/bpf/arena.c-18- * user space process.\n--\nkernel/bpf/arena.c-22- * kern_vm_start ffffc90001e69000 // picked by get_vm_area()\nkernel/bpf/arena.c:23: * For user space all pointers within the arena are normal 8-byte addresses.\nkernel/bpf/arena.c-24- * In this example 7f7d26200000 is the address of the first page (pgoff=0).\n--\nkernel/bpf/arena.c-30- *\nkernel/bpf/arena.c:31: * BPF JITs generate the following code to access arena:\nkernel/bpf/arena.c-32- *   mov eax, eax  // eax has lower 32-bit of user pointer\n--\nkernel/bpf/arena.c-39- * into kernel and user vma.\nkernel/bpf/arena.c:40: * bpf program can allocate a page via bpf_arena_alloc_pages() kfunc\nkernel/bpf/arena.c-41- * which will insert it into kernel vm_area.\n--\nkernel/bpf/arena.c-48-\nkernel/bpf/arena.c:49:static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable);\nkernel/bpf/arena.c-50-\nkernel/bpf/arena.c:51:struct bpf_arena {\nkernel/bpf/arena.c-52-\tstruct bpf_map map;\n--\nkernel/bpf/arena.c-59-\trqspinlock_t spinlock;\nkernel/bpf/arena.c:60:\t/* number of pages currently populated in the arena */\nkernel/bpf/arena.c-61-\tu64 nr_pages;\n--\nkernel/bpf/arena.c-71-\nkernel/bpf/arena.c:72:static void arena_free_worker(struct work_struct *work);\nkernel/bpf/arena.c:73:static void arena_free_irq(struct irq_work *iw);\nkernel/bpf/arena.c-74-\nkernel/bpf/arena.c:75:struct arena_free_span {\nkernel/bpf/arena.c-76-\tstruct llist_node node;\n--\nkernel/bpf/arena.c-80-\nkernel/bpf/arena.c:81:u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)\nkernel/bpf/arena.c-82-{\nkernel/bpf/arena.c:83:\treturn arena ? (u64) (long) arena-\u003ekern_vm-\u003eaddr + GUARD_SZ / 2 : 0;\nkernel/bpf/arena.c-84-}\nkernel/bpf/arena.c-85-\nkernel/bpf/arena.c:86:u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena)\nkernel/bpf/arena.c-87-{\nkernel/bpf/arena.c:88:\treturn arena ? arena-\u003euser_vm_start : 0;\nkernel/bpf/arena.c-89-}\n--\nkernel/bpf/arena.c-91-/**\nkernel/bpf/arena.c:92: * bpf_arena_map_kern_vm_start - kern_vm_start lookup by struct bpf_map *\nkernel/bpf/arena.c-93- * @map: a BPF_MAP_TYPE_ARENA map\n--\nkernel/bpf/arena.c-96- */\nkernel/bpf/arena.c:97:u64 bpf_arena_map_kern_vm_start(struct bpf_map *map)\nkernel/bpf/arena.c-98-{\nkernel/bpf/arena.c:99:\treturn bpf_arena_get_kern_vm_start(container_of(map, struct bpf_arena, map));\nkernel/bpf/arena.c-100-}\n--\nkernel/bpf/arena.c-102-/**\nkernel/bpf/arena.c:103: * bpf_prog_arena - return the bpf_map of the arena referenced by @prog\nkernel/bpf/arena.c-104- * @prog: a loaded BPF program\nkernel/bpf/arena.c-105- *\nkernel/bpf/arena.c:106: * The verifier enforces at most one arena per program and stores it in\nkernel/bpf/arena.c:107: * prog-\u003eaux-\u003earena. Return that arena's underlying bpf_map, or NULL if\nkernel/bpf/arena.c:108: * @prog does not reference an arena.\nkernel/bpf/arena.c-109- */\nkernel/bpf/arena.c:110:struct bpf_map *bpf_prog_arena(struct bpf_prog *prog)\nkernel/bpf/arena.c-111-{\nkernel/bpf/arena.c:112:\tstruct bpf_arena *arena = prog-\u003eaux-\u003earena;\nkernel/bpf/arena.c-113-\nkernel/bpf/arena.c:114:\treturn arena ? \u0026arena-\u003emap : NULL;\nkernel/bpf/arena.c-115-}\nkernel/bpf/arena.c-116-\nkernel/bpf/arena.c:117:static long arena_map_peek_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-118-{\n--\nkernel/bpf/arena.c-121-\nkernel/bpf/arena.c:122:static long arena_map_push_elem(struct bpf_map *map, void *value, u64 flags)\nkernel/bpf/arena.c-123-{\n--\nkernel/bpf/arena.c-126-\nkernel/bpf/arena.c:127:static long arena_map_pop_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-128-{\n--\nkernel/bpf/arena.c-131-\nkernel/bpf/arena.c:132:static long arena_map_delete_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-133-{\n--\nkernel/bpf/arena.c-136-\nkernel/bpf/arena.c:137:static int arena_map_get_next_key(struct bpf_map *map, void *key, void *next_key)\nkernel/bpf/arena.c-138-{\n--\nkernel/bpf/arena.c-141-\nkernel/bpf/arena.c:142:static long compute_pgoff(struct bpf_arena *arena, long uaddr)\nkernel/bpf/arena.c-143-{\nkernel/bpf/arena.c:144:\treturn (u32)(uaddr - (u32)arena-\u003euser_vm_start) \u003e\u003e PAGE_SHIFT;\nkernel/bpf/arena.c-145-}\n--\nkernel/bpf/arena.c=147=struct apply_range_data {\nkernel/bpf/arena.c:148:\tstruct bpf_arena *arena;\nkernel/bpf/arena.c-149-\tstruct page **pages;\n--\nkernel/bpf/arena.c=153=struct clear_range_data {\nkernel/bpf/arena.c:154:\tstruct bpf_arena *arena;\nkernel/bpf/arena.c-155-\tstruct llist_head *free_pages;\n--\nkernel/bpf/arena.c=158=static int apply_range_set_cb(pte_t *pte, unsigned long addr, void *data)\n--\nkernel/bpf/arena.c-184-\t\t\tcontinue;\nkernel/bpf/arena.c:185:\t\tif (WARN_ON_ONCE(pte_page(old) != d-\u003earena-\u003escratch_page))\nkernel/bpf/arena.c-186-\t\t\treturn -EBUSY;\n--\nkernel/bpf/arena.c-192-\t * Without ptep_try_set() there is no atomic installer, but such arches\nkernel/bpf/arena.c:193:\t * also do not wire up bpf_arena_handle_page_fault(), so no scratch page\nkernel/bpf/arena.c-194-\t * is ever installed and the slot is always none here.\n--\nkernel/bpf/arena.c-200-\td-\u003ei++;\nkernel/bpf/arena.c:201:\tWRITE_ONCE(d-\u003earena-\u003enr_pages, d-\u003earena-\u003enr_pages + 1);\nkernel/bpf/arena.c-202-\treturn 0;\n--\nkernel/bpf/arena.c=210=static int apply_range_clear_cb(pte_t *pte, unsigned long addr, void *data)\n--\nkernel/bpf/arena.c-228-\t/*\nkernel/bpf/arena.c:229:\t * Skip the per-arena scratch page. A kernel fault on an unallocated uaddr\nkernel/bpf/arena.c:230:\t * scratches its PTE. A later bpf_arena_free_pages() over that range walks\nkernel/bpf/arena.c-231-\t * here. Without the skip, scratch_page would be freed.\nkernel/bpf/arena.c-232-\t */\nkernel/bpf/arena.c:233:\tif (page == d-\u003earena-\u003escratch_page)\nkernel/bpf/arena.c-234-\t\treturn 0;\n--\nkernel/bpf/arena.c-236-\t__llist_add(\u0026page-\u003epcp_llist, d-\u003efree_pages);\nkernel/bpf/arena.c:237:\tWRITE_ONCE(d-\u003earena-\u003enr_pages, d-\u003earena-\u003enr_pages - 1);\nkernel/bpf/arena.c-238-\treturn 0;\n--\nkernel/bpf/arena.c=241=static int apply_range_set_scratch_cb(pte_t *pte, unsigned long addr, void *data)\n--\nkernel/bpf/arena.c-258-\nkernel/bpf/arena.c:259:static int populate_pgtable_except_pte(struct bpf_arena *arena)\nkernel/bpf/arena.c-260-{\nkernel/bpf/arena.c-261-\t/* Populate intermediates for the recovery range (4 GiB + upper half-guard). */\nkernel/bpf/arena.c:262:\treturn apply_to_page_range(\u0026init_mm, bpf_arena_get_kern_vm_start(arena),\nkernel/bpf/arena.c-263-\t\t\t\t   SZ_4G + GUARD_SZ / 2, apply_range_set_cb, NULL);\n--\nkernel/bpf/arena.c-265-\nkernel/bpf/arena.c:266:static struct bpf_map *arena_map_alloc(union bpf_attr *attr)\nkernel/bpf/arena.c-267-{\n--\nkernel/bpf/arena.c-269-\tint numa_node = bpf_map_attr_numa_node(attr);\nkernel/bpf/arena.c:270:\tstruct bpf_arena *arena;\nkernel/bpf/arena.c-271-\tu64 vm_range;\n--\nkernel/bpf/arena.c-273-\nkernel/bpf/arena.c:274:\tif (!bpf_jit_supports_arena())\nkernel/bpf/arena.c-275-\t\treturn ERR_PTR(-EOPNOTSUPP);\n--\nkernel/bpf/arena.c-299-\nkernel/bpf/arena.c:300:\tarena = bpf_map_area_alloc(sizeof(*arena), numa_node);\nkernel/bpf/arena.c:301:\tif (!arena)\nkernel/bpf/arena.c-302-\t\tgoto err;\nkernel/bpf/arena.c-303-\nkernel/bpf/arena.c:304:\tarena-\u003ekern_vm = kern_vm;\nkernel/bpf/arena.c:305:\tarena-\u003euser_vm_start = attr-\u003emap_extra;\nkernel/bpf/arena.c:306:\tif (arena-\u003euser_vm_start)\nkernel/bpf/arena.c:307:\t\tarena-\u003euser_vm_end = arena-\u003euser_vm_start + vm_range;\nkernel/bpf/arena.c-308-\nkernel/bpf/arena.c:309:\tINIT_LIST_HEAD(\u0026arena-\u003evma_list);\nkernel/bpf/arena.c:310:\tinit_llist_head(\u0026arena-\u003efree_spans);\nkernel/bpf/arena.c:311:\tinit_irq_work(\u0026arena-\u003efree_irq, arena_free_irq);\nkernel/bpf/arena.c:312:\tINIT_WORK(\u0026arena-\u003efree_work, arena_free_worker);\nkernel/bpf/arena.c:313:\tbpf_map_init_from_attr(\u0026arena-\u003emap, attr);\nkernel/bpf/arena.c-314-\nkernel/bpf/arena.c:315:\terr = bpf_map_alloc_pages(\u0026arena-\u003emap, NUMA_NO_NODE, 1, \u0026arena-\u003escratch_page);\nkernel/bpf/arena.c-316-\tif (err)\nkernel/bpf/arena.c:317:\t\tgoto err_free_arena;\nkernel/bpf/arena.c-318-\nkernel/bpf/arena.c:319:\trange_tree_init(\u0026arena-\u003ert);\nkernel/bpf/arena.c:320:\terr = range_tree_set(\u0026arena-\u003ert, 0, attr-\u003emax_entries);\nkernel/bpf/arena.c-321-\tif (err)\nkernel/bpf/arena.c-322-\t\tgoto err_free_scratch;\nkernel/bpf/arena.c:323:\tmutex_init(\u0026arena-\u003elock);\nkernel/bpf/arena.c:324:\tmutex_init(\u0026arena-\u003ezap_mutex);\nkernel/bpf/arena.c:325:\traw_res_spin_lock_init(\u0026arena-\u003espinlock);\nkernel/bpf/arena.c:326:\terr = populate_pgtable_except_pte(arena);\nkernel/bpf/arena.c-327-\tif (err)\n--\nkernel/bpf/arena.c-329-\nkernel/bpf/arena.c:330:\treturn \u0026arena-\u003emap;\nkernel/bpf/arena.c-331-\nkernel/bpf/arena.c-332-err_destroy_rt:\nkernel/bpf/arena.c:333:\trange_tree_destroy(\u0026arena-\u003ert);\nkernel/bpf/arena.c-334-err_free_scratch:\nkernel/bpf/arena.c:335:\t__free_page(arena-\u003escratch_page);\nkernel/bpf/arena.c:336:err_free_arena:\nkernel/bpf/arena.c:337:\tbpf_map_area_free(arena);\nkernel/bpf/arena.c-338-err:\n--\nkernel/bpf/arena.c=343=static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)\nkernel/bpf/arena.c-344-{\nkernel/bpf/arena.c:345:\tstruct bpf_arena *arena = data;\nkernel/bpf/arena.c-346-\tstruct page *page;\n--\nkernel/bpf/arena.c-356-\t */\nkernel/bpf/arena.c:357:\tif (page == arena-\u003escratch_page)\nkernel/bpf/arena.c-358-\t\treturn 0;\n--\nkernel/bpf/arena.c-360-\t * We do not update pte here:\nkernel/bpf/arena.c:361:\t * 1. Nobody should be accessing bpf_arena's range outside of a kernel bug\nkernel/bpf/arena.c-362-\t * 2. TLB flushing is batched or deferred. Even if we clear pte,\n--\nkernel/bpf/arena.c-369-\nkernel/bpf/arena.c:370:static void arena_map_free(struct bpf_map *map)\nkernel/bpf/arena.c-371-{\nkernel/bpf/arena.c:372:\tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\nkernel/bpf/arena.c-373-\n--\nkernel/bpf/arena.c-376-\t * mmap() holds vm_file which holds bpf_map refcnt.\nkernel/bpf/arena.c:377:\t * munmap() must have happened on vma followed by arena_vm_close()\nkernel/bpf/arena.c:378:\t * which would clear arena-\u003evma_list.\nkernel/bpf/arena.c-379-\t */\nkernel/bpf/arena.c:380:\tif (WARN_ON_ONCE(!list_empty(\u0026arena-\u003evma_list)))\nkernel/bpf/arena.c-381-\t\treturn;\n--\nkernel/bpf/arena.c-383-\t/* Ensure no pending deferred frees */\nkernel/bpf/arena.c:384:\tirq_work_sync(\u0026arena-\u003efree_irq);\nkernel/bpf/arena.c:385:\tflush_work(\u0026arena-\u003efree_work);\nkernel/bpf/arena.c-386-\n--\nkernel/bpf/arena.c-392-\t */\nkernel/bpf/arena.c:393:\tapply_to_existing_page_range(\u0026init_mm, bpf_arena_get_kern_vm_start(arena),\nkernel/bpf/arena.c:394:\t\t\t\t     SZ_4G + GUARD_SZ / 2, existing_page_cb, arena);\nkernel/bpf/arena.c:395:\tfree_vm_area(arena-\u003ekern_vm);\nkernel/bpf/arena.c:396:\trange_tree_destroy(\u0026arena-\u003ert);\nkernel/bpf/arena.c:397:\t__free_page(arena-\u003escratch_page);\nkernel/bpf/arena.c:398:\tbpf_map_area_free(arena);\nkernel/bpf/arena.c-399-}\nkernel/bpf/arena.c-400-\nkernel/bpf/arena.c:401:static void *arena_map_lookup_elem(struct bpf_map *map, void *key)\nkernel/bpf/arena.c-402-{\n--\nkernel/bpf/arena.c-405-\nkernel/bpf/arena.c:406:static long arena_map_update_elem(struct bpf_map *map, void *key,\nkernel/bpf/arena.c-407-\t\t\t\t  void *value, u64 flags)\n--\nkernel/bpf/arena.c-411-\nkernel/bpf/arena.c:412:static int arena_map_check_btf(struct bpf_map *map, const struct btf *btf,\nkernel/bpf/arena.c-413-\t\t\t       const struct btf_type *key_type, const struct btf_type *value_type)\n--\nkernel/bpf/arena.c-417-\nkernel/bpf/arena.c:418:static u64 arena_map_mem_usage(const struct bpf_map *map)\nkernel/bpf/arena.c-419-{\nkernel/bpf/arena.c:420:\tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\nkernel/bpf/arena.c-421-\nkernel/bpf/arena.c:422:\treturn (u64)READ_ONCE(arena-\u003enr_pages) \u003c\u003c PAGE_SHIFT;\nkernel/bpf/arena.c-423-}\n--\nkernel/bpf/arena.c=425=struct vma_list {\n--\nkernel/bpf/arena.c-431-\nkernel/bpf/arena.c:432:static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)\nkernel/bpf/arena.c-433-{\n--\nkernel/bpf/arena.c-442-\tvml-\u003ezap_gen = 0;\nkernel/bpf/arena.c:443:\tlist_add(\u0026vml-\u003ehead, \u0026arena-\u003evma_list);\nkernel/bpf/arena.c-444-\treturn 0;\n--\nkernel/bpf/arena.c-446-\nkernel/bpf/arena.c:447:static void arena_vm_open(struct vm_area_struct *vma)\nkernel/bpf/arena.c-448-{\n--\nkernel/bpf/arena.c-453-\nkernel/bpf/arena.c:454:static int arena_vm_may_split(struct vm_area_struct *vma, unsigned long addr)\nkernel/bpf/arena.c-455-{\n--\nkernel/bpf/arena.c-458-\nkernel/bpf/arena.c:459:static int arena_vm_mremap(struct vm_area_struct *vma)\nkernel/bpf/arena.c-460-{\n--\nkernel/bpf/arena.c-463-\nkernel/bpf/arena.c:464:static void arena_vm_close(struct vm_area_struct *vma)\nkernel/bpf/arena.c-465-{\nkernel/bpf/arena.c-466-\tstruct bpf_map *map = vma-\u003evm_file-\u003eprivate_data;\nkernel/bpf/arena.c:467:\tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\nkernel/bpf/arena.c-468-\tstruct vma_list *vml = vma-\u003evm_private_data;\n--\nkernel/bpf/arena.c-471-\t\treturn;\nkernel/bpf/arena.c:472:\tguard(mutex)(\u0026arena-\u003elock);\nkernel/bpf/arena.c-473-\t/* update link list under lock */\n--\nkernel/bpf/arena.c-478-\nkernel/bpf/arena.c:479:static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\nkernel/bpf/arena.c-480-{\nkernel/bpf/arena.c-481-\tstruct bpf_map *map = vmf-\u003evma-\u003evm_file-\u003eprivate_data;\nkernel/bpf/arena.c:482:\tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\nkernel/bpf/arena.c-483-\tstruct mem_cgroup *new_memcg, *old_memcg;\n--\nkernel/bpf/arena.c-488-\nkernel/bpf/arena.c:489:\tkbase = bpf_arena_get_kern_vm_start(arena);\nkernel/bpf/arena.c-490-\tkaddr = kbase + (u32)(vmf-\u003eaddress);\nkernel/bpf/arena.c-491-\nkernel/bpf/arena.c:492:\tif (raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags))\nkernel/bpf/arena.c-493-\t\t/*\n--\nkernel/bpf/arena.c-501-\tif (page) {\nkernel/bpf/arena.c:502:\t\tif (page == arena-\u003escratch_page)\nkernel/bpf/arena.c-503-\t\t\t/* BPF triggered scratch here; don't lazy-alloc over it */\n--\nkernel/bpf/arena.c-508-\nkernel/bpf/arena.c:509:\tbpf_map_memcg_enter(\u0026arena-\u003emap, \u0026old_memcg, \u0026new_memcg);\nkernel/bpf/arena.c-510-\nkernel/bpf/arena.c:511:\tif (arena-\u003emap.map_flags \u0026 BPF_F_SEGV_ON_FAULT)\nkernel/bpf/arena.c-512-\t\t/* User space requested to segfault when page is not allocated by bpf prog */\n--\nkernel/bpf/arena.c-514-\nkernel/bpf/arena.c:515:\tret = range_tree_clear(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\nkernel/bpf/arena.c-516-\tif (ret)\n--\nkernel/bpf/arena.c-518-\nkernel/bpf/arena.c:519:\tstruct apply_range_data data = { .arena = arena, .pages = \u0026page, .i = 0 };\nkernel/bpf/arena.c:520:\t/* Account into memcg of the process that created bpf_arena */\nkernel/bpf/arena.c-521-\tret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, \u0026page);\nkernel/bpf/arena.c-522-\tif (ret) {\nkernel/bpf/arena.c:523:\t\trange_tree_set(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\nkernel/bpf/arena.c-524-\t\tgoto out_sigsegv_memcg;\n--\nkernel/bpf/arena.c-528-\tif (ret) {\nkernel/bpf/arena.c:529:\t\trange_tree_set(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\nkernel/bpf/arena.c-530-\t\tfree_pages_nolock(page, 0);\n--\nkernel/bpf/arena.c-536-\tpage_ref_add(page, 1);\nkernel/bpf/arena.c:537:\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\nkernel/bpf/arena.c-538-\tvmf-\u003epage = page;\n--\nkernel/bpf/arena.c-542-out_sigsegv:\nkernel/bpf/arena.c:543:\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\nkernel/bpf/arena.c-544-\treturn VM_FAULT_SIGSEGV;\n--\nkernel/bpf/arena.c-546-\nkernel/bpf/arena.c:547:static const struct vm_operations_struct arena_vm_ops = {\nkernel/bpf/arena.c:548:\t.open\t\t= arena_vm_open,\nkernel/bpf/arena.c:549:\t.may_split\t= arena_vm_may_split,\nkernel/bpf/arena.c:550:\t.mremap\t\t= arena_vm_mremap,\nkernel/bpf/arena.c:551:\t.close\t\t= arena_vm_close,\nkernel/bpf/arena.c:552:\t.fault          = arena_vm_fault,\nkernel/bpf/arena.c-553-};\nkernel/bpf/arena.c-554-\nkernel/bpf/arena.c:555:static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long addr,\nkernel/bpf/arena.c-556-\t\t\t\t\t     unsigned long len, unsigned long pgoff,\n--\nkernel/bpf/arena.c-559-\tstruct bpf_map *map = filp-\u003eprivate_data;\nkernel/bpf/arena.c:560:\tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\nkernel/bpf/arena.c-561-\tlong ret;\n--\nkernel/bpf/arena.c-567-\nkernel/bpf/arena.c:568:\t/* if user_vm_start was specified at arena creation time */\nkernel/bpf/arena.c:569:\tif (arena-\u003euser_vm_start) {\nkernel/bpf/arena.c:570:\t\tif (len \u003e arena-\u003euser_vm_end - arena-\u003euser_vm_start)\nkernel/bpf/arena.c-571-\t\t\treturn -E2BIG;\nkernel/bpf/arena.c:572:\t\tif (len != arena-\u003euser_vm_end - arena-\u003euser_vm_start)\nkernel/bpf/arena.c-573-\t\t\treturn -EINVAL;\nkernel/bpf/arena.c:574:\t\tif (addr != arena-\u003euser_vm_start)\nkernel/bpf/arena.c-575-\t\t\treturn -EINVAL;\n--\nkernel/bpf/arena.c-582-\t\treturn ret;\nkernel/bpf/arena.c:583:\tif (WARN_ON_ONCE(arena-\u003euser_vm_start))\nkernel/bpf/arena.c-584-\t\t/* checks at map creation time should prevent this */\n--\nkernel/bpf/arena.c-588-\nkernel/bpf/arena.c:589:static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)\nkernel/bpf/arena.c-590-{\nkernel/bpf/arena.c:591:\tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\nkernel/bpf/arena.c-592-\nkernel/bpf/arena.c:593:\tguard(mutex)(\u0026arena-\u003elock);\nkernel/bpf/arena.c:594:\tif (arena-\u003euser_vm_start \u0026\u0026 arena-\u003euser_vm_start != vma-\u003evm_start)\nkernel/bpf/arena.c-595-\t\t/*\nkernel/bpf/arena.c:596:\t\t * If map_extra was not specified at arena creation time then\nkernel/bpf/arena.c-597-\t\t * 1st user process can do mmap(NULL, ...) to pick user_vm_start\n--\nkernel/bpf/arena.c-604-\nkernel/bpf/arena.c:605:\tif (arena-\u003euser_vm_end \u0026\u0026 arena-\u003euser_vm_end != vma-\u003evm_end)\nkernel/bpf/arena.c-606-\t\t/* all user processes must have the same size of mmap-ed region */\n--\nkernel/bpf/arena.c-612-\nkernel/bpf/arena.c:613:\tif (remember_vma(arena, vma))\nkernel/bpf/arena.c-614-\t\treturn -ENOMEM;\nkernel/bpf/arena.c-615-\nkernel/bpf/arena.c:616:\tarena-\u003euser_vm_start = vma-\u003evm_start;\nkernel/bpf/arena.c:617:\tarena-\u003euser_vm_end = vma-\u003evm_end;\nkernel/bpf/arena.c-618-\t/*\n--\nkernel/bpf/arena.c-620-\t * clears VM_MAYEXEC. Set VM_DONTEXPAND to avoid potential change\nkernel/bpf/arena.c:621:\t * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from\nkernel/bpf/arena.c-622-\t * being copied into the child process on fork.\n--\nkernel/bpf/arena.c-624-\tvm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);\nkernel/bpf/arena.c:625:\tvma-\u003evm_ops = \u0026arena_vm_ops;\nkernel/bpf/arena.c-626-\treturn 0;\n--\nkernel/bpf/arena.c-628-\nkernel/bpf/arena.c:629:static int arena_map_direct_value_addr(const struct bpf_map *map, u64 *imm, u32 off)\nkernel/bpf/arena.c-630-{\nkernel/bpf/arena.c:631:\tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\nkernel/bpf/arena.c-632-\nkernel/bpf/arena.c:633:\tif ((u64)off \u003e= arena-\u003euser_vm_end - arena-\u003euser_vm_start)\nkernel/bpf/arena.c-634-\t\treturn -ERANGE;\nkernel/bpf/arena.c:635:\t*imm = (unsigned long)arena-\u003euser_vm_start;\nkernel/bpf/arena.c-636-\treturn 0;\n--\nkernel/bpf/arena.c-638-\nkernel/bpf/arena.c:639:BTF_ID_LIST_SINGLE(bpf_arena_map_btf_ids, struct, bpf_arena)\nkernel/bpf/arena.c:640:const struct bpf_map_ops arena_map_ops = {\nkernel/bpf/arena.c-641-\t.map_meta_equal = bpf_map_meta_equal,\nkernel/bpf/arena.c:642:\t.map_alloc = arena_map_alloc,\nkernel/bpf/arena.c:643:\t.map_free = arena_map_free,\nkernel/bpf/arena.c:644:\t.map_direct_value_addr = arena_map_direct_value_addr,\nkernel/bpf/arena.c:645:\t.map_mmap = arena_map_mmap,\nkernel/bpf/arena.c:646:\t.map_get_unmapped_area = arena_get_unmapped_area,\nkernel/bpf/arena.c:647:\t.map_get_next_key = arena_map_get_next_key,\nkernel/bpf/arena.c:648:\t.map_push_elem = arena_map_push_elem,\nkernel/bpf/arena.c:649:\t.map_peek_elem = arena_map_peek_elem,\nkernel/bpf/arena.c:650:\t.map_pop_elem = arena_map_pop_elem,\nkernel/bpf/arena.c:651:\t.map_lookup_elem = arena_map_lookup_elem,\nkernel/bpf/arena.c:652:\t.map_update_elem = arena_map_update_elem,\nkernel/bpf/arena.c:653:\t.map_delete_elem = arena_map_delete_elem,\nkernel/bpf/arena.c:654:\t.map_check_btf = arena_map_check_btf,\nkernel/bpf/arena.c:655:\t.map_mem_usage = arena_map_mem_usage,\nkernel/bpf/arena.c:656:\t.map_btf_id = \u0026bpf_arena_map_btf_ids[0],\nkernel/bpf/arena.c-657-};\n--\nkernel/bpf/arena.c=659=static u64 clear_lo32(u64 val)\n--\nkernel/bpf/arena.c-667- */\nkernel/bpf/arena.c:668:static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt, int node_id,\nkernel/bpf/arena.c-669-\t\t\t      bool sleepable)\n--\nkernel/bpf/arena.c-671-\t/* user_vm_end/start are fixed before bpf prog runs */\nkernel/bpf/arena.c:672:\tlong page_cnt_max = (arena-\u003euser_vm_end - arena-\u003euser_vm_start) \u003e\u003e PAGE_SHIFT;\nkernel/bpf/arena.c:673:\tu64 kern_vm_start = bpf_arena_get_kern_vm_start(arena);\nkernel/bpf/arena.c-674-\tstruct mem_cgroup *new_memcg, *old_memcg;\n--\nkernel/bpf/arena.c-693-\t\t\treturn 0;\nkernel/bpf/arena.c:694:\t\tpgoff = compute_pgoff(arena, uaddr);\nkernel/bpf/arena.c-695-\t\tif (pgoff \u003e page_cnt_max - page_cnt)\n--\nkernel/bpf/arena.c-699-\nkernel/bpf/arena.c:700:\tbpf_map_memcg_enter(\u0026arena-\u003emap, \u0026old_memcg, \u0026new_memcg);\nkernel/bpf/arena.c-701-\t/* Cap allocation size to KMALLOC_MAX_CACHE_SIZE so kmalloc_nolock() can succeed. */\n--\nkernel/bpf/arena.c-707-\t}\nkernel/bpf/arena.c:708:\tdata.arena = arena;\nkernel/bpf/arena.c-709-\tdata.pages = pages;\nkernel/bpf/arena.c-710-\nkernel/bpf/arena.c:711:\tif (raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags))\nkernel/bpf/arena.c-712-\t\tgoto out_free_pages;\n--\nkernel/bpf/arena.c-714-\tif (uaddr) {\nkernel/bpf/arena.c:715:\t\tret = is_range_tree_set(\u0026arena-\u003ert, pgoff, page_cnt);\nkernel/bpf/arena.c-716-\t\tif (ret)\nkernel/bpf/arena.c-717-\t\t\tgoto out_unlock_free_pages;\nkernel/bpf/arena.c:718:\t\tret = range_tree_clear(\u0026arena-\u003ert, pgoff, page_cnt);\nkernel/bpf/arena.c-719-\t} else {\nkernel/bpf/arena.c:720:\t\tret = pgoff = range_tree_find(\u0026arena-\u003ert, page_cnt);\nkernel/bpf/arena.c-721-\t\tif (pgoff \u003e= 0)\nkernel/bpf/arena.c:722:\t\t\tret = range_tree_clear(\u0026arena-\u003ert, pgoff, page_cnt);\nkernel/bpf/arena.c-723-\t}\n--\nkernel/bpf/arena.c-727-\tremaining = page_cnt;\nkernel/bpf/arena.c:728:\tuaddr32 = (u32)(arena-\u003euser_vm_start + pgoff * PAGE_SIZE);\nkernel/bpf/arena.c-729-\n--\nkernel/bpf/arena.c-735-\nkernel/bpf/arena.c:736:\t\tret = bpf_map_alloc_pages(\u0026arena-\u003emap, node_id, this_batch, pages);\nkernel/bpf/arena.c-737-\t\tif (ret)\n--\nkernel/bpf/arena.c-763-\tflush_vmap_cache(kern_vm_start + uaddr32, mapped \u003c\u003c PAGE_SHIFT);\nkernel/bpf/arena.c:764:\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\nkernel/bpf/arena.c-765-\tkfree_nolock(pages);\nkernel/bpf/arena.c-766-\tbpf_map_memcg_exit(old_memcg, new_memcg);\nkernel/bpf/arena.c:767:\treturn clear_lo32(arena-\u003euser_vm_start) + uaddr32;\nkernel/bpf/arena.c-768-out:\nkernel/bpf/arena.c:769:\trange_tree_set(\u0026arena-\u003ert, pgoff + mapped, page_cnt - mapped);\nkernel/bpf/arena.c:770:\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\nkernel/bpf/arena.c-771-\tif (mapped) {\nkernel/bpf/arena.c-772-\t\tflush_vmap_cache(kern_vm_start + uaddr32, mapped \u003c\u003c PAGE_SHIFT);\nkernel/bpf/arena.c:773:\t\tarena_free_pages(arena, uaddr32, mapped, sleepable);\nkernel/bpf/arena.c-774-\t}\n--\nkernel/bpf/arena.c-776-out_unlock_free_pages:\nkernel/bpf/arena.c:777:\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\nkernel/bpf/arena.c-778-out_free_pages:\n--\nkernel/bpf/arena.c-788- */\nkernel/bpf/arena.c:789:static void zap_pages(struct bpf_arena *arena, long uaddr, long page_cnt)\nkernel/bpf/arena.c-790-{\n--\nkernel/bpf/arena.c-798-\t/*\nkernel/bpf/arena.c:799:\t * Taking mmap_read_lock() under arena-\u003elock would deadlock against\nkernel/bpf/arena.c:800:\t * arena_vm_close(), which runs with mmap_write_lock held and then\nkernel/bpf/arena.c:801:\t * acquires arena-\u003elock. Drop arena-\u003elock for mmap_read_lock().\nkernel/bpf/arena.c-802-\t *\n--\n\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 27120
							output: 35
							thoughts: 64
						

Thoughts:
Okay, here's my take on those thoughts, framed from my own perspective:

**BPF Arena & Instruction Generation on AArch64**

Right, so the BPF arena – that's fundamentally tied to the `CONFIG_BPF_SYSCALL` configuration, as expected. Need to keep that in mind when dealing with system calls and BPF interactions. Let's see... I'm curious about something specific, the `aarch64_insn_gen_add_sub_extended_reg` function. Is it actually exported and available for use? I seem to remember seeing it in the AArch64 architecture code.

Let me quickly confirm its accessibility. Okay, a quick grep reveals it's sitting there in `arch/arm64/lib/insn.c`. That file is where I expected it to be based on the general structure of the kernel. Good, good. Now I can work with that function.



16/2 2026/08/10 22:46 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*aarch64_insn_gen_add_sub_extended_reg"
}

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

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

Args:
null

Results:
null

Tokens:
							input: 27435
							output: 28
							thoughts: 59
						

Thoughts:
Okay, let's see...

**Investigating BPF JIT Arena Argument Support**

Right, so this thing isn't a statically compiled element, which is the first thing to check. But it's also not directly exported, that's what's throwing me. Hmm... Okay, where did I see it? Ah, yes, it was in `arch/arm64/net/bpf_jit_comp.c`, which is the BPF JIT compiler for the ARM64 architecture. Let me quickly verify if it's actually exported. That's the key question now.



18/2 2026/08/10 22:46 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*bpf_jit_supports_arena_args"
}

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

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

Args:
null

Results:
null

Tokens:
							input: 27665
							output: 24
							thoughts: 150
						

Thoughts:
**Investigating the JIT Compilation Path**

Okay, so I'm looking at this code and I see something isn't quite right, specifically that whatever I'm expecting isn't getting exported. Right, time to dig deeper. My initial thought is to examine `bpf_int_jit_compile`. It's the primary entry point for the JIT compilation process, so that seems like a logical place to start. But hold on, `bpf_int_jit_compile` is a fairly generic name – might be too broad a starting point for my purposes.

Let's refine the search. Instead of that, what about `bpf_jit_supports_arena_args`? That seems like a more focused symbol, more likely to provide the information I need, and perhaps shed light on why it is not exported. It's a key function related to arena arguments, so I'll see if that offers a clearer path to the root cause. Okay, that might not be it.

Alternatively, I could examine `aarch64_insn_gen_add_sub_extended_reg` – that sounds like a good candidate too, if the previous path doesn't yield anything. Focusing on a specific instruction generation function might give me the granular insight I need to understand why whatever it is I am expecting isn't working as I had hoped. Or what about `arch_prepare_bpf_trampoline`? That might be the right path to track down.

Right, let's start with `bpf_jit_supports_arena_args`. Let me check the call sites to see if that function is even being used anywhere. Hopefully that leads me to some clues as to where things are going wrong.



20/2 2026/08/10 22:46 tool grepper
0m Args:
{
  "Expression": "bpf_jit_supports_arena_args"
}

Results:
{
  "Output": "arch/arm64/net/bpf_jit_comp.c=2375=bool bpf_jit_supports_stack_args(void)\n--\narch/arm64/net/bpf_jit_comp.c-2379-\narch/arm64/net/bpf_jit_comp.c:2380:bool bpf_jit_supports_arena_args(void)\narch/arm64/net/bpf_jit_comp.c-2381-{\n--\narch/x86/net/bpf_jit_comp.c=4140=bool bpf_jit_supports_stack_args(void)\n--\narch/x86/net/bpf_jit_comp.c-4144-\narch/x86/net/bpf_jit_comp.c:4145:bool bpf_jit_supports_arena_args(void)\narch/x86/net/bpf_jit_comp.c-4146-{\n--\ninclude/linux/filter.h=1216=bool bpf_jit_supports_stack_args(void);\ninclude/linux/filter.h:1217:bool bpf_jit_supports_arena_args(void);\ninclude/linux/filter.h-1218-bool bpf_jit_supports_far_kfunc_call(void);\n--\nkernel/bpf/core.c=3306=bool __weak bpf_jit_supports_stack_args(void)\n--\nkernel/bpf/core.c-3310-\nkernel/bpf/core.c:3311:bool __weak bpf_jit_supports_arena_args(void)\nkernel/bpf/core.c-3312-{\n--\nkernel/bpf/verifier.c=11282=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-11359-\telse if (is_kfunc_arg_arena(meta-\u003ebtf, \u0026args[arg])) {\nkernel/bpf/verifier.c:11360:\t\tif (!bpf_jit_supports_arena_args()) {\nkernel/bpf/verifier.c-11361-\t\t\tverbose(env, \"JIT does not support kfunc %s() with arena pointer arguments\\n\",\n--\nkernel/bpf/verifier.c=18684=static int check_struct_ops_btf_id(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-18773-\t\tif (base_type(info-\u003ereg_type) == PTR_TO_ARENA) {\nkernel/bpf/verifier.c:18774:\t\t\tif (!bpf_jit_supports_arena_args()) {\nkernel/bpf/verifier.c-18775-\t\t\t\tverbose(env, \"JIT does not support arena arguments\\n\");\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 28953
							output: 155
							thoughts: 617
						

Thoughts:
**My Analysis of BPF Arena Argument Support in ARM64 JIT Compiler**

Okay, so the verifier calls `bpf_jit_supports_arena_args`. That seems like a good starting point for analysis. Let's see... I need to identify relevant symbols and then determine the necessary Kconfig options.

I'll check `emit_kfunc_arena_args` and `emit_arena_arg_conv`, but since they are static they won't be exposed. However, `aarch64_insn_gen_add_sub_extended_reg` is visible as a symbol. That's good. And `arch_prepare_bpf_trampoline` is also visible.

Therefore, my list of key symbols to consider are:

*   `aarch64_insn_gen_add_sub_extended_reg`
*   `bpf_jit_supports_arena_args`
*   `arch_prepare_bpf_trampoline`

Now for the Kconfig options. I see `BPF_JIT` and `BPF_SYSCALL` in the provided text, and I need to avoid prepending the "CONFIG_" prefix.

I'll quickly verify that each of the three symbols are non-static: `aarch64_insn_gen_add_sub_extended_reg` is defined in `arch/arm64/lib/insn.c`, and `arch_prepare_bpf_trampoline` and `bpf_jit_supports_arena_args` are in `arch/arm64/net/bpf_jit_comp.c`.

Okay, so this is a functional change. The patch implements arena argument support within the ARM64 JIT compiler, specifically affecting both the JIT compiler itself and the trampoline generation. This change allows BPF programs using arena arguments to be loaded and executed. It represents a significant change in the kernel's core BPF infrastructure, so it definitely needs fuzzing.

I'll provide the results using the `set-results` tool.



22/2 2026/08/10 22:46 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "aarch64_insn_gen_add_sub_extended_reg",
    "bpf_jit_supports_arena_args",
    "arch_prepare_bpf_trampoline"
  ],
  "Reasoning": "The patch adds support for BPF arena arguments in the ARM64 JIT compiler. It modifies the BPF JIT compiler and trampoline generation to handle arena arguments, which are reachable when loading and executing BPF programs that use arena arguments. This is a significant functional change in the core kernel/BPF subsystem.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "aarch64_insn_gen_add_sub_extended_reg",
    "bpf_jit_supports_arena_args",
    "arch_prepare_bpf_trampoline"
  ],
  "Reasoning": "The patch adds support for BPF arena arguments in the ARM64 JIT compiler. It modifies the BPF JIT compiler and trampoline generation to handle arena arguments, which are reachable when loading and executing BPF programs that use arena arguments. This is a significant functional change in the core kernel/BPF subsystem.",
  "WorthFuzzing": true
}

23/1 2026/08/10 22:46 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

24/2 2026/08/10 22:46 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch modifies the ARM64 BPF JIT compiler to support arena arguments. It adds new instruction generation functions and updates the trampoline generation logic to emit instructions that rebase arena pointers. The changes are confined to generating machine code and do not introduce new kernel structures copied to user space, nor do they add complex allocations or logic that could expose uninitialized memory in the kernel's C code. Any bugs in the JIT compiler would likely result in invalid memory accesses (e.g., out-of-bounds or NULL pointer dereferences) or arbitrary code execution, which are effectively caught by standard KASAN and other bug detectors. There is no specific risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

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

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

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

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

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

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

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


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

Prompt:
For your convenience, here is the diff of the changes:
commit 03c08c9d505790f3a5a72b6a35a089721c14fde1
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Aug 10 22:45:01 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 1004eb0bec617..d9cc2ab1cf018 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -301,9 +301,9 @@ An example is given below::
         }
 
 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.
+arena argument support (currently x86-64 and arm64); 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
diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
index cc0702fa64a79..4548e8015808d 100644
--- a/arch/arm64/include/asm/insn.h
+++ b/arch/arm64/include/asm/insn.h
@@ -205,6 +205,18 @@ enum aarch64_insn_adsb_type {
 	AARCH64_INSN_ADSB_SUB_SETFLAGS
 };
 
+/* option field of add/sub (extended register) */
+enum aarch64_insn_extend_type {
+	AARCH64_INSN_EXTEND_UXTB,
+	AARCH64_INSN_EXTEND_UXTH,
+	AARCH64_INSN_EXTEND_UXTW,
+	AARCH64_INSN_EXTEND_UXTX,
+	AARCH64_INSN_EXTEND_SXTB,
+	AARCH64_INSN_EXTEND_SXTH,
+	AARCH64_INSN_EXTEND_SXTW,
+	AARCH64_INSN_EXTEND_SXTX,
+};
+
 enum aarch64_insn_movewide_type {
 	AARCH64_INSN_MOVEWIDE_ZERO,
 	AARCH64_INSN_MOVEWIDE_KEEP,
@@ -378,6 +390,10 @@ __AARCH64_INSN_FUNCS(add,	0x7F200000, 0x0B000000)
 __AARCH64_INSN_FUNCS(adds,	0x7F200000, 0x2B000000)
 __AARCH64_INSN_FUNCS(sub,	0x7F200000, 0x4B000000)
 __AARCH64_INSN_FUNCS(subs,	0x7F200000, 0x6B000000)
+__AARCH64_INSN_FUNCS(add_ext,	0x7F200000, 0x0B200000)
+__AARCH64_INSN_FUNCS(adds_ext,	0x7F200000, 0x2B200000)
+__AARCH64_INSN_FUNCS(sub_ext,	0x7F200000, 0x4B200000)
+__AARCH64_INSN_FUNCS(subs_ext,	0x7F200000, 0x6B200000)
 __AARCH64_INSN_FUNCS(madd,	0x7FE08000, 0x1B000000)
 __AARCH64_INSN_FUNCS(msub,	0x7FE08000, 0x1B008000)
 __AARCH64_INSN_FUNCS(udiv,	0x7FE0FC00, 0x1AC00800)
@@ -637,6 +653,13 @@ u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,
 					 int shift,
 					 enum aarch64_insn_variant variant,
 					 enum aarch64_insn_adsb_type type);
+u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,
+					  enum aarch64_insn_register src,
+					  enum aarch64_insn_register reg,
+					  enum aarch64_insn_extend_type extend,
+					  int shift,
+					  enum aarch64_insn_variant variant,
+					  enum aarch64_insn_adsb_type type);
 u32 aarch64_insn_gen_data1(enum aarch64_insn_register dst,
 			   enum aarch64_insn_register src,
 			   enum aarch64_insn_variant variant,
diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
index 37ce75f7f1f08..e70ac02385153 100644
--- a/arch/arm64/lib/insn.c
+++ b/arch/arm64/lib/insn.c
@@ -986,6 +986,66 @@ u32 aarch64_insn_gen_add_sub_shifted_reg(enum aarch64_insn_register dst,
 	return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn, shift);
 }
 
+/*
+ * Unlike the shifted-register form, register 31 is not XZR everywhere here:
+ * it encodes SP for @src, and for @dst too unless @type sets the flags. Only
+ * @reg keeps the XZR meaning.
+ */
+u32 aarch64_insn_gen_add_sub_extended_reg(enum aarch64_insn_register dst,
+					  enum aarch64_insn_register src,
+					  enum aarch64_insn_register reg,
+					  enum aarch64_insn_extend_type extend,
+					  int shift,
+					  enum aarch64_insn_variant variant,
+					  enum aarch64_insn_adsb_type type)
+{
+	u32 insn;
+
+	switch (type) {
+	case AARCH64_INSN_ADSB_ADD:
+		insn = aarch64_insn_get_add_ext_value();
+		break;
+	case AARCH64_INSN_ADSB_SUB:
+		insn = aarch64_insn_get_sub_ext_value();
+		break;
+	case AARCH64_INSN_ADSB_ADD_SETFLAGS:
+		insn = aarch64_insn_get_adds_ext_value();
+		break;
+	case AARCH64_INSN_ADSB_SUB_SETFLAGS:
+		insn = aarch64_insn_get_subs_ext_value();
+		break;
+	default:
+		pr_err("%s: unknown add/sub encoding %d\n", __func__, type);
+		return AARCH64_BREAK_FAULT;
+	}
+
+	switch (variant) {
+	case AARCH64_INSN_VARIANT_32BIT:
+		break;
+	case AARCH64_INSN_VARIANT_64BIT:
+		insn |= AARCH64_INSN_SF_BIT;
+		break;
+	default:
+		pr_err("%s: unknown variant encoding %d\n", __func__, variant);
+		return AARCH64_BREAK_FAULT;
+	}
+
+	if (shift < 0 || shift > 4) {
+		pr_err("%s: invalid shift encoding %d\n", __func__, shift);
+		return AARCH64_BREAK_FAULT;
+	}
+
+	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RD, insn, dst);
+
+	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RN, insn, src);
+
+	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RM, insn, reg);
+
+	/* option in bits [15:13] and imm3 in [12:10] together fill IMM_6 */
+	return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn,
+					     (extend << 3) | shift);
+}
+
 u32 aarch64_insn_gen_data1(enum aarch64_insn_register dst,
 			   enum aarch64_insn_register src,
 			   enum aarch64_insn_variant variant,
diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h
index d13de4222cfb7..b2fe6e6dcf447 100644
--- a/arch/arm64/net/bpf_jit.h
+++ b/arch/arm64/net/bpf_jit.h
@@ -243,6 +243,17 @@
 /* Rn - Rm; set condition flags */
 #define A64_CMP(sf, Rn, Rm) A64_SUBS(sf, A64_ZR, Rn, Rm)
 
+/* Add/subtract (extended register) */
+#define A64_ADDSUB_EREG(sf, Rd, Rn, Rm, ext, shift, type) \
+	aarch64_insn_gen_add_sub_extended_reg(Rd, Rn, Rm, \
+		AARCH64_INSN_EXTEND_##ext, shift, A64_VARIANT(sf), \
+		AARCH64_INSN_ADSB_##type)
+/* Rd = Rn + (EXT(Rm) << shift) */
+#define A64_ADD_EXT(sf, Rd, Rn, Rm, ext, shift) \
+	A64_ADDSUB_EREG(sf, Rd, Rn, Rm, ext, shift, ADD)
+/* Rd = Rn + (u32)Rm */
+#define A64_ADD_UXTW(Rd, Rn, Rm) A64_ADD_EXT(1, Rd, Rn, Rm, UXTW, 0)
+
 /* Data-processing (1 source) */
 #define A64_DATA1(sf, Rd, Rn, type) aarch64_insn_gen_data1(Rd, Rn, \
 	A64_VARIANT(sf), AARCH64_INSN_DATA1_##type)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index d14d297ebb967..1cd327d213e34 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -1249,6 +1249,43 @@ static void emit_stack_arg_store_imm(s32 imm, s16 bpf_off, const u8 tmp, struct
 	}
 }
 
+/*
+ * Rebase the __arena args of a kfunc call to arena kernel addresses,
+ * xN = kern_vm_start + (u32)xN, with the arena base register holding
+ * kern_vm_start. A nullable arg preserves NULL by skipping the add, tested
+ * on the truncated value as arena NULL is offset 0.
+ */
+static int emit_kfunc_arena_args(struct jit_ctx *ctx, const struct bpf_insn *insn)
+{
+	const u8 arena_vm_base = bpf2a64[ARENA_VM_START];
+	const struct btf_func_model *fm;
+	int i;
+
+	fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
+	if (!fm)
+		return -EINVAL;
+
+	for (i = 0; i < min_t(int, fm->nr_args, MAX_BPF_FUNC_REG_ARGS); i++) {
+		const u8 reg = bpf2a64[BPF_REG_1 + i];
+		u8 flags = fm->arg_flags[i];
+
+		if (!(flags & BTF_FMODEL_ARENA_ARG))
+			continue;
+		if (WARN_ON_ONCE(!ctx->arena_vm_start))
+			return -EINVAL;
+
+		if (flags & BTF_FMODEL_NULLABLE_ARG) {
+			/* 32-bit mov clears the upper 32 bits */
+			emit(A64_MOV(0, reg, reg), ctx);
+			/* skip the add so that NULL stays NULL */
+			emit(A64_CBZ(0, reg, 2), ctx);
+		}
+		emit(A64_ADD_UXTW(reg, arena_vm_base, reg), ctx);
+	}
+
+	return 0;
+}
+
 /* JITs an eBPF instruction.
  * Returns:
  * 0  - successfully JITed an 8-byte eBPF instruction.
@@ -1671,6 +1708,11 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 					    &func_addr, &func_addr_fixed);
 		if (ret < 0)
 			return ret;
+		if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
+			ret = emit_kfunc_arena_args(ctx, insn);
+			if (ret < 0)
+				return ret;
+		}
 		emit_call(func_addr, ctx);
 		/*
 		 * Call to arch_bpf_timed_may_goto() is emitted by the
@@ -2335,6 +2377,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 (!aarch64_insn_copy(dst, src, len))
@@ -2508,34 +2555,76 @@ static void clear_garbage(struct jit_ctx *ctx, int reg, int effective_bytes)
 	}
 }
 
+/*
+ * Convert an arena kernel address into the arena pointer form on its way into
+ * the BPF ctx, dst = (u32)(src - kern_vm_start), with @base_lo holding the low
+ * 32 bits of 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(struct jit_ctx *ctx, u8 dst, u8 src, bool nullable, u8 base_lo)
+{
+	if (nullable) {
+		if (dst != src)
+			emit(A64_MOV(1, dst, src), ctx);
+		/* skip the subtraction so that NULL stays NULL */
+		emit(A64_CBZ(1, dst, 2), ctx);
+		src = dst;
+	}
+	emit(A64_SUB(0, dst, src, base_lo), ctx);
+}
+
 static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,
-		      const struct btf_func_model *m,
-		      const struct arg_aux *a,
-		      bool for_call_origin)
+		      const struct btf_func_model *m, const struct arg_aux *a,
+		      bool for_call_origin, bool is_struct_ops, u64 arena_base)
 {
-	int i;
-	int reg;
-	int doff;
-	int soff;
-	int slots;
 	u8 tmp = bpf2a64[TMP_REG_1];
+	u8 base_lo = bpf2a64[TMP_REG_2];
+	int i, reg, doff, soff, slots;
+
+	/* only the low 32 bits of the base take part in the subtraction */
+	if (arena_base)
+		emit_a64_mov_i(0, base_lo, (s32)(u32)arena_base, ctx);
 
 	/* store arguments to the stack for the bpf program, or restore
 	 * arguments from stack for the original function
 	 */
-	for (reg = 0; reg < a->regs_for_args; reg++) {
-		emit(for_call_origin ?
-		     A64_LDR64I(reg, A64_SP, bargs_off) :
-		     A64_STR64I(reg, A64_SP, bargs_off),
-		     ctx);
-		bargs_off += 8;
+	for (i = 0, reg = 0; i < a->args_in_regs; i++) {
+		bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+		bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
+		slots = (m->arg_size[i] + 7) / 8;
+		while (slots-- > 0) {
+			if (for_call_origin) {
+				emit(A64_LDR64I(reg, A64_SP, bargs_off), ctx);
+			} else if (arena_arg) {
+				emit_arena_arg_conv(ctx, tmp, reg, nullable, base_lo);
+				emit(A64_STR64I(tmp, A64_SP, bargs_off), ctx);
+			} else {
+				emit(A64_STR64I(reg, A64_SP, bargs_off), ctx);
+			}
+			reg++;
+			bargs_off += 8;
+		}
 	}
 
-	soff = 32; /* on stack arguments start from FP + 32 */
+	/*
+	 * On-stack arguments start above the frame(s) pushed by the trampoline
+	 * prologue. Entered through the fentry call from a traced function, the
+	 * prologue saves both the parent (FP/x9) and the traced function
+	 * (FP/LR) frames, so the arguments start at FP + 32. A struct_ops
+	 * callback is called indirectly and only the FP/LR frame is saved, so
+	 * they start at FP + 16.
+	 */
+	soff = is_struct_ops ? 16 : 32;
 	doff = (for_call_origin ? oargs_off : bargs_off);
 
 	/* save on stack arguments */
 	for (i = a->args_in_regs; i < m->nr_args; i++) {
+		bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+		bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
 		slots = (m->arg_size[i] + 7) / 8;
 		/* verifier ensures arg_size <= 16, so slots equals 1 or 2 */
 		while (slots-- > 0) {
@@ -2545,6 +2634,8 @@ static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,
 			 */
 			if (slots == 0 && !for_call_origin)
 				clear_garbage(ctx, tmp, m->arg_size[i] % 8);
+			if (arena_arg)
+				emit_arena_arg_conv(ctx, tmp, tmp, nullable, base_lo);
 			emit(A64_STR64I(tmp, A64_SP, doff), ctx);
 			soff += 8;
 			doff += 8;
@@ -2604,8 +2695,21 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
 	bool is_struct_ops = is_struct_ops_tramp(fentry);
 	int cookie_off, cookie_cnt, cookie_bargs_off;
 	int fsession_cnt = bpf_fsession_cnt(tnodes);
+	u64 arena_base;
 	u64 func_meta;
 
+	/*
+	 * F_INDIRECT is only compatible with F_RET_FENTRY_RET, it is explicitly
+	 * incompatible with F_CALL_ORIG | F_SKIP_FRAME | F_IP_ARG because
+	 * @func_addr. Arena conversion relies on this: bpf_tramp_arena_base()
+	 * only returns a base for the indirect trampoline, which therefore
+	 * never calls the original function with converted arguments.
+	 */
+	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);
+
 	/* trampoline stack layout:
 	 *                    [ parent ip         ]
 	 *                    [ FP                ]
@@ -2721,7 +2825,7 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
 	store_func_meta(ctx, func_meta, func_meta_off);
 
 	/* save args for bpf */
-	save_args(ctx, bargs_off, oargs_off, m, a, false);
+	save_args(ctx, bargs_off, oargs_off, m, a, false, is_struct_ops, arena_base);
 
 	/* save callee saved registers */
 	emit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);
@@ -2769,8 +2873,8 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
 	}
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
-		/* save args for original func */
-		save_args(ctx, bargs_off, oargs_off, m, a, true);
+		/* the original func takes kernel addresses, never converted ones */
+		save_args(ctx, bargs_off, oargs_off, m, a, true, is_struct_ops, 0);
 		/* call original func */
 		emit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);
 		emit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx);
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
index 940ec2cda0d52..7f9f54ba3fbe5 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
@@ -6,7 +6,7 @@
 #include "struct_ops_arena_attach.skel.h"
 #include "struct_ops_arena_fail.skel.h"
 
-#if defined(__x86_64__)
+#if defined(__x86_64__) || defined(__aarch64__)
 /*
  * Attach callbacks with __arena and __arena__nullable arguments and drive
  * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.
@@ -111,11 +111,11 @@ static void arena_arg_attach(void)
 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.
+	 * Arena struct_ops arguments need JIT support, currently x86-64 and
+	 * arm64 only. Elsewhere verification fails with "JIT does not support
+	 * arena arguments", so the programs cannot even load.
 	 */
-#if defined(__x86_64__)
+#if defined(__x86_64__) || defined(__aarch64__)
 	if (test__start_subtest("arena_arg"))
 		arena_arg();
 	if (test__start_subtest("arena_arg_fail"))
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c
index cdcea889da586..bf0d304e0e59c 100644
--- a/tools/testing/selftests/bpf/progs/arena_kfunc.c
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -27,6 +27,7 @@ volatile u64 stash;
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_forms(void *ctx)
 {
@@ -70,6 +71,7 @@ int arena_arg_forms(void *ctx)
  */
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_rebase(void *ctx)
 {
@@ -111,6 +113,7 @@ int arena_arg_rebase(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_args5(void *ctx)
 {
@@ -142,6 +145,7 @@ int arena_args5(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_mixed(void *ctx)
 {
@@ -169,6 +173,7 @@ int arena_arg_mixed(void *ctx)
 /* kernel-side faults on unpopulated pages recover via the scratch page */
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __success __retval(0)
 int arena_arg_unpopulated(void *ctx)
 {
@@ -189,6 +194,7 @@ int arena_arg_unpopulated(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __failure __msg("arena pointer requires a program with an associated arena")
 int arena_arg_no_arena(void *ctx)
 {
@@ -198,6 +204,7 @@ int arena_arg_no_arena(void *ctx)
 
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __failure __msg("is not a pointer to arena or scalar")
 int arena_arg_bad_reg(void *ctx)
 {
@@ -213,6 +220,7 @@ int arena_arg_bad_reg(void *ctx)
 	defined(__BPF_FEATURE_STACK_ARGUMENT)
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __failure __msg("arena pointer cannot be a stack argument")
 int arena_arg_stack(void *ctx)
 {
@@ -223,6 +231,7 @@ int arena_arg_stack(void *ctx)
 #else
 SEC("syscall")
 __arch_x86_64
+__arch_arm64
 __description("arena_arg_stack: not supported, dummy test")
 __success
 int arena_arg_stack(void *ctx)
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
index c9b9186626162..b5a01cbc33a73 100644
--- a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
@@ -32,6 +32,10 @@ __jited("	movl	%edi, %edi")
 __jited("	addq	%r12, %rdi")
 __jited("...")
 __jited("	callq	{{.*}}")
+__arch_arm64
+__jited("...")
+__jited("	add	x0, x28, w0, uxtw")
+__jited("	{{(bl|mov)	.*}}")
 __success
 int arena_arg_jit_rebase(void *ctx)
 {
@@ -48,6 +52,12 @@ __jited("	testl	%edi, %edi")
 __jited("	je	L0")
 __jited("	addq	%r12, %rdi")
 __jited("L0:	callq	{{.*}}")
+__arch_arm64
+__jited("...")
+__jited("	mov	w0, w0")
+__jited("	cbz	w0, L0")
+__jited("	add	x0, x28, w0, uxtw")
+__jited("L0:	{{.*}}")
 __success
 int arena_arg_jit_nullable(void *ctx)
 {
@@ -72,6 +82,16 @@ __jited("	testl	%r8d, %r8d")
 __jited("	je	L0")
 __jited("	addq	%r12, %r8")
 __jited("L0:	callq	{{.*}}")
+__arch_arm64
+__jited("...")
+__jited("	add	x0, x28, w0, uxtw")
+__jited("	add	x1, x28, w1, uxtw")
+__jited("	add	x2, x28, w2, uxtw")
+__jited("	add	x3, x28, w3, uxtw")
+__jited("	mov	w4, w4")
+__jited("	cbz	w4, L0")
+__jited("	add	x4, x28, w4, uxtw")
+__jited("L0:	{{.*}}")
 __success
 int arena_arg_jit_args5(void *ctx)
 {
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
index ba04c73d8d967..8aa8639df91f0 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_arena.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
@@ -59,11 +59,28 @@ int test_arena_stack_cb(unsigned long long *ctx)
 	return 0;
 }
 
+SEC("struct_ops/test_arena_multislot")
+int test_arena_multislot_cb(unsigned long long *ctx)
+{
+	u64 __arena *ptr = (u64 __arena *)ctx[2];
+
+	arena_touch++;
+	/*
+	 * The 16-byte struct occupies ctx[0] and ctx[1], so @ptr is argument
+	 * one but slot two. Getting that wrong hands the callback a scalar.
+	 */
+	if (ctx[0] != 11 || ctx[1] != 22)
+		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,
+	.test_arena_multislot = (void *)test_arena_multislot_cb,
 };
 
 SEC("syscall")
@@ -109,6 +126,13 @@ int trigger(void *ctx)
 	if (*val != 44)
 		return 9;
 
+	/* a multi-slot arg precedes the arena pointer here */
+	ret = bpf_testmod_ops3_call_test_arena_multislot((u64 *)val);
+	if (ret)
+		return 10;
+	if (*val != 45)
+		return 11;
+
 	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
 #endif
 	return 0;
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index a6133f7521f34..9366a3c578f13 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -402,12 +402,19 @@ static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,
 	return 0;
 }
 
+static int bpf_testmod_ops3__test_arena_multislot(struct bpf_testmod_arena_pair p,
+						  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,
+	.test_arena_multislot = bpf_testmod_ops3__test_arena_multislot,
 };
 
 static void bpf_testmod_test_struct_ops3(void)
@@ -441,6 +448,13 @@ __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);
 }
 
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_multislot(u64 *ptr__arena)
+{
+	struct bpf_testmod_arena_pair p = { .a = 11, .b = 22 };
+
+	return st_ops3->test_arena_multislot(p, ptr__arena);
+}
+
 struct bpf_testmod_btf_type_tag_1 {
 	int a;
 };
@@ -852,6 +866,7 @@ 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_testmod_ops3_call_test_arena_multislot)
 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 33f2af5b70857..210b919290cc2 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -103,6 +103,12 @@ struct bpf_testmod_ops2 {
 	int (*test_1)(void);
 };
 
+/* 16 bytes, so it takes two argument slots when passed by value */
+struct bpf_testmod_arena_pair {
+	u64 a;
+	u64 b;
+};
+
 struct bpf_testmod_ops3 {
 	int (*test_1)(void);
 	int (*test_2)(void);
@@ -112,6 +118,8 @@ struct bpf_testmod_ops3 {
 	/* 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);
+	/* a multi-slot leading arg, so @ptr is not at the slot its arg index suggests */
+	int (*test_arena_multislot)(struct bpf_testmod_arena_pair p, 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 c4383acb53c11..7d81070eefe7f 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -123,6 +123,7 @@ 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;
+int bpf_testmod_ops3_call_test_arena_multislot(__u64 *ptr__arena) __ksym;
 
 __u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,
 				__u32 c, __u64 d) __ksym;

25/3 2026/08/10 22:46 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12323
							output: 181
							thoughts: 1088
						

Thoughts:
**KMSAN Assessment of ARM64 BPF JIT Arena Argument Support**

Okay, so this patch is adding arena argument support to the ARM64 BPF JIT compiler. I need to make sure this doesn't introduce any uninitialized memory issues that KMSAN would catch. The patch's core is modifying the JIT to generate instructions for handling arena pointers. It's also updating instruction generation (`arch/arm64/lib/insn.c`) and adding tests.

Initially, I looked for the typical KMSAN red flags: kernel data sent to userspace, uninitialized variables in conditional logic, complex data structure initialization, or vulnerabilities in bounds checking.  None of those apply directly.  This is all about machine code generation within the JIT.

The JIT itself generates instructions, so a bug *could* lead to out-of-bounds access or arbitrary code execution, but that's a general JIT bug, not necessarily something KMSAN is tailored for unless the *JIT* itself uses uninitialized memory during compilation.  I did a quick scan for uninitialized variables in the compilation process, variables like `tmp`, `base_lo`, and `slots`, and they all appear initialized.

Specifically, I dove into the `save_args` function and the `emit_arena_arg_conv` function to ensure there weren't sneaky uninitialized register uses, but it's just generating code that saves arguments to the stack or performs an address conversion, and nothing points to direct risks.  The `clear_garbage` call for stack arguments is existing code, so that's not a new concern.

So, this patch modifies the JIT's code generation, but it doesn't seem to introduce any of the uninitialized memory risks KMSAN is specifically designed to detect. I think the JIT compilation process itself is sound and doesn't rely on uninitialized memory during its execution. I'll set `NeedsKMSAN=false`.



26/3 2026/08/10 22:47 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch modifies the ARM64 BPF JIT compiler to support arena arguments. It adds new instruction generation functions and updates the trampoline generation logic to emit instructions that rebase arena pointers. The changes are confined to generating machine code and do not introduce new kernel structures copied to user space, nor do they add complex allocations or logic that could expose uninitialized memory in the kernel's C code. Any bugs in the JIT compiler would likely result in invalid memory accesses (e.g., out-of-bounds or NULL pointer dereferences) or arbitrary code execution, which are effectively caught by standard KASAN and other bug detectors. There is no specific risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch modifies the ARM64 BPF JIT compiler to support arena arguments. It adds new instruction generation functions and updates the trampoline generation logic to emit instructions that rebase arena pointers. The changes are confined to generating machine code and do not introduce new kernel structures copied to user space, nor do they add complex allocations or logic that could expose uninitialized memory in the kernel's C code. Any bugs in the JIT compiler would likely result in invalid memory accesses (e.g., out-of-bounds or NULL pointer dereferences) or arbitrary code execution, which are effectively caught by standard KASAN and other bug detectors. There is no specific risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

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