AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/08 17:15 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "emit_bpf_tail_call",
    "__arch_prepare_bpf_trampoline",
    "bpf_jit_build_prologue",
    "__build_epilogue"
  ],
  "Reasoning": "The patch implements support for mixing bpf2bpf calls and tailcalls in the RISC-V BPF JIT compiler. It modifies core JIT functions like prologue/epilogue generation, tail call emission, and trampoline preparation. These are functional changes to reachable kernel code that should be fuzzed to ensure correctness of the JIT compiler.",
  "WorthFuzzing": true
}

1/1 2026/07/08 17:15 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit fdc44c1b41e64f09d930a6d3578bb3a920ea1fc2\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Jul 8 17:15:41 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c\nindex b0075ece4a6e7f..36337c3dad8513 100644\n--- a/arch/arm64/net/bpf_jit_comp.c\n+++ b/arch/arm64/net/bpf_jit_comp.c\n@@ -2544,12 +2544,6 @@ static void restore_args(struct jit_ctx *ctx, int bargs_off, int nregs)\n \t}\n }\n \n-static bool is_struct_ops_tramp(const struct bpf_tramp_nodes *fentry_nodes)\n-{\n-\treturn fentry_nodes-\u003enr_nodes == 1 \u0026\u0026\n-\t\tfentry_nodes-\u003enodes[0]-\u003elink-\u003etype == BPF_LINK_TYPE_STRUCT_OPS;\n-}\n-\n static void store_func_meta(struct jit_ctx *ctx, u64 func_meta, int func_meta_off)\n {\n \temit_a64_mov_i64(A64_R(10), func_meta, ctx);\ndiff --git a/arch/riscv/net/bpf_jit.h b/arch/riscv/net/bpf_jit.h\nindex da027179024421..419b9d795f2a7a 100644\n--- a/arch/riscv/net/bpf_jit.h\n+++ b/arch/riscv/net/bpf_jit.h\n@@ -81,6 +81,7 @@ struct rv_jit_context {\n \tint ex_jmp_off;\n \tunsigned long flags;\n \tint stack_size;\n+\tint tcc_offset;\n \tu64 arena_vm_start;\n \tu64 user_vm_start;\n };\ndiff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c\nindex f9d5347ba966b7..823262ca47ebf7 100644\n--- a/arch/riscv/net/bpf_jit_comp64.c\n+++ b/arch/riscv/net/bpf_jit_comp64.c\n@@ -18,12 +18,12 @@\n #define RV_MAX_REG_ARGS 8\n #define RV_FENTRY_NINSNS 2\n #define RV_FENTRY_NBYTES (RV_FENTRY_NINSNS * 4)\n-#define RV_KCFI_NINSNS (IS_ENABLED(CONFIG_CFI) ? 1 : 0)\n /* imm that allows emit_imm to emit max count insns */\n #define RV_MAX_COUNT_IMM 0x7FFF7FF7FF7FF7FF\n+/* fentry and TCC init insns will be skipped on tailcall */\n+#define RV_TAILCALL_OFFSET ((RV_FENTRY_NINSNS + 1) * 4)\n \n #define RV_REG_TCC RV_REG_A6\n-#define RV_REG_TCC_SAVED RV_REG_S6 /* Store A6 in S6 if program do calls */\n #define RV_REG_ARENA RV_REG_S7 /* For storing arena_vm_start */\n \n static const int regmap[] = {\n@@ -57,14 +57,12 @@ static const int pt_regmap[] = {\n };\n \n enum {\n-\tRV_CTX_F_SEEN_TAIL_CALL =\t0,\n \tRV_CTX_F_SEEN_CALL =\t\tRV_REG_RA,\n \tRV_CTX_F_SEEN_S1 =\t\tRV_REG_S1,\n \tRV_CTX_F_SEEN_S2 =\t\tRV_REG_S2,\n \tRV_CTX_F_SEEN_S3 =\t\tRV_REG_S3,\n \tRV_CTX_F_SEEN_S4 =\t\tRV_REG_S4,\n \tRV_CTX_F_SEEN_S5 =\t\tRV_REG_S5,\n-\tRV_CTX_F_SEEN_S6 =\t\tRV_REG_S6,\n };\n \n static u8 bpf_to_rv_reg(int bpf_reg, struct rv_jit_context *ctx)\n@@ -77,7 +75,6 @@ static u8 bpf_to_rv_reg(int bpf_reg, struct rv_jit_context *ctx)\n \tcase RV_CTX_F_SEEN_S3:\n \tcase RV_CTX_F_SEEN_S4:\n \tcase RV_CTX_F_SEEN_S5:\n-\tcase RV_CTX_F_SEEN_S6:\n \t\t__set_bit(reg, \u0026ctx-\u003eflags);\n \t}\n \treturn reg;\n@@ -92,7 +89,6 @@ static bool seen_reg(int reg, struct rv_jit_context *ctx)\n \tcase RV_CTX_F_SEEN_S3:\n \tcase RV_CTX_F_SEEN_S4:\n \tcase RV_CTX_F_SEEN_S5:\n-\tcase RV_CTX_F_SEEN_S6:\n \t\treturn test_bit(reg, \u0026ctx-\u003eflags);\n \t}\n \treturn false;\n@@ -108,32 +104,6 @@ static void mark_call(struct rv_jit_context *ctx)\n \t__set_bit(RV_CTX_F_SEEN_CALL, \u0026ctx-\u003eflags);\n }\n \n-static bool seen_call(struct rv_jit_context *ctx)\n-{\n-\treturn test_bit(RV_CTX_F_SEEN_CALL, \u0026ctx-\u003eflags);\n-}\n-\n-static void mark_tail_call(struct rv_jit_context *ctx)\n-{\n-\t__set_bit(RV_CTX_F_SEEN_TAIL_CALL, \u0026ctx-\u003eflags);\n-}\n-\n-static bool seen_tail_call(struct rv_jit_context *ctx)\n-{\n-\treturn test_bit(RV_CTX_F_SEEN_TAIL_CALL, \u0026ctx-\u003eflags);\n-}\n-\n-static u8 rv_tail_call_reg(struct rv_jit_context *ctx)\n-{\n-\tmark_tail_call(ctx);\n-\n-\tif (seen_call(ctx)) {\n-\t\t__set_bit(RV_CTX_F_SEEN_S6, \u0026ctx-\u003eflags);\n-\t\treturn RV_REG_S6;\n-\t}\n-\treturn RV_REG_A6;\n-}\n-\n static bool is_32b_int(s64 val)\n {\n \treturn -(1L \u003c\u003c 31) \u003c= val \u0026\u0026 val \u003c (1L \u003c\u003c 31);\n@@ -258,23 +228,20 @@ static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)\n \t\temit_ld(RV_REG_S5, store_offset, RV_REG_SP, ctx);\n \t\tstore_offset -= 8;\n \t}\n-\tif (seen_reg(RV_REG_S6, ctx)) {\n-\t\temit_ld(RV_REG_S6, store_offset, RV_REG_SP, ctx);\n-\t\tstore_offset -= 8;\n-\t}\n \tif (ctx-\u003earena_vm_start) {\n \t\temit_ld(RV_REG_ARENA, store_offset, RV_REG_SP, ctx);\n \t\tstore_offset -= 8;\n \t}\n \n+\t/* restore TCC from stack to RV_REG_TCC */\n+\temit_ld(RV_REG_TCC, ctx-\u003etcc_offset, RV_REG_SP, ctx);\n+\n \temit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);\n \t/* Set return value. */\n \tif (!is_tail_call)\n \t\temit_addiw(RV_REG_A0, RV_REG_A5, 0, ctx);\n \temit_jalr(RV_REG_ZERO, is_tail_call ? RV_REG_T3 : RV_REG_RA,\n-\t\t  /* kcfi, fentry and TCC init insns will be skipped on tailcall */\n-\t\t  is_tail_call ? (RV_KCFI_NINSNS + RV_FENTRY_NINSNS + 1) * 4 : 0,\n-\t\t  ctx);\n+\t\t  is_tail_call ? RV_TAILCALL_OFFSET : 0, ctx);\n }\n \n static void emit_bcc(u8 cond, u8 rd, u8 rs, int rvoff,\n@@ -355,7 +322,6 @@ static void emit_branch(u8 cond, u8 rd, u8 rs, int rvoff,\n static int emit_bpf_tail_call(int insn, struct rv_jit_context *ctx)\n {\n \tint tc_ninsn, off, start_insn = ctx-\u003eninsns;\n-\tu8 tcc = rv_tail_call_reg(ctx);\n \n \t/* a0: \u0026ctx\n \t * a1: \u0026array\n@@ -378,7 +344,8 @@ static int emit_bpf_tail_call(int insn, struct rv_jit_context *ctx)\n \t/* if (--TCC \u003c 0)\n \t *     goto out;\n \t */\n-\temit_addi(RV_REG_TCC, tcc, -1, ctx);\n+\temit_ld(RV_REG_TCC, ctx-\u003etcc_offset, RV_REG_SP, ctx);\n+\temit_addi(RV_REG_TCC, RV_REG_TCC, -1, ctx);\n \toff = ninsns_rvoff(tc_ninsn - (ctx-\u003eninsns - start_insn));\n \temit_branch(BPF_JSLT, RV_REG_TCC, RV_REG_ZERO, off, ctx);\n \n@@ -394,7 +361,10 @@ static int emit_bpf_tail_call(int insn, struct rv_jit_context *ctx)\n \toff = ninsns_rvoff(tc_ninsn - (ctx-\u003eninsns - start_insn));\n \temit_branch(BPF_JEQ, RV_REG_T2, RV_REG_ZERO, off, ctx);\n \n-\t/* goto *(prog-\u003ebpf_func + 4); */\n+\t/* store updated TCC back to stack */\n+\temit_sd(RV_REG_SP, ctx-\u003etcc_offset, RV_REG_TCC, ctx);\n+\n+\t/* goto *(prog-\u003ebpf_func + RV_TAILCALL_OFFSET); */\n \toff = offsetof(struct bpf_prog, bpf_func);\n \tif (is_12b_check(off, insn))\n \t\treturn -1;\n@@ -1028,12 +998,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \tint i, ret, offset;\n \tint *branches_off = NULL;\n \tint stack_size = 0, nr_arg_slots = 0;\n-\tint retval_off, args_off, func_meta_off, ip_off, run_ctx_off, sreg_off, stk_arg_off;\n+\tint retval_off, args_off, func_meta_off, ip_off;\n+\tint run_ctx_off, sreg_off, stk_arg_off, tcc_off;\n \tint cookie_off, cookie_cnt;\n \tstruct bpf_tramp_nodes *fentry = \u0026tnodes[BPF_TRAMP_FENTRY];\n \tstruct bpf_tramp_nodes *fexit = \u0026tnodes[BPF_TRAMP_FEXIT];\n \tstruct bpf_tramp_nodes *fmod_ret = \u0026tnodes[BPF_TRAMP_MODIFY_RETURN];\n-\tbool is_struct_ops = flags \u0026 BPF_TRAMP_F_INDIRECT;\n+\tbool is_struct_ops = is_struct_ops_tramp(fentry);\n \tvoid *orig_call = func_addr;\n \tbool save_ret;\n \tu64 func_meta;\n@@ -1079,6 +1050,8 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \t *\n \t * FP - sreg_off    [ callee saved reg\t]\n \t *\n+\t * FP - tcc_off     [ tail call count\t] BPF_TRAMP_F_TAIL_CALL_CTX\n+\t *\n \t *\t\t    [ pads              ] pads for 16 bytes alignment\n \t *\n \t *\t\t    [ stack_argN        ]\n@@ -1126,6 +1099,11 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \tstack_size += 8;\n \tsreg_off = stack_size;\n \n+\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX) {\n+\t\tstack_size += 8;\n+\t\ttcc_off = stack_size;\n+\t}\n+\n \tif ((flags \u0026 BPF_TRAMP_F_CALL_ORIG) \u0026\u0026 (nr_arg_slots - RV_MAX_REG_ARGS \u003e 0))\n \t\tstack_size += (nr_arg_slots - RV_MAX_REG_ARGS) * 8;\n \n@@ -1160,6 +1138,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \t\temit_addi(RV_REG_FP, RV_REG_SP, stack_size, ctx);\n \t}\n \n+\t/* store tail call count */\n+\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX)\n+\t\temit_sd(RV_REG_FP, -tcc_off, RV_REG_TCC, ctx);\n+\n \t/* callee saved register S1 to pass start time */\n \temit_sd(RV_REG_FP, -sreg_off, RV_REG_S1, ctx);\n \n@@ -1195,7 +1177,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \t}\n \n \tif (fmod_ret-\u003enr_nodes) {\n-\t\tbranches_off = kzalloc_objs(int, fmod_ret-\u003enr_nodes);\n+\t\tbranches_off = kvzalloc_objs(int, fmod_ret-\u003enr_nodes);\n \t\tif (!branches_off)\n \t\t\treturn -ENOMEM;\n \n@@ -1218,9 +1200,15 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \t\torig_call += RV_FENTRY_NINSNS * 4;\n \t\trestore_args(min_t(int, nr_arg_slots, RV_MAX_REG_ARGS), args_off, ctx);\n \t\trestore_stack_args(nr_arg_slots - RV_MAX_REG_ARGS, args_off, stk_arg_off, ctx);\n+\t\t/* restore TCC to RV_REG_TCC before calling the orig bpf func */\n+\t\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX)\n+\t\t\temit_ld(RV_REG_TCC, -tcc_off, RV_REG_FP, ctx);\n \t\tret = emit_call((const u64)orig_call, true, ctx);\n \t\tif (ret)\n \t\t\tgoto out;\n+\t\t/* store updated TCC back to stack after calling the orig bpf func */\n+\t\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX)\n+\t\t\temit_sd(RV_REG_FP, -tcc_off, RV_REG_TCC, ctx);\n \t\temit_sd(RV_REG_FP, -retval_off, RV_REG_A0, ctx);\n \t\temit_sd(RV_REG_FP, -(retval_off - 8), regmap[BPF_REG_0], ctx);\n \t\tim-\u003eip_after_call = ctx-\u003ero_insns + ctx-\u003eninsns;\n@@ -1273,6 +1261,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \n \temit_ld(RV_REG_S1, -sreg_off, RV_REG_FP, ctx);\n \n+\t/* restore TCC from stack to RV_REG_TCC */\n+\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX)\n+\t\temit_ld(RV_REG_TCC, -tcc_off, RV_REG_FP, ctx);\n+\n \tif (!is_struct_ops) {\n \t\t/* trampoline called from function entry */\n \t\temit_ld(RV_REG_T0, stack_size - 8, RV_REG_SP, ctx);\n@@ -1300,7 +1292,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,\n \n \tret = ctx-\u003eninsns;\n out:\n-\tkfree(branches_off);\n+\tkvfree(branches_off);\n \treturn ret;\n }\n \n@@ -1837,10 +1829,18 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,\n \t\t\t}\n \t\t}\n \n+\t\t/* restore TCC to RV_REG_TCC before bpf2bpf call */\n+\t\tif (aux-\u003etail_call_reachable \u0026\u0026 insn-\u003esrc_reg == BPF_PSEUDO_CALL)\n+\t\t\temit_ld(RV_REG_TCC, ctx-\u003etcc_offset, RV_REG_SP, ctx);\n+\n \t\tret = emit_call(addr, fixed_addr, ctx);\n \t\tif (ret)\n \t\t\treturn ret;\n \n+\t\t/* store updated TCC back to stack after bpf2bpf call */\n+\t\tif (aux-\u003etail_call_reachable \u0026\u0026 insn-\u003esrc_reg == BPF_PSEUDO_CALL)\n+\t\t\temit_sd(RV_REG_SP, ctx-\u003etcc_offset, RV_REG_TCC, ctx);\n+\n \t\tif (insn-\u003esrc_reg != BPF_PSEUDO_CALL)\n \t\t\temit_mv(bpf_to_rv_reg(BPF_REG_0, ctx), RV_REG_A0, ctx);\n \t\tbreak;\n@@ -2020,10 +2020,9 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)\n \t\tstack_adjust += 8;\n \tif (seen_reg(RV_REG_S5, ctx))\n \t\tstack_adjust += 8;\n-\tif (seen_reg(RV_REG_S6, ctx))\n-\t\tstack_adjust += 8;\n \tif (ctx-\u003earena_vm_start)\n \t\tstack_adjust += 8;\n+\tstack_adjust += 8; /* RV_REG_TCC */\n \n \tstack_adjust = round_up(stack_adjust, STACK_ALIGN);\n \tstack_adjust += bpf_stack_adjust;\n@@ -2033,15 +2032,16 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)\n \t/* emit kcfi type preamble immediately before the  first insn */\n \temit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);\n \n+\t/* bpf prog starts here as kcfi skipped during prog-\u003ebpf_func setup */\n+\n \t/* nops reserved for auipc+jalr pair */\n \tfor (i = 0; i \u003c RV_FENTRY_NINSNS; i++)\n \t\temit(rv_nop(), ctx);\n \n-\t/* First instruction is always setting the tail-call-counter\n-\t * (TCC) register. This instruction is skipped for tail calls.\n-\t * Force using a 4-byte (non-compressed) instruction.\n-\t */\n-\temit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);\n+\tif (!is_subprog)\n+\t\temit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);\n+\n+\t/* tailcall starts here, emit insn before it must be fixed */\n \n \temit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);\n \n@@ -2071,26 +2071,20 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)\n \t\temit_sd(RV_REG_SP, store_offset, RV_REG_S5, ctx);\n \t\tstore_offset -= 8;\n \t}\n-\tif (seen_reg(RV_REG_S6, ctx)) {\n-\t\temit_sd(RV_REG_SP, store_offset, RV_REG_S6, ctx);\n-\t\tstore_offset -= 8;\n-\t}\n \tif (ctx-\u003earena_vm_start) {\n \t\temit_sd(RV_REG_SP, store_offset, RV_REG_ARENA, ctx);\n \t\tstore_offset -= 8;\n \t}\n \n+\t/* store TCC from RV_REG_TCC to stack */\n+\temit_sd(RV_REG_SP, store_offset, RV_REG_TCC, ctx);\n+\tctx-\u003etcc_offset = store_offset;\n+\n \temit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);\n \n \tif (bpf_stack_adjust)\n \t\temit_addi(RV_REG_S5, RV_REG_SP, bpf_stack_adjust, ctx);\n \n-\t/* Program contains calls and tail calls, so RV_REG_TCC need\n-\t * to be saved across calls.\n-\t */\n-\tif (seen_tail_call(ctx) \u0026\u0026 seen_call(ctx))\n-\t\temit_mv(RV_REG_TCC_SAVED, RV_REG_TCC, ctx);\n-\n \tctx-\u003estack_size = stack_adjust;\n \n \tif (ctx-\u003earena_vm_start)\n@@ -2157,3 +2151,8 @@ bool bpf_jit_supports_fsession(void)\n {\n \treturn true;\n }\n+\n+bool bpf_jit_supports_subprog_tailcalls(void)\n+{\n+\treturn true;\n+}\ndiff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c\nindex ce3bd3762e08cc..cbfcd287ea1668 100644\n--- a/arch/riscv/net/bpf_jit_core.c\n+++ b/arch/riscv/net/bpf_jit_core.c\n@@ -72,7 +72,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr\n \tctx-\u003earena_vm_start = bpf_arena_get_kern_vm_start(prog-\u003eaux-\u003earena);\n \tctx-\u003euser_vm_start = bpf_arena_get_user_vm_start(prog-\u003eaux-\u003earena);\n \tctx-\u003eprog = prog;\n-\tctx-\u003eoffset = kzalloc_objs(int, prog-\u003elen);\n+\tctx-\u003eoffset = kvzalloc_objs(int, prog-\u003elen);\n \tif (!ctx-\u003eoffset)\n \t\tgoto out_offset;\n \n@@ -170,7 +170,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr\n \t\t\tctx-\u003eoffset[i] = ninsns_rvoff(ctx-\u003eoffset[i]);\n \t\tbpf_prog_fill_jited_linfo(prog, ctx-\u003eoffset);\n out_offset:\n-\t\tkfree(ctx-\u003eoffset);\n+\t\tkvfree(ctx-\u003eoffset);\n \t\tkfree(jit_data);\n \t\tprog-\u003eaux-\u003ejit_data = NULL;\n \t}\n@@ -234,6 +234,7 @@ void bpf_jit_free(struct bpf_prog *prog)\n \t\t */\n \t\tif (jit_data) {\n \t\t\tbpf_jit_binary_pack_finalize(jit_data-\u003ero_header, jit_data-\u003eheader);\n+\t\t\tkvfree(jit_data-\u003ectx.offset);\n \t\t\tkfree(jit_data);\n \t\t}\n \t\thdr = bpf_jit_binary_pack_hdr(prog);\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex adf53f7edf2873..70aad8d7d84fb2 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -2196,6 +2196,12 @@ static inline bool is_tracing_multi(enum bpf_attach_type type)\n \t       type == BPF_TRACE_FSESSION_MULTI;\n }\n \n+static inline bool is_struct_ops_tramp(const struct bpf_tramp_nodes *fentry_nodes)\n+{\n+\treturn fentry_nodes-\u003enr_nodes == 1 \u0026\u0026\n+\t       fentry_nodes-\u003enodes[0]-\u003elink-\u003etype == BPF_LINK_TYPE_STRUCT_OPS;\n+}\n+\n #if defined(CONFIG_BPF_JIT) \u0026\u0026 defined(CONFIG_BPF_SYSCALL)\n /* This macro helps developer to register a struct_ops type and generate\n  * type information correctly. Developers should use this macro to register\ndiff --git a/tools/testing/selftests/bpf/DENYLIST.riscv64 b/tools/testing/selftests/bpf/DENYLIST.riscv64\nindex 4fc4dfdde29375..ca1beae7fe8f10 100644\n--- a/tools/testing/selftests/bpf/DENYLIST.riscv64\n+++ b/tools/testing/selftests/bpf/DENYLIST.riscv64\n@@ -1,3 +1,2 @@\n # riscv64 deny list for BPF CI and local vmtest\n exceptions\t\t\t\t\t# JIT does not support exceptions\n-tailcalls/tailcall_bpf2bpf*\t\t\t# JIT does not support mixing bpf2bpf and tailcalls\n"
}

