Skip to content
← All articles

Spectre BTR: when the JIT on your server leaks memory

Sep 29, 2026 · Backend · 5 min

·By Dimitri Pisarev

The embargo on Branch Target Reuse (BTR) lifted on September 29, 2026. The attack comes from VUSec at the Vrije Universiteit Amsterdam and the Scuola Superiore Sant'Anna, the paper is accepted at ACM CCS 2026 in The Hague in November, and the exploit code is public. Two end-to-end exploits leak the root password hash from su in minutes, through a JIT filter any unprivileged process can install.

My interest is narrower: which JITs I operate were tested, and whether the July kernel patches closed the door. They did.

What survives when the JIT frees the code?#

Modern CPUs keep code coherent after self-modification. The branch predictor is a different animal. After a JIT frees and refills a code-cache region, the CPU, in the paper's words, does "not necessarily invalidate stale indirect branch prediction entries (i.e., branch targets)." Those targets outlive the freed code and get reused when the cache is repopulated, "yielding a transient execute-after-free primitive." The attacker redirects transient control flow to the new code at old offsets, onto misaligned gadgets the new program never contained.

The code is gone. The predictions about it are not.

VUSec confirmed the behavior on every CPU tested across Intel, AMD and Arm: "No current CPU has a mechanism to keep the two in sync." IBT and BTI raise the bar. On older Intel CPUs, instructions still execute speculatively before the endbr64 check.

The kernel exploit, step by step#

The chain, assembled from the paper and the project page, runs from unprivileged process to kernel memory:

  1. Install cBPF without privileges. eBPF is privileged-only, but classic BPF still reaches the JIT through the seccomp filters Docker and Chrome install routinely.
  2. Shape the JIT output. The first exploit targets default mitigations on stock Ubuntu and runs a misaligned microarchitectural gadget. Against constant blinding, the authors adapted JIT spraying through jump offsets (Maisuradze et al., 2016), a first for cBPF.
  3. Free the program. The kernel returns the code region, but the branch predictor keeps the targets pointing into it.
  4. Reallocate. A new cBPF program lands in the same region, and as the CVE announcement puts it, "an indirect jump into the new program can reuse a branch prediction left behind by the old one."
  5. Redirect transient execution to the stale offsets, run the planted gadget, and exfiltrate through a cache side channel at 8 bytes per second.
  6. Walk the kernel task list backwards through the prev pointers, find the victim's task_struct by PID, then follow mm_struct and the page tables to the hash. The demo leaks from su after su root.

The full hash falls in 3 minutes on average on Raptor Cove, 5 on Lion Cove, and the constant-blinding bypass still manages 10 bytes per second.

Is anything I run on the list?#

The paper evaluates three engines: SpiderMonkey, the JIT of Firefox; Oracle GraalVM; and the kernel's cBPF JIT. That is the list. V8 appears once, in a sentence about site isolation, and no claim is made about Node.js. The word PHP never occurs in the paper.

For PHP the operative fact is a default: the JIT ships off across 8.x, before 8.4 through a zero opcache.jit_buffer_size ("A zero value disables the JIT"), from 8.4 through an explicit opcache.jit=disable (PHP docs). If you switched it on for throughput, BTR is a reason to re-check that decision on your own schedule.

SpiderMonkey's BTB entries survived a full free-and-reallocate cycle on Intel, leaking 36 and 62 bytes per second, but not on Zen 4, A76 or X3. Firefox's site isolation is not fully deployed, so another tab can share the address space.

What shipped in July, and what it covers#

The load-bearing fix is already in mainline. Since July, x86 kernels issue an IBPB flush on all cores whenever a cBPF program reuses a previously executed cBPF or eBPF region, regardless of IBT. That landed on July 25, 2026 as CVE-2026-64507, "x86/bugs: Enable IBPB flush on BPF JIT allocation", announced by Greg Kroah-Hartman, alongside CVE-2026-64508, "bpf: Support for hardening against JIT spraying". Fixes are backported to 6.6.145, 6.12.97, 6.18.39 and 7.1.4, and in 7.2-rc2. The authors count IBPB among the effective countermeasures, so a kernel with the July fix flushes exactly where this chain needs stale predictions.

Note

The Hacker News wrote that the demo leaks "within minutes from a fully patched Intel system with default protections enabled." The paper says "default mitigations on stock Ubuntu." Whether that kernel already carried the July IBPB fix is not stated. Read "fully patched" as the outlet's wording.

The engine vendors moved too, unevenly. GraalVM merged randomization of JIT code-cache locations (PR #14261). Mozilla considered IBPB and chose to finish site isolation first. The hardware vendors said the mechanisms already exist and deployment belongs in software. Phoronix confirmed on disclosure day that July is still the whole kernel response. The paper ranks FineIBT alone as insufficient, strong with constant blinding, and IBPB or selective retpoline as effective but costlier.

Monday morning#

  • Update kernels everywhere, including the VPS this site reaches by hand with docker compose up -d --build. Containers inherit the host kernel, and the July backports carry both fixes.
  • Leave the PHP JIT off unless you have measured the win. The defaults already do.
  • On hosts you cannot upgrade yet, bpf_jit_harden enables constant blinding. The authors bypassed it, so treat it as a stopgap.
  • Keep unprivileged code out of processes that hold secrets, the same discipline as treating agent tool output as untrusted, one layer down.

BTR moves transient execution from the browser tab into any JIT that recycles its code cache, through a seccomp filter unprivileged processes install all day long. The response is boring and already available: kernels from the July branches onward, the JIT off where nobody measured a win, untrusted code away from secrets. Building a database per tenant taught me that isolation holds when it is structural, and a code cache was one more wall held up by convention. Patch, and get back to work.