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

From: Tiezhu Yang

Date: Sun Jun 14 2026 - 22:53:38 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,
so introduce a late fixup pass via bpf_fixup_fallback_helpers() in
__bpf_prog_select_runtime() to fix this panic.

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.

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>
Acked-by: Leon Hwang <leon.hwang@xxxxxxxxx>
Tested-by: Xu Kuohai <xukuohai@xxxxxxxxxx>
---
v4:
- Rename the static function to bpf_fixup_fallback_helpers() to
be more accurate and concise, following the file's naming style.
- Remove redundant helper ID bounds check since invalid helper
IDs are already filtered out in bpf_jit_inlines_helper_call().
- Simplify code by merging nested if statements for helper checks.

kernel/bpf/core.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

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

+/* Fix up helper call offsets on JIT fallback path. */
+static void bpf_fixup_fallback_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 (bpf_helper_call(insn) && 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 +2662,9 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct
*err = -ENOTSUPP;
return fp;
}
+
+ if (!fp->jited)
+ bpf_fixup_fallback_helpers(env, fp);
} else {
*err = bpf_prog_offload_compile(fp);
if (*err)
--
2.42.0