2/1 2026/07/08 17:15 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "emit_bpf_tail_call",
    "__arch_prepare_bpf_trampoline",
    "bpf_jit_build_prologue",
    "__build_epilogue"
  ],
  "Reasoning": "The patch implements support for mixing bpf2bpf calls and tailcalls in the RISC-V BPF JIT compiler. It modifies core JIT functions like prologue/epilogue generation, tail call emission, and trampoline preparation. These are functional changes to reachable kernel code that should be fuzzed to ensure correctness of the JIT compiler.",
  "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).

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.
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.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. 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 fdc44c1b41e64f09d930a6d3578bb3a920ea1fc2
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Jul 8 17:15:41 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index b0075ece4a6e7f..36337c3dad8513 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -2544,12 +2544,6 @@ static void restore_args(struct jit_ctx *ctx, int bargs_off, int nregs)
 	}
 }
 
-static bool is_struct_ops_tramp(const struct bpf_tramp_nodes *fentry_nodes)
-{
-	return fentry_nodes->nr_nodes == 1 &&
-		fentry_nodes->nodes[0]->link->type == BPF_LINK_TYPE_STRUCT_OPS;
-}
-
 static void store_func_meta(struct jit_ctx *ctx, u64 func_meta, int func_meta_off)
 {
 	emit_a64_mov_i64(A64_R(10), func_meta, ctx);
diff --git a/arch/riscv/net/bpf_jit.h b/arch/riscv/net/bpf_jit.h
index da027179024421..419b9d795f2a7a 100644
--- a/arch/riscv/net/bpf_jit.h
+++ b/arch/riscv/net/bpf_jit.h
@@ -81,6 +81,7 @@ struct rv_jit_context {
 	int ex_jmp_off;
 	unsigned long flags;
 	int stack_size;
+	int tcc_offset;
 	u64 arena_vm_start;
 	u64 user_vm_start;
 };
diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index f9d5347ba966b7..823262ca47ebf7 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -18,12 +18,12 @@
 #define RV_MAX_REG_ARGS 8
 #define RV_FENTRY_NINSNS 2
 #define RV_FENTRY_NBYTES (RV_FENTRY_NINSNS * 4)
-#define RV_KCFI_NINSNS (IS_ENABLED(CONFIG_CFI) ? 1 : 0)
 /* imm that allows emit_imm to emit max count insns */
 #define RV_MAX_COUNT_IMM 0x7FFF7FF7FF7FF7FF
+/* fentry and TCC init insns will be skipped on tailcall */
+#define RV_TAILCALL_OFFSET ((RV_FENTRY_NINSNS + 1) * 4)
 
 #define RV_REG_TCC RV_REG_A6
-#define RV_REG_TCC_SAVED RV_REG_S6 /* Store A6 in S6 if program do calls */
 #define RV_REG_ARENA RV_REG_S7 /* For storing arena_vm_start */
 
 static const int regmap[] = {
@@ -57,14 +57,12 @@ static const int pt_regmap[] = {
 };
 
 enum {
-	RV_CTX_F_SEEN_TAIL_CALL =	0,
 	RV_CTX_F_SEEN_CALL =		RV_REG_RA,
 	RV_CTX_F_SEEN_S1 =		RV_REG_S1,
 	RV_CTX_F_SEEN_S2 =		RV_REG_S2,
 	RV_CTX_F_SEEN_S3 =		RV_REG_S3,
 	RV_CTX_F_SEEN_S4 =		RV_REG_S4,
 	RV_CTX_F_SEEN_S5 =		RV_REG_S5,
-	RV_CTX_F_SEEN_S6 =		RV_REG_S6,
 };
 
 static u8 bpf_to_rv_reg(int bpf_reg, struct rv_jit_context *ctx)
@@ -77,7 +75,6 @@ static u8 bpf_to_rv_reg(int bpf_reg, struct rv_jit_context *ctx)
 	case RV_CTX_F_SEEN_S3:
 	case RV_CTX_F_SEEN_S4:
 	case RV_CTX_F_SEEN_S5:
-	case RV_CTX_F_SEEN_S6:
 		__set_bit(reg, &ctx->flags);
 	}
 	return reg;
@@ -92,7 +89,6 @@ static bool seen_reg(int reg, struct rv_jit_context *ctx)
 	case RV_CTX_F_SEEN_S3:
 	case RV_CTX_F_SEEN_S4:
 	case RV_CTX_F_SEEN_S5:
-	case RV_CTX_F_SEEN_S6:
 		return test_bit(reg, &ctx->flags);
 	}
 	return false;
