[PATCH bpf-next v2] bpf: Fix unaligned interpreter panic on JIT fallback path

From: Tiezhu Yang

Date: Thu Jun 11 2026 - 06:17:08 EST


When an architecture implements bpf_jit_inlines_helper_call(), such
as LoongArch, ARM64, and RISC-V, the BPF verifier skips rewriting
the helper call offset (insn->imm) during the bpf_do_misc_fixups()
phase if the helper is expected to be inlined by the JIT compiler.
As a result, insn->imm remains as the raw helper enum ID.

However, if JIT is disabled at runtime (net.core.bpf_jit_enable=0)
or if the JIT compilation later dynamically fails (e.g., due to OOM
during bpf_jit_alloc_exec()), the core BPF subsystem falls back to
the BPF interpreter.

When the fallback interpreter executes (__bpf_call_base + insn->imm)
with the unpatched raw helper ID, it jumps into an unaligned invalid
address space, triggering a fatal instruction alignment fault (ADEF)
or illegal memory access kernel panic.

This issue impacts all architectures that support helper inlining.
Introduce a late fixup pass via bpf_fixup_fallback_inline_helpers()
in __bpf_prog_select_runtime() to fix it.

When JIT compilation fails or is disabled, the helper call offsets
originally skipped for inlining are rewritten to relative memory
offsets right before transferring control to the interpreter.

A bounds check on insn->imm against __BPF_FUNC_MAX_ID is added to
ensure already-patched address offsets of regular non-inlined helpers
are safely filtered out during traversal.

1. Test case (test_panic.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("kprobe/sys_getpid")
int test_panic(void *ctx)
{
struct task_struct *task;

task = (struct task_struct *)bpf_get_current_task();
if (task)
bpf_printk("Task address: %p\n", task);

return 0;
}

char LICENSE[] SEC("license") = "GPL";

2. Reproduction steps:

$ clang -target bpf -O2 -g -c test_panic.c -o test_panic.o
$ sudo sysctl -w net.core.bpf_jit_enable=0
$ sudo bpftool prog load test_panic.o /sys/fs/bpf/test_panic autoattach
$ sudo cat /sys/kernel/debug/tracing/trace_pipe

3. Panic information on LoongArch:

Kernel ade access[#1]:
...
ra: 9000000000486e50 ___bpf_prog_run+0x1370/0x36b0
ERA: 9000000000485383 __bpf_prog_ret0_warn+0x13/0x20
...
ESTAT: 00080000 [ADEF] (IS= ECode=8 EsubCode=0)

Fixes: 2ddec2c80b44 ("riscv, bpf: inline bpf_get_smp_processor_id()")
Signed-off-by: Tiezhu Yang <yangtiezhu@xxxxxxxxxxx>
---
kernel/bpf/core.c | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)

diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 649cce41e13f..5785c8cb4dbe 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -2608,6 +2608,31 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc
return prog;
}

+/* Fix up helper call offsets for inlined helpers on JIT fallback path. */
+static void bpf_fixup_fallback_inline_helpers(struct bpf_verifier_env *env, struct bpf_prog *fp)
+{
+ struct bpf_insn *insn = fp->insnsi;
+ const struct bpf_func_proto *fn;
+ int i;
+
+ if (!env || !env->ops->get_func_proto)
+ return;
+
+ for (i = 0; i < fp->len; i++, insn++) {
+ if (insn->code == (BPF_JMP | BPF_CALL) && insn->src_reg == 0) {
+ /* Filter out already-patched address offsets. */
+ if (insn->imm >= __BPF_FUNC_MAX_ID)
+ continue;
+
+ if (bpf_jit_inlines_helper_call(insn->imm)) {
+ fn = env->ops->get_func_proto(insn->imm, env->prog);
+ if (fn && fn->func)
+ insn->imm = fn->func - __bpf_call_base;
+ }
+ }
+ }
+}
+
struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp,
int *err)
{
@@ -2643,6 +2668,9 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct
*err = -ENOTSUPP;
return fp;
}
+
+ if (!fp->jited)
+ bpf_fixup_fallback_inline_helpers(env, fp);
} else {
*err = bpf_prog_offload_compile(fp);
if (*err)
--
2.42.0