CVE-2026-89581 Overview
CVE-2026-89581 is a Linux kernel vulnerability in the BPF x86 Just-In-Time (JIT) compiler. The flaw resides in the per-CPU address resolution logic, where the REX prefix byte is constructed incorrectly for extended registers. Specifically, the JIT uses add_1mod() which sets REX.B instead of add_2mod() which sets REX.R, causing the high bit of the destination register in ModRM.reg to be lost. As a result, BPF programs targeting registers R5, R7, R8, or R9 for per-CPU address loads write to the wrong physical register, corrupting scratch registers, frame pointers, or argument registers and triggering kernel page faults.
Critical Impact
A local unprivileged user loading a crafted BPF program can trigger register corruption in the kernel JIT output, leading to memory corruption and kernel panic.
Affected Products
- Linux kernel (BPF x86-64 JIT compiler)
- Kernel builds that JIT BPF programs referencing per-CPU variables into extended registers (R8–R15, and R5/R7 via low-three-bit aliasing)
- Distributions shipping affected mainline and stable kernel branches prior to the fix commits
Discovery Timeline
- 2026-09-11 - CVE-2026-89581 published to the National Vulnerability Database
- 2026-09-13 - Last updated in NVD database
Technical Details for CVE-2026-89581
Vulnerability Analysis
The Linux BPF x86 JIT emits an instruction to add a per-CPU offset from the %gs segment into a destination register. The destination is encoded in the ModRM.reg field, which is extended by the REX.R bit for access to registers R8 through R15. The JIT instead constructed the REX prefix with add_1mod(), which sets REX.B. REX.B extends ModRM.rm and SIB.base, neither of which is used by this disp32-with-no-base addressing form, so the extension bit is discarded.
When the high bit of the destination register is dropped, the encoded register collapses to the one sharing its low three bits: R5 aliases RAX, R7 aliases RBP, R8 aliases RSI, and R9 aliases RDI. The emitted opcode 65 49 03 04 25 <off> decodes to add %gs:<off>,%rax rather than the intended add %gs:<off>,%r8. The BPF program then dereferences a pointer that was never translated to a per-CPU address, producing a kernel page fault such as BUG: unable to handle page fault for address: 0000607e386a8894.
Root Cause
The root cause is an incorrect REX prefix construction in the JIT emitter for the per-CPU MOV instruction. add_1mod(0x48, dst_reg) was used where add_2mod(0x48, 0, dst_reg) is required, mismatching the register field placement in ModRM.reg with the wrong REX extension bit. The fix aligns the emitter with add_2reg() and emit_priv_frame_ptr(), which correctly hardcode 0x4c for R9.
Attack Vector
Exploitation is local. A user with the ability to load BPF programs (typically requiring CAP_BPF or CAP_SYS_ADMIN, though unprivileged BPF is enabled in some configurations) can construct a program whose register allocator assigns per-CPU variable addresses to R5, R7, R8, or R9. On execution, the JITed instruction clobbers the aliased general-purpose register and dereferences an invalid pointer, causing a kernel panic. The condition was surfaced by GCC-compiled BPF selftests (test_progs-bpf_gcc); Clang masked the bug by reloading addresses into R1 before each access.
See the kernel commit 6a19b18d for the corrected emitter logic.
Detection Methods for CVE-2026-89581
Indicators of Compromise
- Kernel page faults originating from addresses inside bpf_prog_* JITed functions, particularly at offsets performing per-CPU loads.
- Kernel panic - not syncing: Fatal exception in interrupt messages following __bpf_prog_test_run_raw_tp or similar BPF invocation paths.
- Unexpected crashes when running BPF binaries compiled with GCC (BPF_GCC) that reference per-CPU variables.
Detection Strategies
- Audit dmesg and kernel crash dumps for RIP values inside BPF JIT-emitted code combined with faulting addresses that resemble unrelocated per-CPU offsets.
- Enable bpf_jit_dump or inspect JITed output for the byte sequence 65 49 03 04 25 on affected kernel versions to confirm the miscoded prefix.
- Monitor BPF program load events via audit subsystem (SYSCALL bpf) and correlate with subsequent kernel oops or panic events.
Monitoring Recommendations
- Track bpf() syscall usage by unprivileged accounts and container workloads to identify unusual program loads.
- Ingest kernel crash telemetry into a centralized data lake to correlate BPF JIT faults across fleets.
- Alert on repeated node reboots or unexplained kernel panics on hosts that permit BPF program loading.
How to Mitigate CVE-2026-89581
Immediate Actions Required
- Apply the upstream stable kernel updates that include the JIT fix as soon as they are packaged by your distribution.
- Inventory hosts running kernels that JIT BPF programs and prioritize patching multi-tenant systems and container hosts first.
- Restrict who can load BPF programs by setting kernel.unprivileged_bpf_disabled=1 where operationally acceptable.
Patch Information
The vulnerability is resolved in the mainline and stable kernels through the following commits: 5bbbce02, 638bc3aa, 68866424, and 6a19b18d. The fix replaces add_1mod() with add_2mod() so the destination register is extended through REX.R rather than the ineffective REX.B.
Workarounds
- Disable unprivileged BPF program loading via sysctl -w kernel.unprivileged_bpf_disabled=1.
- Continue building BPF programs with Clang until patched kernels are deployed, since Clang's code generation avoids extended-register per-CPU destinations.
- Remove CAP_BPF and CAP_SYS_ADMIN from workloads and containers that do not require BPF loading.
# Disable unprivileged BPF and verify the setting
sudo sysctl -w kernel.unprivileged_bpf_disabled=1
echo 'kernel.unprivileged_bpf_disabled=1' | sudo tee /etc/sysctl.d/99-bpf-hardening.conf
sudo sysctl -p /etc/sysctl.d/99-bpf-hardening.conf
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.