@@ -108,32 +104,6 @@ static void mark_call(struct rv_jit_context *ctx)
 	__set_bit(RV_CTX_F_SEEN_CALL, &ctx->flags);
 }
 
-static bool seen_call(struct rv_jit_context *ctx)
-{
-	return test_bit(RV_CTX_F_SEEN_CALL, &ctx->flags);
-}
-
-static void mark_tail_call(struct rv_jit_context *ctx)
-{
-	__set_bit(RV_CTX_F_SEEN_TAIL_CALL, &ctx->flags);
-}
-
-static bool seen_tail_call(struct rv_jit_context *ctx)
-{
-	return test_bit(RV_CTX_F_SEEN_TAIL_CALL, &ctx->flags);
-}
-
-static u8 rv_tail_call_reg(struct rv_jit_context *ctx)
-{
-	mark_tail_call(ctx);
-
-	if (seen_call(ctx)) {
-		__set_bit(RV_CTX_F_SEEN_S6, &ctx->flags);
-		return RV_REG_S6;
-	}
-	return RV_REG_A6;
-}
-
 static bool is_32b_int(s64 val)
 {
 	return -(1L << 31) <= val && val < (1L << 31);
@@ -258,23 +228,20 @@ static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
 		emit_ld(RV_REG_S5, store_offset, RV_REG_SP, ctx);
 		store_offset -= 8;
 	}
