* PROBLEM: BPF interpreter fallback after JIT compilation of BPF_ADDR_PERCPU leads to kernel panic
@ 2026-06-29 14:42 Vincent Thiberville
2026-06-30 1:48 ` Leon Hwang
0 siblings, 1 reply; 4+ messages in thread
From: Vincent Thiberville @ 2026-06-29 14:42 UTC (permalink / raw)
To: ast@kernel.org, leon.hwang@linux.dev, daniel@iogearbox.net
Cc: bpf@vger.kernel.org
[-- Attachment #1.1: Type: text/plain, Size: 3481 bytes --]
Hello BPF maintainers
I have stumbled upon what I believe is a kernel bug that occurs when a BPF program is executed
in specific conditions. The in-the-wild situation is a fairly large BPF program that runs as a kretprobe,
and the crash happened on up-to-date debian13 computers, with the parameter `net.core.bpf_jit_harden=2`.
I have managed to reduce the conditions to a fairly small reproducer (joined to this mail), that does the
following:
*
Call bpf_map_lookup_elem (1)
*
Dereference the result if non null (2)
*
Jump with a large enough offset (>=11000) over instructions using constants (3)
```
0: .......... (62) *(u32 *)(r10 -4) = 0
1: .......... (bf) r2 = r10
2: ..2....... (07) r2 += -4
3: ..2....... (18) r1 = 0xffff8f0708cca200
5: .12....... (85) call bpf_map_lookup_elem#1
6: 0......... (15) if r0 == 0x0 goto pc+1
7: 0......... (79) r0 = *(u64 *)(r0 +0)
8: .......... (b7) r0 = 0
9: 0......... (15) if r0 == 0x12345678 goto pc+11001
10: 0......... (07) r0 += 1
11: 0......... (07) r0 += 1
...
11009: 0......... (07) r0 += 1
11010: .......... (b7) r0 = 0
11011: 0......... (95) exit
```
This program, in most situations, will either be JIT compiled and run properly, or will be rejected with the
error ENOTSUPP. But on machines with:
*
`CONFIG_BPF_JIT_ALWAYS_ON` not set
*
`net.core.bpf_jit_harden=2`
the program will fail to be JIT compiled, will fallback to the interpreter, and when executed, will cause
a invalid pointer dereference leading to a kernel panic.
To the best of my understanding, what happens is that:
*
The JIT compiler will optimize the map lookup at (1)
*
The JIT compiler will fail when reaching (3). This is because the JIT hardening enables constant blinding, which triples the size of the jump, overflowing 32767.
*
Since the JIT compiler failed, and CONFIG_BPF_JIT_ALWAYS_ON is not set, the kernel falls back to the interpreter
*
When executed, the interpreter will badly interpret (1), I suppose due to some JIT optimization not being cleaned after the JIT compiler failure.
*
This will leave R0 with an invalid value, which is dereferenced when the interpreter reaches (2)
But of course this may not be what is happening, I am far from an expert and you will know better than me.
This is easily reproducible on debian images, which do not set CONFIG_BPF_JIT_ALWAYS_ON. I have reproduced it on 6.12.74+deb13+1-amd64,
6.12.94+deb13-amd64 and 7.0.13+deb14-amd64. Setting `net.core.bpf_jit_harden=2` and running the reproducer is enough to trigger the kernel panic.
I am not entirely sure when this bug was introduced, or if it is fixed on the latest version. From some research, it may be related to this patch:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bdbf7446305
and there is a commit in review for bpf-next that mentions a fix for the aforementioned commit: `bpf: Disallow interpreter fallback for BPF_ADDR_PERCPU insn` in https://lore.kernel.org/bpf/20260622143557.22955-1-leon.hwang@linux.dev/T/#t
that would indicate an impact since 6.10.
Joined to this mail are the reproducer for the issue, and the dmesg of a kernel panic caused by the reproducer on the standard debian image `debian-sid-nocloud-amd64-daily-20260629-2524.qcow2`.
Thank you for all your work and for your help on this matter.
Best Regards
Vincent Thiberville
[-- Attachment #1.2: Type: text/html, Size: 19817 bytes --]
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: repro.c --]
[-- Type: text/x-csrc; name="repro.c", Size: 9195 bytes --]
/*
* Reproducer for BPF_ADDR_PERCPU interpreter crash.
*
* This is reproducer for a kernel-panic that happens when the kernel
* executes a BPF program that:
*
* - uses a per-cpu array map
* - is JIT compiled, but the JIT compilation fails
*
* This results in the kernel running the BPF interpreter on the program,
* but mis-interpreting the map lookup (probably optimized by the JIT
* compilation and not properly rolled-back after JIT compilation has
* failed). The map lookup is a no-op, thus not properly setting R0,
* and access to this register can then crash.
*
* This reproducer sets up a BPF program that looks like this:
*
* ```
* r0 = bpf_map_lookup_elem(map_fd, 0);
* r0 = r0 ? *r0 : 0;
* if (r0 != 0x12345678) {
* r0 += 1;
* ... // Copied JUMPLEN times
* r0 += 1;
* }
* r0 = 0
* ```
*
* What this does is:
*
* - Call bpf_map_lookup_elem, and deref the result
* - Setup a long jump afterwards
*
* When JIT hardening is enabled, the instructions inside the jump
* will increase in size (*3 I believe), making the jump offset invalid,
* and thus making the JIT compilation fail.
*
* The overall behavior is, to the best of my understanding:
*
* - If JIT hardening is disabled, this program is JIT compiled properly,
* and there is no bug.
*
* - If JIT hardening is enabled then:
*
* - constant binding will run, increasing the size of the jump by 3
* - JIT compilation will optimize the per-cpu array map lookup
* - JIT compilation will then fail on the jump, as the jump size
* overflows s16.
* - Since JIT compilation failed, the interpreter will be used, but
* the map lookup optimizations are still present.
* - When the program is executed, the execution of map_lookup_elem
* is buggy and r0 is not properly set. When it is then dereferenced,
* this results in a crash.
*/
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>
#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \
((struct bpf_insn){ .code = (CODE), .dst_reg = (DST), \
.src_reg = (SRC), .off = (OFF), .imm = (IMM) })
#define BPF_MOV64_REG(DST, SRC) \
BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_X, DST, SRC, 0, 0)
#define BPF_MOV64_IMM(DST, IMM) \
BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_K, DST, 0, 0, IMM)
#define BPF_ALU64_IMM(OP, DST, IMM) \
BPF_RAW_INSN(BPF_ALU64 | (OP) | BPF_K, DST, 0, 0, IMM)
#define BPF_ST_MEM(SZ, DST, OFF, IMM) \
BPF_RAW_INSN(BPF_ST | (SZ) | BPF_MEM, DST, 0, OFF, IMM)
#define BPF_LDX_MEM(SZ, DST, SRC, OFF) \
BPF_RAW_INSN(BPF_LDX | (SZ) | BPF_MEM, DST, SRC, OFF, 0)
#define BPF_LD_MAP_FD(DST, FD) \
BPF_RAW_INSN(BPF_LD | BPF_DW | BPF_IMM, DST, BPF_PSEUDO_MAP_FD, 0, FD)
#define BPF_JMP_IMM(OP, DST, IMM, OFF) \
BPF_RAW_INSN(BPF_JMP | (OP) | BPF_K, DST, 0, OFF, IMM)
#define BPF_CALL_FUNC(ID) \
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, ID)
#define BPF_EXIT_() \
BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, 0, 0, 0)
/* ── Helpers ─────────────────────────────────────────────────────── */
static long read_sysctl(const char *path)
{
char buf[64];
int fd = open(path, O_RDONLY);
if (fd < 0) {
return -1;
}
int n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n <= 0) {
return -1;
}
buf[n] = '\0';
return strtol(buf, NULL, 10);
}
static int is_jitted(int prog_fd)
{
struct bpf_prog_info info = {};
union bpf_attr attr = {};
attr.info.bpf_fd = prog_fd;
attr.info.info_len = (uint32_t)sizeof(info);
attr.info.info = (uint64_t)&info;
if (syscall(__NR_bpf, BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr)) < 0) {
return -1;
}
return info.jited_prog_len > 0;
}
int main(void)
{
int prog_fd = 0;
int map_fd = 0;
int sock = 0;
printf("BPF_ADDR_PERCPU JIT bug reproducer\n\n");
printf("WARNING: this WILL crash the kernel. Run in a disposable VM.\n\n");
long harden = read_sysctl("/proc/sys/net/core/bpf_jit_harden");
long enable = read_sysctl("/proc/sys/net/core/bpf_jit_enable");
long limit = read_sysctl("/proc/sys/net/core/bpf_jit_limit");
printf("Current config: bpf_jit_enable=%ld bpf_jit_harden=%ld bpf_jit_limit=%ld\n",
enable, harden, limit);
if (enable != 1 || harden != 2) {
fprintf(stderr, "WARNING: Need bpf_jit_enable=1 and bpf_jit_harden=2.\n");
fprintf(stderr, "Without those parameters, bug will very likely not trigger.\n\n");
}
/* --- Create per-cpu array map --- */
union bpf_attr mattr = {};
mattr.map_type = BPF_MAP_TYPE_PERCPU_ARRAY;
mattr.key_size = 4;
mattr.value_size = 8;
mattr.max_entries = 1;
map_fd = syscall(__NR_bpf, BPF_MAP_CREATE, &mattr, sizeof(mattr));
if (map_fd < 0) {
perror("BPF_MAP_CREATE");
goto cleanup;
}
printf("Created per-cpu array map (fd %d)\n", map_fd);
/* --- Create BPF program --- */
#define JUMPLEN 11000
struct bpf_insn *prog = malloc((JUMPLEN + 12) * sizeof(struct bpf_insn));
// First part is
//
// ```
// r0 = bpf_map_lookup_elem(map_fd, 0);
// r0 = r0 ? *r0 : 0;
// ```
//
// i.e., call bpf_map_lookup_elem, and deref the result.
//
// *(fp-4) = 0 -- store 0 on stack, the key
prog[0] = BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0);
// r2 = &key
prog[1] = BPF_MOV64_REG(BPF_REG_2, BPF_REG_10);
prog[2] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4);
// r1 = map_fd
prog[3] = BPF_LD_MAP_FD(BPF_REG_1, map_fd);
prog[4] = BPF_RAW_INSN(0, 0, 0, 0, 0);
// r0 = bpf_map_lookup_elem(r1, r2)
prog[5] = BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem);
// if (!r0) goto +1
prog[6] = BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1);
// r0 = *r0
prog[7] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_0, 0);
// r0 = 0
prog[8] = BPF_MOV64_IMM(BPF_REG_0, 0);
// Second part is
//
// ```
// if (r0 != 0x12345678) {
// r0 += 1;
// ... // Copied N times
// r0 += 1;
// }
// r0 = 0;
// ```
//
// i.e., setup a jump of a long enough length.
prog[9] = BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0x12345678, JUMPLEN + 1);
for (int k = 0; k < JUMPLEN; k++) {
prog[k + 10] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, 1);
}
prog[JUMPLEN + 10] = BPF_MOV64_IMM(BPF_REG_0, 0);
prog[JUMPLEN + 11] = BPF_EXIT_();
#define LOGSIZE 1024 * 1024
char *log_buf = malloc(LOGSIZE);
union bpf_attr pattr = {};
pattr.prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
pattr.insns = (uint64_t)prog;
pattr.insn_cnt = JUMPLEN + 12;
pattr.license = (uint64_t)"GPL";
pattr.log_buf = (uint64_t)log_buf;
pattr.log_size = LOGSIZE;
pattr.log_level = 1;
prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &pattr, sizeof(pattr));
if (prog_fd < 0) {
fprintf(stderr, "Failed to load BPF program: %s\n", strerror(errno));
fprintf(stderr, "Verifier:\n%s\n", log_buf);
goto cleanup;
}
printf("Loaded target BPF program (fd %d)\n", prog_fd);
printf("Verifier:\n%s\n", log_buf);
int jitted = is_jitted(prog_fd);
if (jitted < 0) {
fprintf(stderr, "Could not check if BPF program is JIT-compiled: %s\n", strerror(errno));
goto cleanup;
} else if (jitted > 0) {
fprintf(stderr, "BPF program was unexpectedly JIT-compiled, the bug will not occur. Aborting.\n");
goto cleanup;
}
printf("BPF program is INTERPRETED (JIT failed as expected)\n");
/* Phase 3: trigger via UDP loopback */
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
fprintf(stderr, "Could not create socket: %s\n", strerror(errno));
goto cleanup;
}
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "Could not bind socket: %s\n", strerror(errno));
goto cleanup;
}
socklen_t alen = sizeof(addr);
getsockname(sock, (struct sockaddr *)&addr, &alen);
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd)) < 0) {
fprintf(stderr, "Could not attach BPF program to socket: %s\n", strerror(errno));
goto cleanup;
}
printf("Sending UDP packet to trigger the BPF program...\n");
printf("If the bug is present, the kernel crashes NOW.\n");
if (sendto(sock, "x", 1, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "Could not send UDP packet: %s\n", strerror(errno));
goto cleanup;
}
char buf[1];
recv(sock, buf, 1, MSG_DONTWAIT);
printf("No crash — The kernel is either too old or has a fix.\n");
cleanup:
if (sock > 0) {
close(sock);
}
if (prog_fd > 0) {
close(prog_fd);
}
if (map_fd > 0) {
close(map_fd);
}
return 0;
}
[-- Attachment #3: dmesg --]
[-- Type: application/octet-stream, Size: 6887 bytes --]
[ 0.000000] [ T0] Linux version 7.0.13+deb14-amd64 (debian-kernel@lists.debian.org) (x86_64-linux-gnu-gcc-15 (Debian 15.3.0-1) 15.3.0, GNU ld (GNU Binutils for Debian) 2.46.50.20260617) #1 SMP PREEMPT_DYNAMIC Debian 7.0.13-1 (2026-06-19)
[ 0.000000] [ T0] Command line: BOOT_IMAGE=/boot/vmlinuz-7.0.13+deb14-amd64 root=PARTUUID=1fef9040-d8d4-4a91-9d20-17eedc7ec5a5 ro console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 crashkernel=256M crashkernel=512M-:192M
...
[ 50.164955] [ C12] BUG: unable to handle page fault for address: 000063c186484810
[ 50.165999] [ C12] #PF: supervisor read access in kernel mode
[ 50.166810] [ C12] #PF: error_code(0x0000) - not-present page
[ 50.167397] [ C12] PGD 0 P4D 0
[ 50.167768] [ C12] Oops: Oops: 0000 [#1] SMP NOPTI
[ 50.168253] [ C12] CPU: 12 UID: 0 PID: 1151 Comm: good-repro Kdump: loaded Not tainted 7.0.13+deb14-amd64 #1 PREEMPT(lazy) Debian 7.0.13-1
[ 50.169674] [ C12] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014
[ 50.170721] [ C12] RIP: 0010:___bpf_prog_run+0x2e0/0x2370
[ 50.171279] [ C12] Code: 00 48 83 c3 08 e9 53 fd ff ff f3 0f 1e fa 0f b6 43 01 48 0f bf 4b 02 48 83 c3 08 89 c2 83 e0 0f c0 ea 04 0f b6 d2 49 8b 14 d4 <48> 8b 14 0a 49 89 14 c4 e9 26 fd ff ff f3 0f 1e fa 0f b6 43 01 48
[ 50.173356] [ C12] RSP: 0018:ffffd01d8039cbd8 EFLAGS: 00010246
[ 50.173980] [ C12] RAX: 0000000000000000 RBX: ffffd01d831210e0 RCX: 0000000000000000
[ 50.174929] [ C12] RDX: 000063c186484810 RSI: 0000000000000079 RDI: ffffd01d8039cc20
[ 50.175772] [ C12] RBP: 0000000000000000 R08: ffff8c5b0752e800 R09: ffff8c5b0fad3f00
[ 50.176589] [ C12] R10: ffff8c5b0a155564 R11: ffff8c5b0fad3f00 R12: ffffd01d8039cc20
[ 50.177383] [ C12] R13: 0000000000000000 R14: ffff8c5b030f8540 R15: 0000000000000008
[ 50.178218] [ C12] FS: 00007f5c6525c780(0000) GS:ffff8c5bf987d000(0000) knlGS:0000000000000000
[ 50.179117] [ C12] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 50.179769] [ C12] CR2: 000063c186484810 CR3: 0000000007091006 CR4: 0000000000f72ef0
[ 50.180580] [ C12] PKRU: 55555554
[ 50.180902] [ C12] Call Trace:
[ 50.181224] [ C12] <IRQ>
[ 50.181578] [ C12] __bpf_prog_run32+0x69/0x90
[ 50.182039] [ C12] sk_filter_trim_cap+0x17f/0x3b0
[ 50.182531] [ C12] udp_queue_rcv_one_skb+0x121/0x520
[ 50.183221] [ C12] udp_unicast_rcv_skb+0x74/0x90
[ 50.183675] [ C12] __udp4_lib_rcv+0xb14/0xec0
[ 50.184083] [ C12] ? free_frozen_page_commit.isra.0+0xa5/0x4a0
[ 50.184651] [ C12] ? raw_local_deliver+0xce/0x2d0
[ 50.185085] [ C12] ip_protocol_deliver_rcu+0xcd/0x1b0
[ 50.185507] [ C12] ip_local_deliver_finish+0x85/0x100
[ 50.185999] [ C12] __netif_receive_skb_one_core+0x85/0xa0
[ 50.186436] [ C12] process_backlog+0x87/0x130
[ 50.186788] [ C12] __napi_poll+0x30/0x200
[ 50.187106] [ C12] ? skb_defer_free_flush+0x9c/0xc0
[ 50.187456] [ C12] net_rx_action+0x2fe/0x3f0
[ 50.187763] [ C12] handle_softirqs+0xd8/0x340
[ 50.188073] [ C12] do_softirq.part.0+0x3b/0x60
[ 50.188386] [ C12] </IRQ>
[ 50.188545] [ C12] <TASK>
[ 50.188706] [ C12] __local_bh_enable_ip+0x60/0x70
[ 50.188994] [ C12] __dev_queue_xmit+0x2c4/0x10b0
[ 50.189276] [ C12] ? arp_constructor+0x137/0x2e0
[ 50.189590] [ C12] ? _raw_spin_unlock_bh+0xe/0x20
[ 50.189878] [ C12] ? ___neigh_create+0x709/0xa30
[ 50.190129] [ C12] ? eth_header+0x2a/0xc0
[ 50.190348] [ C12] ip_finish_output2+0x1c3/0x660
[ 50.190900] [ C12] ? __ip_finish_output+0x46/0x170
[ 50.191305] [ C12] ip_output+0x63/0x110
[ 50.191621] [ C12] ? __pfx_ip_finish_output+0x10/0x10
[ 50.191933] [ C12] ip_send_skb+0x89/0xa0
[ 50.192196] [ C12] udp_send_skb+0x1a7/0x3c0
[ 50.192468] [ C12] ? udp_sendmsg+0xa1c/0xdf0
[ 50.192746] [ C12] ? udp_sendmsg+0xa1c/0xdf0
[ 50.193058] [ C12] udp_sendmsg+0xa40/0xdf0
[ 50.193321] [ C12] ? __pfx_ip_generic_getfrag+0x10/0x10
[ 50.193637] [ C12] __sys_sendto+0x1ca/0x1e0
[ 50.193874] [ C12] __x64_sys_sendto+0x24/0x30
[ 50.194112] [ C12] do_syscall_64+0xcd/0x15f0
[ 50.194342] [ C12] ? ksys_write+0x73/0xf0
[ 50.194567] [ C12] ? do_syscall_64+0x10a/0x15f0
[ 50.194804] [ C12] ? ksys_write+0x73/0xf0
[ 50.195021] [ C12] ? do_syscall_64+0x10a/0x15f0
[ 50.195255] [ C12] ? __handle_mm_fault+0x946/0xf50
[ 50.195502] [ C12] ? _raw_spin_unlock_irqrestore+0xe/0x40
[ 50.195773] [ C12] ? __wake_up+0x44/0x60
[ 50.195988] [ C12] ? count_memcg_events+0xeb/0x220
[ 50.196235] [ C12] ? handle_mm_fault+0x1e1/0x2f0
[ 50.196470] [ C12] ? do_user_addr_fault+0x2b4/0x7b0
[ 50.196718] [ C12] ? irqentry_exit+0x78/0x680
[ 50.196944] [ C12] ? exc_page_fault+0x82/0x1d0
[ 50.197172] [ C12] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 50.197447] [ C12] RIP: 0033:0x7f5c652f0cb2
[ 50.197735] [ C12] Code: 18 41 8b 93 08 03 00 00 59 5e 48 83 f8 fc 75 1a 83 e2 39 83 fa 08 75 12 e8 2b ff ff ff 0f 1f 00 49 89 ca 48 8b 44 24 20 0f 05 <48> 83 c4 18 c3 66 0f 1f 84 00 00 00 00 00 48 83 ec 10 ff 74 24 18
[ 50.198638] [ C12] RSP: 002b:00007fffa0facd00 EFLAGS: 00000202 ORIG_RAX: 000000000000002c
[ 50.199025] [ C12] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f5c652f0cb2
[ 50.199388] [ C12] RDX: 0000000000000001 RSI: 0000563e9e80f3cf RDI: 0000000000000005
[ 50.199754] [ C12] RBP: 00007fffa0facf20 R08: 00007fffa0facda0 R09: 0000000000000010
[ 50.200116] [ C12] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000001
[ 50.200479] [ C12] R13: 00007f5c6549a000 R14: 00007fffa0fad048 R15: 0000563e9e810dd8
[ 50.200850] [ C12] </TASK>
[ 50.201025] [ C12] Modules linked in: dm_mod nfnetlink cfg80211 rfkill ppdev intel_rapl_msr intel_rapl_common intel_uncore_frequency_common intel_pmc_core nls_ascii pmt_telemetry pmt_discovery nls_cp437 pmt_class vfat intel_pmc_ssram_telemetry fat intel_vsec binfmt_misc kvm_intel kvm irqbypass rapl snd_hda_codec_generic snd_hda_intel snd_hda_codec snd_hda_core snd_intel_dspcfg snd_intel_sdw_acpi snd_hwdep snd_pcm bochs snd_timer drm_client_lib snd vga16fb parport_pc drm_shmem_helper i2c_piix4 soundcore vgastate parport e1000 sg i2c_smbus drm_kms_helper pcspkr joydev button evdev drm efi_pstore configfs vsock_loopback vmw_vsock_virtio_transport_common vmw_vsock_vmci_transport vsock vmw_vmci ext4 crc16 mbcache jbd2 crc32c_cryptoapi sr_mod sd_mod cdrom ata_generic ata_piix libata psmouse scsi_mod floppy ghash_clmulni_intel scsi_common serio_raw qemu_fw_cfg autofs4 aesni_intel
[ 50.204586] [ C12] CR2: 000063c186484810
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: PROBLEM: BPF interpreter fallback after JIT compilation of BPF_ADDR_PERCPU leads to kernel panic
2026-06-29 14:42 PROBLEM: BPF interpreter fallback after JIT compilation of BPF_ADDR_PERCPU leads to kernel panic Vincent Thiberville
@ 2026-06-30 1:48 ` Leon Hwang
2026-06-30 12:08 ` Vincent Thiberville
0 siblings, 1 reply; 4+ messages in thread
From: Leon Hwang @ 2026-06-30 1:48 UTC (permalink / raw)
To: Vincent Thiberville, ast@kernel.org, daniel@iogearbox.net
Cc: bpf@vger.kernel.org
Hi Vincent,
Thank you for the report.
On 29/6/26 22:42, Vincent Thiberville wrote:
> Hello BPF maintainers
>
> I have stumbled upon what I believe is a kernel bug that occurs when a BPF program is executed
> in specific conditions. The in-the-wild situation is a fairly large BPF program that runs as a kretprobe,
> and the crash happened on up-to-date debian13 computers, with the parameter `net.core.bpf_jit_harden=2`.
>
> I have managed to reduce the conditions to a fairly small reproducer (joined to this mail), that does the
> following:
>
>
> *
> Call bpf_map_lookup_elem (1)
> *
> Dereference the result if non null (2)
> *
> Jump with a large enough offset (>=11000) over instructions using constants (3)
>
> ```
> 0: .......... (62) *(u32 *)(r10 -4) = 0
> 1: .......... (bf) r2 = r10
> 2: ..2....... (07) r2 += -4
> 3: ..2....... (18) r1 = 0xffff8f0708cca200
> 5: .12....... (85) call bpf_map_lookup_elem#1
> 6: 0......... (15) if r0 == 0x0 goto pc+1
> 7: 0......... (79) r0 = *(u64 *)(r0 +0)
> 8: .......... (b7) r0 = 0
> 9: 0......... (15) if r0 == 0x12345678 goto pc+11001
> 10: 0......... (07) r0 += 1
> 11: 0......... (07) r0 += 1
> ...
> 11009: 0......... (07) r0 += 1
> 11010: .......... (b7) r0 = 0
> 11011: 0......... (95) exit
> ```
>
> This program, in most situations, will either be JIT compiled and run properly, or will be rejected with the
> error ENOTSUPP. But on machines with:
>
>
> *
> `CONFIG_BPF_JIT_ALWAYS_ON` not set
> *
> `net.core.bpf_jit_harden=2`
I tried your reproducer with latest bpf-next code, but failed. It did
not crash the kernel.
gcc -o bpf-interpreter-bug ./repro.c
uname -r
7.1.0-ga187b7f97305
zgrep BPF /proc/config.gz
# BPF subsystem
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
# CONFIG_BPF_JIT_ALWAYS_ON is not set
sysctl -a | grep bpf_jit
net.core.bpf_jit_enable = 1
net.core.bpf_jit_harden = 2
net.core.bpf_jit_kallsyms = 1
net.core.bpf_jit_limit = 528482304
./bpf-interpreter-bug
BPF_ADDR_PERCPU JIT bug reproducer
WARNING: this WILL crash the kernel. Run in a disposable VM.
Current config: bpf_jit_enable=1 bpf_jit_harden=2 bpf_jit_limit=528482304
Created per-cpu array map (fd 3)
Loaded target BPF program (fd 4)
Verifier:
processed 11011 insns (limit 1000000) max_states_per_insn 0 total_states
1 peak_states 1 mark_read 0
BPF program was unexpectedly JIT-compiled, the bug will not occur. Aborting.
Did I miss something?
>
> the program will fail to be JIT compiled, will fallback to the interpreter, and when executed, will cause
> a invalid pointer dereference leading to a kernel panic.
>
> To the best of my understanding, what happens is that:
>
>
> *
> The JIT compiler will optimize the map lookup at (1)
> *
> The JIT compiler will fail when reaching (3). This is because the JIT hardening enables constant blinding, which triples the size of the jump, overflowing 32767.
> *
> Since the JIT compiler failed, and CONFIG_BPF_JIT_ALWAYS_ON is not set, the kernel falls back to the interpreter
> *
> When executed, the interpreter will badly interpret (1), I suppose due to some JIT optimization not being cleaned after the JIT compiler failure.
> *
> This will leave R0 with an invalid value, which is dereferenced when the interpreter reaches (2)
>
> But of course this may not be what is happening, I am far from an expert and you will know better than me.
>
> This is easily reproducible on debian images, which do not set CONFIG_BPF_JIT_ALWAYS_ON. I have reproduced it on 6.12.74+deb13+1-amd64,
> 6.12.94+deb13-amd64 and 7.0.13+deb14-amd64. Setting `net.core.bpf_jit_harden=2` and running the reproducer is enough to trigger the kernel panic.
>
> I am not entirely sure when this bug was introduced, or if it is fixed on the latest version. From some research, it may be related to this patch:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bdbf7446305
>
> and there is a commit in review for bpf-next that mentions a fix for the aforementioned commit: `bpf: Disallow interpreter fallback for BPF_ADDR_PERCPU insn` in https://lore.kernel.org/bpf/20260622143557.22955-1-leon.hwang@linux.dev/T/#t
I've posted another patch series to fix the assorted interpreter
fallback issues:
https://lore.kernel.org/bpf/20260626154330.33619-1-leon.hwang@linux.dev/
Thanks,
Leon
>
> that would indicate an impact since 6.10.
>
> Joined to this mail are the reproducer for the issue, and the dmesg of a kernel panic caused by the reproducer on the standard debian image `debian-sid-nocloud-amd64-daily-20260629-2524.qcow2`.
>
> Thank you for all your work and for your help on this matter.
>
> Best Regards
>
> Vincent Thiberville
>
>
>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: PROBLEM: BPF interpreter fallback after JIT compilation of BPF_ADDR_PERCPU leads to kernel panic
2026-06-30 1:48 ` Leon Hwang
@ 2026-06-30 12:08 ` Vincent Thiberville
2026-07-01 2:49 ` Leon Hwang
0 siblings, 1 reply; 4+ messages in thread
From: Vincent Thiberville @ 2026-06-30 12:08 UTC (permalink / raw)
To: Leon Hwang, ast@kernel.org, daniel@iogearbox.net; +Cc: bpf@vger.kernel.org
Hello
Thank you for the quick feedback. I cannot reproduce the issue using my reproducer on bpf-next, nor on 7.1.0-rc1. However, as I said, it works on 6.12.74 and on 7.0.13.
My best guess would be that a patch like this one: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d3e945223e0158c85dbde23de4f89493a2a817f6 makes the reproducer no longer work, but I do not know.
I don't know if this means the bug is no longer reachable on kernels >=7.1, or if my reproducer no longer works but the bug is reachable with other setups.
To the best of my understanding however, the reproducer should work between (and including) 6.10 and 7.0. I have not tested to find the exact version bounds however.
Best Regards
Vincent
________________________________________
From: Leon Hwang <leon.hwang@linux.dev>
Sent: Tuesday, June 30, 2026 03:48
To: Vincent Thiberville <vincent@harfanglab.fr>; ast@kernel.org <ast@kernel.org>; daniel@iogearbox.net <daniel@iogearbox.net>
Cc: bpf@vger.kernel.org <bpf@vger.kernel.org>
Subject: Re: PROBLEM: BPF interpreter fallback after JIT compilation of BPF_ADDR_PERCPU leads to kernel panic
Hi Vincent,
Thank you for the report.
On 29/6/26 22:42, Vincent Thiberville wrote:
> Hello BPF maintainers
>
> I have stumbled upon what I believe is a kernel bug that occurs when a BPF program is executed
> in specific conditions. The in-the-wild situation is a fairly large BPF program that runs as a kretprobe,
> and the crash happened on up-to-date debian13 computers, with the parameter `net.core.bpf_jit_harden=2`.
>
> I have managed to reduce the conditions to a fairly small reproducer (joined to this mail), that does the
> following:
>
>
> *
> Call bpf_map_lookup_elem (1)
> *
> Dereference the result if non null (2)
> *
> Jump with a large enough offset (>=11000) over instructions using constants (3)
>
> ```
> 0: .......... (62) *(u32 *)(r10 -4) = 0
> 1: .......... (bf) r2 = r10
> 2: ..2....... (07) r2 += -4
> 3: ..2....... (18) r1 = 0xffff8f0708cca200
> 5: .12....... (85) call bpf_map_lookup_elem#1
> 6: 0......... (15) if r0 == 0x0 goto pc+1
> 7: 0......... (79) r0 = *(u64 *)(r0 +0)
> 8: .......... (b7) r0 = 0
> 9: 0......... (15) if r0 == 0x12345678 goto pc+11001
> 10: 0......... (07) r0 += 1
> 11: 0......... (07) r0 += 1
> ...
> 11009: 0......... (07) r0 += 1
> 11010: .......... (b7) r0 = 0
> 11011: 0......... (95) exit
> ```
>
> This program, in most situations, will either be JIT compiled and run properly, or will be rejected with the
> error ENOTSUPP. But on machines with:
>
>
> *
> `CONFIG_BPF_JIT_ALWAYS_ON` not set
> *
> `net.core.bpf_jit_harden=2`
I tried your reproducer with latest bpf-next code, but failed. It did
not crash the kernel.
gcc -o bpf-interpreter-bug ./repro.c
uname -r
7.1.0-ga187b7f97305
zgrep BPF /proc/config.gz
# BPF subsystem
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
# CONFIG_BPF_JIT_ALWAYS_ON is not set
sysctl -a | grep bpf_jit
net.core.bpf_jit_enable = 1
net.core.bpf_jit_harden = 2
net.core.bpf_jit_kallsyms = 1
net.core.bpf_jit_limit = 528482304
./bpf-interpreter-bug
BPF_ADDR_PERCPU JIT bug reproducer
WARNING: this WILL crash the kernel. Run in a disposable VM.
Current config: bpf_jit_enable=1 bpf_jit_harden=2 bpf_jit_limit=528482304
Created per-cpu array map (fd 3)
Loaded target BPF program (fd 4)
Verifier:
processed 11011 insns (limit 1000000) max_states_per_insn 0 total_states
1 peak_states 1 mark_read 0
BPF program was unexpectedly JIT-compiled, the bug will not occur. Aborting.
Did I miss something?
>
> the program will fail to be JIT compiled, will fallback to the interpreter, and when executed, will cause
> a invalid pointer dereference leading to a kernel panic.
>
> To the best of my understanding, what happens is that:
>
>
> *
> The JIT compiler will optimize the map lookup at (1)
> *
> The JIT compiler will fail when reaching (3). This is because the JIT hardening enables constant blinding, which triples the size of the jump, overflowing 32767.
> *
> Since the JIT compiler failed, and CONFIG_BPF_JIT_ALWAYS_ON is not set, the kernel falls back to the interpreter
> *
> When executed, the interpreter will badly interpret (1), I suppose due to some JIT optimization not being cleaned after the JIT compiler failure.
> *
> This will leave R0 with an invalid value, which is dereferenced when the interpreter reaches (2)
>
> But of course this may not be what is happening, I am far from an expert and you will know better than me.
>
> This is easily reproducible on debian images, which do not set CONFIG_BPF_JIT_ALWAYS_ON. I have reproduced it on 6.12.74+deb13+1-amd64,
> 6.12.94+deb13-amd64 and 7.0.13+deb14-amd64. Setting `net.core.bpf_jit_harden=2` and running the reproducer is enough to trigger the kernel panic.
>
> I am not entirely sure when this bug was introduced, or if it is fixed on the latest version. From some research, it may be related to this patch:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bdbf7446305
>
> and there is a commit in review for bpf-next that mentions a fix for the aforementioned commit: `bpf: Disallow interpreter fallback for BPF_ADDR_PERCPU insn` in https://lore.kernel.org/bpf/20260622143557.22955-1-leon.hwang@linux.dev/T/#t
I've posted another patch series to fix the assorted interpreter
fallback issues:
https://lore.kernel.org/bpf/20260626154330.33619-1-leon.hwang@linux.dev/
Thanks,
Leon
>
> that would indicate an impact since 6.10.
>
> Joined to this mail are the reproducer for the issue, and the dmesg of a kernel panic caused by the reproducer on the standard debian image `debian-sid-nocloud-amd64-daily-20260629-2524.qcow2`.
>
> Thank you for all your work and for your help on this matter.
>
> Best Regards
>
> Vincent Thiberville
>
>
>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: PROBLEM: BPF interpreter fallback after JIT compilation of BPF_ADDR_PERCPU leads to kernel panic
2026-06-30 12:08 ` Vincent Thiberville
@ 2026-07-01 2:49 ` Leon Hwang
0 siblings, 0 replies; 4+ messages in thread
From: Leon Hwang @ 2026-07-01 2:49 UTC (permalink / raw)
To: Vincent Thiberville, ast@kernel.org, daniel@iogearbox.net
Cc: bpf@vger.kernel.org
On 30/6/26 20:08, Vincent Thiberville wrote:
> Hello
>
> Thank you for the quick feedback. I cannot reproduce the issue using my reproducer on bpf-next, nor on 7.1.0-rc1. However, as I said, it works on 6.12.74 and on 7.0.13.
>
> My best guess would be that a patch like this one: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d3e945223e0158c85dbde23de4f89493a2a817f6 makes the reproducer no longer work, but I do not know.
> I don't know if this means the bug is no longer reachable on kernels >=7.1, or if my reproducer no longer works but the bug is reachable with other setups.
>
> To the best of my understanding however, the reproducer should work between (and including) 6.10 and 7.0. I have not tested to find the exact version bounds however.
>
> Best Regards
> Vincent
>
Pls do not top-post. And reply in plain text mode.
See
https://docs.kernel.org/process/submitting-patches.html#use-trimmed-interleaved-replies-in-email-discussions.
[...]
>>
>>
>> *
>> Call bpf_map_lookup_elem (1)
>> *
>> Dereference the result if non null (2)
>> *
>> Jump with a large enough offset (>=11000) over instructions using constants (3)
>>
>> ```
>> 0: .......... (62) *(u32 *)(r10 -4) = 0
>> 1: .......... (bf) r2 = r10
>> 2: ..2....... (07) r2 += -4
>> 3: ..2....... (18) r1 = 0xffff8f0708cca200
>> 5: .12....... (85) call bpf_map_lookup_elem#1
>> 6: 0......... (15) if r0 == 0x0 goto pc+1
>> 7: 0......... (79) r0 = *(u64 *)(r0 +0)
>> 8: .......... (b7) r0 = 0
- 190 │ prog[8] = BPF_MOV64_IMM(BPF_REG_0, 0);
+ 190 │ prog[8] = BPF_JMP_IMM(BPF_IMM, 0, 0, 0);
By changing 'r0 = 0' to nop, I can reproduce the BUG with bpf-next code:
[ 742.666733] BUG: unable to handle page fault for address:
0000339b72aa42b8
[ 742.667846] #PF: supervisor read access in kernel mode
[ 742.ogr6a6m8595] # PiFs INT:E RePrRror_code(0x0000) - not-present page
With this diff, the internal BPF_ADDR_PERCPU insn is supported in the
interpreter.
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 6b28c8641b26..d0672f1ea1b1 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1918,6 +1918,9 @@ static u64 ___bpf_prog_run(u64 *regs, const struct
bpf_insn *insn)
case 32:
DST = (s32) SRC;
break;
+ case BPF_ADDR_PERCPU:
+ DST = (unsigned long)raw_cpu_ptr((void __percpu
*)(unsigned long)SRC);
+ break;
}
CONT;
ALU64_MOV_K:
The reproducer cannot crash the kernel after fixing.
./bpf-interpreter-bug
BPF_ADDR_PERCPU JIT bug reproducer
WARNING: this WILL crash the kernel. Run in a disposable VM.
Current config: bpf_jit_enable=1 bpf_jit_harden=2 bpf_jit_limit=528482304
Created per-cpu array map (fd 3)
Loaded target BPF program (fd 4)
Verifier:
processed 11012 insns (limit 1000000) max_states_per_insn 0 total_states
2 peak_states 2 mark_read 0
insn 10893 cannot be patched due to 16-bit range
BPF program is INTERPRETED (JIT failed as expected)
Sending UDP packet to trigger the BPF program...
If the bug is present, the kernel crashes NOW.
No crash — The kernel is either too old or has a fix.
Thanks,
Leon
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-01 2:49 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-29 14:42 PROBLEM: BPF interpreter fallback after JIT compilation of BPF_ADDR_PERCPU leads to kernel panic Vincent Thiberville
2026-06-30 1:48 ` Leon Hwang
2026-06-30 12:08 ` Vincent Thiberville
2026-07-01 2:49 ` Leon Hwang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox