From: George Guo The tail call count (TCC) and its pointer occupy the two deepest slots of the callee-saved area set up by build_prologue(). An arena program reserves one extra word for REG_ARENA (arena_vm_start) right above them: ra fp s0 s1 s2 s3 s4 s5 <- 8 words [ REG_ARENA ] <- only if ctx->arena_vm_start tail_call_cnt tail_call_cnt_ptr <- loaded on tail call / bpf2bpf call BPF_TAIL_CALL_CNT_PTR_STACK_OFF() hardcodes the pointer at round_up(stack, 16) - 80, which is only correct when REG_ARENA is absent. For an arena program the extra word shifts every slot below it down by 8 bytes, so the macro resolves to the tail_call_cnt slot (the counter value) instead of tail_call_cnt_ptr. The JIT then loads the counter value and dereferences it as the TCC pointer, corrupting memory or panicking the kernel whenever an arena program performs a tail call or a bpf2bpf call. Replace the macro with a helper that accounts for the REG_ARENA slot, mirroring the reservation logic in build_prologue(). Fixes: ef54c517a937 ("LoongArch: BPF: Implement PROBE_MEM32 pseudo instructions") Cc: stable@vger.kernel.org Signed-off-by: George Guo --- v2: - Dropped the second patch ("Don't charge an empty prog_array slot to the tail call count"); that off-by-one was fixed independently by commit 0379d10f09bc ("LoongArch: BPF: Fix off-by-one error in tail call"), now in 7.2-rc1. The arena tail call count pointer offset bug addressed here is independent and still unfixed. - No code change; reworded the commit message and rebased on 7.2-rc1. - v1: https://lore.kernel.org/all/20260625083212.277417-1-dongtai.guo@linux.dev arch/loongarch/net/bpf_jit.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index ad7e28375aa9..5e34e9e3f508 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -25,7 +25,23 @@ #define REG_TCC LOONGARCH_GPR_A6 #define REG_ARENA LOONGARCH_GPR_S6 /* For storing arena_vm_start */ -#define BPF_TAIL_CALL_CNT_PTR_STACK_OFF(stack) (round_up(stack, 16) - 80) + +static int tail_call_cnt_ptr_stack_off(struct jit_ctx *ctx) +{ + /* Ten words are pushed below the BPF stack: ra, fp, s0-s5, and the + * tail call count plus its pointer, which occupy the two deepest + * slots of the callee-saved area. + */ + int offset = sizeof(long) * 10; + + /* An arena program reserves one extra word above them (REG_ARENA), + * which pushes the tail call count pointer down by one slot. + */ + if (ctx->arena_vm_start) + offset += sizeof(long); + + return round_up(ctx->stack_size, 16) - offset; +} static const int regmap[] = { /* return value from in-kernel function, and exit value for eBPF program */ @@ -291,7 +307,7 @@ bool bpf_jit_supports_far_kfunc_call(void) static int emit_bpf_tail_call(struct jit_ctx *ctx, int insn) { int off, tc_ninsn = 0; - int tcc_ptr_off = BPF_TAIL_CALL_CNT_PTR_STACK_OFF(ctx->stack_size); + int tcc_ptr_off = tail_call_cnt_ptr_stack_off(ctx); u8 a1 = LOONGARCH_GPR_A1; u8 a2 = LOONGARCH_GPR_A2; u8 t1 = LOONGARCH_GPR_T1; @@ -1181,7 +1197,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext return ret; if (insn->src_reg == BPF_PSEUDO_CALL) { - tcc_ptr_off = BPF_TAIL_CALL_CNT_PTR_STACK_OFF(ctx->stack_size); + tcc_ptr_off = tail_call_cnt_ptr_stack_off(ctx); emit_insn(ctx, ldd, REG_TCC, LOONGARCH_GPR_SP, tcc_ptr_off); } -- 2.25.1