-	if (seen_reg(RV_REG_S6, ctx)) {
-		emit_ld(RV_REG_S6, store_offset, RV_REG_SP, ctx);
-		store_offset -= 8;
-	}
 	if (ctx->arena_vm_start) {
 		emit_ld(RV_REG_ARENA, store_offset, RV_REG_SP, ctx);
 		store_offset -= 8;
 	}
 
+	/* restore TCC from stack to RV_REG_TCC */
+	emit_ld(RV_REG_TCC, ctx->tcc_offset, RV_REG_SP, ctx);
+
 	emit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);
 	/* Set return value. */
 	if (!is_tail_call)
 		emit_addiw(RV_REG_A0, RV_REG_A5, 0, ctx);
 	emit_jalr(RV_REG_ZERO, is_tail_call ? RV_REG_T3 : RV_REG_RA,
-		  /* kcfi, fentry and TCC init insns will be skipped on tailcall */
-		  is_tail_call ? (RV_KCFI_NINSNS + RV_FENTRY_NINSNS + 1) * 4 : 0,
-		  ctx);
+		  is_tail_call ? RV_TAILCALL_OFFSET : 0, ctx);
 }
 
 static void emit_bcc(u8 cond, u8 rd, u8 rs, int rvoff,
@@ -355,7 +322,6 @@ static void emit_branch(u8 cond, u8 rd, u8 rs, int rvoff,
 static int emit_bpf_tail_call(int insn, struct rv_jit_context *ctx)
 {
 	int tc_ninsn, off, start_insn = ctx->ninsns;
-	u8 tcc = rv_tail_call_reg(ctx);
 
 	/* a0: &ctx
 	 * a1: &array
@@ -378,7 +344,8 @@ static int emit_bpf_tail_call(int insn, struct rv_jit_context *ctx)
 	/* if (--TCC < 0)
 	 *     goto out;
 	 */
-	emit_addi(RV_REG_TCC, tcc, -1, ctx);
+	emit_ld(RV_REG_TCC, ctx->tcc_offset, RV_REG_SP, ctx);
+	emit_addi(RV_REG_TCC, RV_REG_TCC, -1, ctx);
 	off = ninsns_rvoff(tc_ninsn - (ctx->ninsns - start_insn));
 	emit_branch(BPF_JSLT, RV_REG_TCC, RV_REG_ZERO, off, ctx);
 
@@ -394,7 +361,10 @@ static int emit_bpf_tail_call(int insn, struct rv_jit_context *ctx)
 	off = ninsns_rvoff(tc_ninsn - (ctx->ninsns - start_insn));
 	emit_branch(BPF_JEQ, RV_REG_T2, RV_REG_ZERO, off, ctx);
 
-	/* goto *(prog->bpf_func + 4); */
+	/* store updated TCC back to stack */
+	emit_sd(RV_REG_SP, ctx->tcc_offset, RV_REG_TCC, ctx);
+
+	/* goto *(prog->bpf_func + RV_TAILCALL_OFFSET); */
 	off = offsetof(struct bpf_prog, bpf_func);
 	if (is_12b_check(off, insn))
 		return -1;
@@ -1028,12 +998,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 	int i, ret, offset;
 	int *branches_off = NULL;
 	int stack_size = 0, nr_arg_slots = 0;
-	int retval_off, args_off, func_meta_off, ip_off, run_ctx_off, sreg_off, stk_arg_off;
+	int retval_off, args_off, func_meta_off, ip_off;
+	int run_ctx_off, sreg_off, stk_arg_off, tcc_off;
 	int cookie_off, cookie_cnt;
 	struct bpf_tramp_nodes *fentry = &tnodes[BPF_TRAMP_FENTRY];
 	struct bpf_tramp_nodes *fexit = &tnodes[BPF_TRAMP_FEXIT];
 	struct bpf_tramp_nodes *fmod_ret = &tnodes[BPF_TRAMP_MODIFY_RETURN];
-	bool is_struct_ops = flags & BPF_TRAMP_F_INDIRECT;
+	bool is_struct_ops = is_struct_ops_tramp(fentry);
 	void *orig_call = func_addr;
 	bool save_ret;
 	u64 func_meta;
@@ -1079,6 +1050,8 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 	 *
 	 * FP - sreg_off    [ callee saved reg	]
 	 *
+	 * FP - tcc_off     [ tail call count	] BPF_TRAMP_F_TAIL_CALL_CTX
+	 *
 	 *		    [ pads              ] pads for 16 bytes alignment
 	 *
 	 *		    [ stack_argN        ]
@@ -1126,6 +1099,11 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 	stack_size += 8;
 	sreg_off = stack_size;
 
+	if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
+		stack_size += 8;
+		tcc_off = stack_size;
+	}
+
 	if ((flags & BPF_TRAMP_F_CALL_ORIG) && (nr_arg_slots - RV_MAX_REG_ARGS > 0))
 		stack_size += (nr_arg_slots - RV_MAX_REG_ARGS) * 8;
 
@@ -1160,6 +1138,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 		emit_addi(RV_REG_FP, RV_REG_SP, stack_size, ctx);
 	}
 
+	/* store tail call count */
+	if (flags & BPF_TRAMP_F_TAIL_CALL_CTX)
+		emit_sd(RV_REG_FP, -tcc_off, RV_REG_TCC, ctx);
+
 	/* callee saved register S1 to pass start time */
 	emit_sd(RV_REG_FP, -sreg_off, RV_REG_S1, ctx);
 
@@ -1195,7 +1177,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 	}
 
 	if (fmod_ret->nr_nodes) {
-		branches_off = kzalloc_objs(int, fmod_ret->nr_nodes);
+		branches_off = kvzalloc_objs(int, fmod_ret->nr_nodes);
 		if (!branches_off)
 			return -ENOMEM;
 
@@ -1218,9 +1200,15 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 		orig_call += RV_FENTRY_NINSNS * 4;
 		restore_args(min_t(int, nr_arg_slots, RV_MAX_REG_ARGS), args_off, ctx);
 		restore_stack_args(nr_arg_slots - RV_MAX_REG_ARGS, args_off, stk_arg_off, ctx);
+		/* restore TCC to RV_REG_TCC before calling the orig bpf func */
+		if (flags & BPF_TRAMP_F_TAIL_CALL_CTX)
+			emit_ld(RV_REG_TCC, -tcc_off, RV_REG_FP, ctx);
 		ret = emit_call((const u64)orig_call, true, ctx);
 		if (ret)
 			goto out;
+		/* store updated TCC back to stack after calling the orig bpf func */
+		if (flags & BPF_TRAMP_F_TAIL_CALL_CTX)
+			emit_sd(RV_REG_FP, -tcc_off, RV_REG_TCC, ctx);
 		emit_sd(RV_REG_FP, -retval_off, RV_REG_A0, ctx);
 		emit_sd(RV_REG_FP, -(retval_off - 8), regmap[BPF_REG_0], ctx);
 		im->ip_after_call = ctx->ro_insns + ctx->ninsns;
@@ -1273,6 +1261,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 
 	emit_ld(RV_REG_S1, -sreg_off, RV_REG_FP, ctx);
 
+	/* restore TCC from stack to RV_REG_TCC */
+	if (flags & BPF_TRAMP_F_TAIL_CALL_CTX)
+		emit_ld(RV_REG_TCC, -tcc_off, RV_REG_FP, ctx);
+
 	if (!is_struct_ops) {
 		/* trampoline called from function entry */
 		emit_ld(RV_REG_T0, stack_size - 8, RV_REG_SP, ctx);
@@ -1300,7 +1292,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
 
 	ret = ctx->ninsns;
 out:
-	kfree(branches_off);
+	kvfree(branches_off);
 	return ret;
 }
 
@@ -1837,10 +1829,18 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 			}
 		}
 
+		/* restore TCC to RV_REG_TCC before bpf2bpf call */
+		if (aux->tail_call_reachable && insn->src_reg == BPF_PSEUDO_CALL)
+			emit_ld(RV_REG_TCC, ctx->tcc_offset, RV_REG_SP, ctx);
+
 		ret = emit_call(addr, fixed_addr, ctx);
 		if (ret)
 			return ret;
 
+		/* store updated TCC back to stack after bpf2bpf call */
+		if (aux->tail_call_reachable && insn->src_reg == BPF_PSEUDO_CALL)
+			emit_sd(RV_REG_SP, ctx->tcc_offset, RV_REG_TCC, ctx);
+
 		if (insn->src_reg != BPF_PSEUDO_CALL)
 			emit_mv(bpf_to_rv_reg(BPF_REG_0, ctx), RV_REG_A0, ctx);
 		break;
@@ -2020,10 +2020,9 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
 		stack_adjust += 8;
 	if (seen_reg(RV_REG_S5, ctx))
 		stack_adjust += 8;
-	if (seen_reg(RV_REG_S6, ctx))
-		stack_adjust += 8;
 	if (ctx->arena_vm_start)
 		stack_adjust += 8;
+	stack_adjust += 8; /* RV_REG_TCC */
 
 	stack_adjust = round_up(stack_adjust, STACK_ALIGN);
 	stack_adjust += bpf_stack_adjust;
@@ -2033,15 +2032,16 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
 	/* emit kcfi type preamble immediately before the  first insn */
 	emit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);
 
+	/* bpf prog starts here as kcfi skipped during prog->bpf_func setup */
+
 	/* nops reserved for auipc+jalr pair */
 	for (i = 0; i < RV_FENTRY_NINSNS; i++)
 		emit(rv_nop(), ctx);
 
-	/* First instruction is always setting the tail-call-counter
-	 * (TCC) register. This instruction is skipped for tail calls.
-	 * Force using a 4-byte (non-compressed) instruction.
-	 */
-	emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
+	if (!is_subprog)
+		emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
+
+	/* tailcall starts here, emit insn before it must be fixed */
 
 	emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
 
@@ -2071,26 +2071,20 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
 		emit_sd(RV_REG_SP, store_offset, RV_REG_S5, ctx);
 		store_offset -= 8;
 	}
-	if (seen_reg(RV_REG_S6, ctx)) {
-		emit_sd(RV_REG_SP, store_offset, RV_REG_S6, ctx);
-		store_offset -= 8;
-	}
 	if (ctx->arena_vm_start) {
 		emit_sd(RV_REG_SP, store_offset, RV_REG_ARENA, ctx);
 		store_offset -= 8;
 	}
 
+	/* store TCC from RV_REG_TCC to stack */
+	emit_sd(RV_REG_SP, store_offset, RV_REG_TCC, ctx);
+	ctx->tcc_offset = store_offset;
+
 	emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
 
 	if (bpf_stack_adjust)
 		emit_addi(RV_REG_S5, RV_REG_SP, bpf_stack_adjust, ctx);
 
-	/* Program contains calls and tail calls, so RV_REG_TCC need
-	 * to be saved across calls.
-	 */
-	if (seen_tail_call(ctx) && seen_call(ctx))
-		emit_mv(RV_REG_TCC_SAVED, RV_REG_TCC, ctx);
-
 	ctx->stack_size = stack_adjust;
 
 	if (ctx->arena_vm_start)
@@ -2157,3 +2151,8 @@ bool bpf_jit_supports_fsession(void)
 {
 	return true;
 }
+
+bool bpf_jit_supports_subprog_tailcalls(void)
+{
+	return true;
+}
diff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c
index ce3bd3762e08cc..cbfcd287ea1668 100644
--- a/arch/riscv/net/bpf_jit_core.c
+++ b/arch/riscv/net/bpf_jit_core.c
@@ -72,7 +72,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
 	ctx->arena_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena);
 	ctx->user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena);
 	ctx->prog = prog;
-	ctx->offset = kzalloc_objs(int, prog->len);
+	ctx->offset = kvzalloc_objs(int, prog->len);
 	if (!ctx->offset)
 		goto out_offset;
 
@@ -170,7 +170,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
 			ctx->offset[i] = ninsns_rvoff(ctx->offset[i]);
 		bpf_prog_fill_jited_linfo(prog, ctx->offset);
 out_offset:
-		kfree(ctx->offset);
+		kvfree(ctx->offset);
 		kfree(jit_data);
 		prog->aux->jit_data = NULL;
 	}
@@ -234,6 +234,7 @@ void bpf_jit_free(struct bpf_prog *prog)
 		 */
 		if (jit_data) {
 			bpf_jit_binary_pack_finalize(jit_data->ro_header, jit_data->header);
+			kvfree(jit_data->ctx.offset);
 			kfree(jit_data);
 		}
 		hdr = bpf_jit_binary_pack_hdr(prog);
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index adf53f7edf2873..70aad8d7d84fb2 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2196,6 +2196,12 @@ static inline bool is_tracing_multi(enum bpf_attach_type type)
 	       type == BPF_TRACE_FSESSION_MULTI;
 }
 
+static inline bool is_struct_ops_tramp(const struct bpf_tramp_nodes *fentry_nodes)
+{
+	return fentry_nodes->nr_nodes == 1 &&
+	       fentry_nodes->nodes[0]->link->type == BPF_LINK_TYPE_STRUCT_OPS;
+}
+
 #if defined(CONFIG_BPF_JIT) && defined(CONFIG_BPF_SYSCALL)
 /* This macro helps developer to register a struct_ops type and generate
  * type information correctly. Developers should use this macro to register
diff --git a/tools/testing/selftests/bpf/DENYLIST.riscv64 b/tools/testing/selftests/bpf/DENYLIST.riscv64
index 4fc4dfdde29375..ca1beae7fe8f10 100644
--- a/tools/testing/selftests/bpf/DENYLIST.riscv64
+++ b/tools/testing/selftests/bpf/DENYLIST.riscv64
@@ -1,3 +1,2 @@
 # riscv64 deny list for BPF CI and local vmtest
 exceptions					# JIT does not support exceptions
-tailcalls/tailcall_bpf2bpf*			# JIT does not support mixing bpf2bpf and tailcalls

3/2 2026/07/08 17:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 8374
							output: 32
							thoughts: 478
						

Thoughts:
**Fuzzing Feasibility of the RISC-V BPF JIT Patch**

Okay, so there's a patch to enable mixing bpf2bpf and tailcalls on RISC-V. It touches a couple of files: `arch/riscv/net/bpf_jit_comp64.c` and `arch/riscv/net/bpf_jit_core.c`, plus it moves `is_struct_ops_tramp` to `include/linux/bpf.h`. The core of this is enabling `bpf_jit_supports_subprog_tailcalls` on RISC-V. My immediate thought is, "Worth fuzzing?"

The patch *definitely* modifies the RISC-V BPF JIT compiler, which is a key area. Now, syzkaller is the go-to fuzzer, and I know it's primarily x86_64 focused, but it *can* fuzz arm64 and RISC-V if properly configured. The big question is: is this reachable in a virtualized environment? Yes, the BPF JIT is, so that's good.

Now, does syzkaller *actually* fuzz riscv64? A quick check confirms that it does, which removes a major hurdle. 

The prompt wants me to verify the code is *executable* using code search tools, so let's check the modified functions in those files, to see if they are part of a fuzzer target:

In `arch/riscv/net/bpf_jit_comp64.c`: I see `__build_epilogue`, `emit_bpf_tail_call`, `__arch_prepare_bpf_trampoline`, `bpf_jit_emit_insn`, `bpf_jit_build_prologue`, and `bpf_jit_supports_subprog_tailcalls`.

And in `arch/riscv/net/bpf_jit_core.c`: I see `bpf_int_jit_compile` and `bpf_jit_free`.

Now, I'll quickly check the config options, just to be sure we have the full picture. `CONFIG_BPF_JIT` is the big one, but I'll double-check for any RISC-V specific config knobs. This all looks like a good candidate for fuzzing.



4/2 2026/07/08 17:15 tool grepper
0m Args:
{
  "Expression": "CONFIG_BPF_JIT",
  "PathPrefix": "arch/riscv/"
}

Results:
{
  "Output": "arch/riscv/include/asm/extable.h=38=static inline bool fixup_exception(struct pt_regs *regs) { return false; }\n--\narch/riscv/include/asm/extable.h-40-\narch/riscv/include/asm/extable.h:41:#if defined(CONFIG_BPF_JIT) \u0026\u0026 defined(CONFIG_ARCH_RV64I)\narch/riscv/include/asm/extable.h-42-bool ex_handler_bpf(const struct exception_table_entry *ex, struct pt_regs *regs);\n--\narch/riscv/net/Makefile-2-\narch/riscv/net/Makefile:3:obj-$(CONFIG_BPF_JIT) += bpf_jit_core.o\narch/riscv/net/Makefile-4-\narch/riscv/net/Makefile=5=ifeq ($(CONFIG_ARCH_RV64I),y)\narch/riscv/net/Makefile:6:\tobj-$(CONFIG_BPF_JIT) += bpf_jit_comp64.o\narch/riscv/net/Makefile-7-else\narch/riscv/net/Makefile:8:\tobj-$(CONFIG_BPF_JIT) += bpf_jit_comp32.o\narch/riscv/net/Makefile-9-endif\n"
}

5/2 2026/07/08 17:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 9737
							output: 32
							thoughts: 196
						

Thoughts:
**Analyzing the RISC-V BPF JIT Compiler**

Okay, so I'm looking at the RISC-V BPF JIT compiler, specifically with the `BPF_JIT` config enabled. My focus is on understanding the core compilation flow and identifying key areas of interest. Let's see, what functions stand out as being critical to the process... Well, first, I need to look at: `__build_epilogue`, `emit_bpf_tail_call`, `__arch_prepare_bpf_trampoline`, `bpf_jit_emit_insn`, and `bpf_jit_build_prologue`.

Hmm, `bpf_jit_emit_insn`... while it *is* generic in name, it's clearly specific to the RISC-V BPF JIT implementation. That's good to keep in mind. Now, `emit_bpf_tail_call` looks promising, that's definitely a core functionality that needs to be understood. Also, I need to check out `__arch_prepare_bpf_trampoline`. And of course `bpf_jit_build_prologue` seems important, too. And then there's `bpf_int_jit_compile`—that's the entry point, the start of the whole compilation process.

Now, let me see... I wonder if the `bpf_jit_supports_subprog_tailcalls` function is actually used anywhere in this process. That's a good place to start next.



6/2 2026/07/08 17:16 tool grepper
0m Args:
{
  "Expression": "bpf_jit_supports_subprog_tailcalls",
  "PathPrefix": ""
}

Results:
{
  "Output": "arch/arm64/net/bpf_jit_comp.c=2327=u64 bpf_jit_alloc_exec_limit(void)\n--\narch/arm64/net/bpf_jit_comp.c-2332-/* Indicate the JIT backend supports mixing bpf2bpf and tailcalls. */\narch/arm64/net/bpf_jit_comp.c:2333:bool bpf_jit_supports_subprog_tailcalls(void)\narch/arm64/net/bpf_jit_comp.c-2334-{\n--\narch/loongarch/net/bpf_jit.c=2389=bool bpf_jit_supports_fsession(void)\n--\narch/loongarch/net/bpf_jit.c-2394-/* Indicate the JIT backend supports mixing bpf2bpf and tailcalls. */\narch/loongarch/net/bpf_jit.c:2395:bool bpf_jit_supports_subprog_tailcalls(void)\narch/loongarch/net/bpf_jit.c-2396-{\n--\narch/powerpc/net/bpf_jit_comp.c=510=bool bpf_jit_supports_exceptions(void)\n--\narch/powerpc/net/bpf_jit_comp.c-514-\narch/powerpc/net/bpf_jit_comp.c:515:bool bpf_jit_supports_subprog_tailcalls(void)\narch/powerpc/net/bpf_jit_comp.c-516-{\n--\narch/riscv/net/bpf_jit_comp64.c=2150=bool bpf_jit_supports_fsession(void)\n--\narch/riscv/net/bpf_jit_comp64.c-2154-\narch/riscv/net/bpf_jit_comp64.c:2155:bool bpf_jit_supports_subprog_tailcalls(void)\narch/riscv/net/bpf_jit_comp64.c-2156-{\n--\narch/s390/net/bpf_jit_comp.c=2981=int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image,\n--\narch/s390/net/bpf_jit_comp.c-3010-\narch/s390/net/bpf_jit_comp.c:3011:bool bpf_jit_supports_subprog_tailcalls(void)\narch/s390/net/bpf_jit_comp.c-3012-{\n--\narch/x86/net/bpf_jit_comp.c=4053=void *bpf_arch_text_copy(void *dst, void *src, size_t len)\n--\narch/x86/net/bpf_jit_comp.c-4060-/* Indicate the JIT backend supports mixing bpf2bpf and tailcalls. */\narch/x86/net/bpf_jit_comp.c:4061:bool bpf_jit_supports_subprog_tailcalls(void)\narch/x86/net/bpf_jit_comp.c-4062-{\n--\ninclude/linux/filter.h=1181=bool bpf_jit_inlines_helper_call(s32 imm);\ninclude/linux/filter.h:1182:bool bpf_jit_supports_subprog_tailcalls(void);\ninclude/linux/filter.h-1183-bool bpf_jit_supports_percpu_insn(void);\n--\nkernel/bpf/core.c=3276=bool __weak bpf_jit_inlines_helper_call(s32 imm)\n--\nkernel/bpf/core.c-3281-/* Return TRUE if the JIT backend supports mixing bpf2bpf and tailcalls. */\nkernel/bpf/core.c:3282:bool __weak bpf_jit_supports_subprog_tailcalls(void)\nkernel/bpf/core.c-3283-{\n--\nkernel/bpf/verifier.c=8491=bool bpf_allow_tail_call_in_subprogs(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-8493-\treturn env-\u003eprog-\u003ejit_requested \u0026\u0026\nkernel/bpf/verifier.c:8494:\t       bpf_jit_supports_subprog_tailcalls();\nkernel/bpf/verifier.c-8495-}\n"
}

7/2 2026/07/08 17:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11462
							output: 162
							thoughts: 184
						

Thoughts:
**RISC-V Tail Calls in Subprograms - A Verification**

Okay, so this patch is enabling `bpf_jit_supports_subprog_tailcalls` for RISC-V, which is a significant step. Basically, we're now allowing tail calls within subprograms on RISC-V. That's good progress.

I need to confirm the changes in `arch/riscv/net/bpf_jit_comp64.c` are functionally sound; I should really get that fuzzed. That's a must.

Now, let me zoom in on the critical symbols here, make sure I understand the flow. I need to pay particular attention to `emit_bpf_tail_call`, `__arch_prepare_bpf_trampoline`, `bpf_jit_build_prologue`, and `__build_epilogue` - these are the building blocks. I need to understand how each piece fits together.

Alright, what about configs? Let's see, `BPF_JIT` and `BPF_SYSCALL` are relevant. But is `BPF_JIT` the only real dependency? A quick check... Yep, it looks like `CONFIG_BPF_JIT` is the main one. That simplifies things a bit. Good.

Right, I'm satisfied. Time to set the results and move forward.



8/2 2026/07/08 17:16 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "emit_bpf_tail_call",
    "__arch_prepare_bpf_trampoline",
    "bpf_jit_build_prologue",
    "__build_epilogue"
  ],
  "Reasoning": "The patch implements support for mixing bpf2bpf calls and tailcalls in the RISC-V BPF JIT compiler. It modifies core JIT functions like prologue/epilogue generation, tail call emission, and trampoline preparation. These are functional changes to reachable kernel code that should be fuzzed to ensure correctness of the JIT compiler.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_JIT",
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "emit_bpf_tail_call",
    "__arch_prepare_bpf_trampoline",
    "bpf_jit_build_prologue",
    "__build_epilogue"
  ],
  "Reasoning": "The patch implements support for mixing bpf2bpf calls and tailcalls in the RISC-V BPF JIT compiler. It modifies core JIT functions like prologue/epilogue generation, tail call emission, and trampoline preparation. These are functional changes to reachable kernel code that should be fuzzed to ensure correctness of the JIT compiler.",
  "WorthFuzzing": true
}

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