Zum Inhalt springen
← Alle Artikel

Spectre BTR: wenn der JIT auf deinem Server Speicher leakt

29. Sept. 2026 · Backend · 5 min

·Von Dimitri Pisarev

Das Embargo zu Branch Target Reuse (BTR) ist am 29. September 2026 gefallen. Der Angriff stammt von VUSec an der Vrije Universiteit Amsterdam und der Scuola Superiore Sant'Anna, das Paper ist auf der ACM CCS 2026 im November in Den Haag angenommen, und der Exploit-Code ist öffentlich. Zwei End-to-End-Exploits holen den Root-Passwort-Hash in Minuten aus su, über einen JIT-Filter, den jeder unprivilegierte Prozess installieren darf.

Mein Interesse ist enger gefasst: Welche der JITs, die ich selbst betreibe, wurden getestet, und haben die Kernel-Patches aus dem Juli die Tür geschlossen. Das haben sie.

Was bleibt übrig, wenn der JIT den Code freigibt?#

Moderne CPUs halten Code nach einer Selbstmodifikation kohärent. Der Branch Predictor folgt eigenen Regeln. Das Paper benennt für eine Code-Cache-Region, die ein JIT freigibt und danach neu füllt, was die CPU nicht zusichert: „not necessarily invalidate stale indirect branch prediction entries (i.e., branch targets)“. Solche Targets überleben den freigegebenen Code und werden bei der Neubefüllung des Caches wiederverwendet, „yielding a transient execute-after-free primitive“. Der Angreifer leitet den transienten Kontrollfluss auf neuen Code an alten Offsets um und landet auf misaligned Gadgets, die das neue Programm nie enthalten hat.

Der Code ist weg. Die Vorhersagen über ihn sind es nicht.

VUSec hat das Verhalten auf jeder getesteten CPU über Intel, AMD und Arm hinweg bestätigt: „No current CPU has a mechanism to keep the two in sync“. IBT und BTI erhöhen die Hürde. Ältere Intel-CPUs führen Instruktionen trotzdem spekulativ aus, bevor die Prüfung auf endbr64 greift.

Der Exploit gegen den Kernel, Schritt für Schritt#

Die Kette läuft von einem unprivilegierten Prozess bis in den Kernel-Speicher, zusammengesetzt aus Paper und Projektseite:

  1. cBPF ohne Privilegien installieren. eBPF bleibt privilegierten Prozessen vorbehalten, aber klassisches BPF erreicht den JIT weiterhin über die Seccomp-Filter, die Docker und Chrome routinemäßig installieren.
  2. Die JIT-Ausgabe formen. Der erste Exploit zielt auf die Default-Mitigations eines unmodifizierten Ubuntu und führt ein misaligned mikroarchitektonisches Gadget aus. Die Autoren haben für den Fall Constant Blinding JIT-Spraying über Jump-Offsets adaptiert (Maisuradze et al., 2016), ein Novum für cBPF.
  3. Das Programm freigeben. Der Kernel gibt die Code-Region zurück, der Branch Predictor behält die Targets, die hineinzeigen.
  4. Neu belegen. Ein neues cBPF-Programm landet in derselben Region, und die CVE-Ankündigung formuliert es so: „an indirect jump into the new program can reuse a branch prediction left behind by the old one“.
  5. Die transiente Ausführung auf die alten Offsets umlenken, das gepflanzte Gadget ausführen und über einen Cache-Seitenkanal mit 8 Byte pro Sekunde herausholen.
  6. Die Kernel-Task-Liste rückwärts über die prev-Pointer durchlaufen, die task_struct des Opfers über die PID finden und dann über mm_struct und die Seitentabellen bis zum Hash folgen. Die Demo leakt aus su nach su root.

Der vollständige Hash fällt auf Raptor Cove nach durchschnittlich 3 Minuten, auf Lion Cove nach 5 Minuten, und der Bypass gegen Constant Blinding schafft noch 10 Byte pro Sekunde.

Drei JITs stehen auf der Liste#

Die Liste der getesteten Engines umfasst genau drei Einträge: SpiderMonkey, den JIT von Firefox, Oracle GraalVM und den cBPF-JIT des Kernels. Drei Engines. V8 taucht genau einmal auf, in einem Satz über Site Isolation, und über Node.js fällt im Paper keine Aussage. Das Wort PHP kommt im Paper nicht vor.

Ein Default beantwortet die PHP-Frage: Der JIT wird über die gesamte 8.x-Reihe ausgeschaltet ausgeliefert, vor 8.4 durch ein opcache.jit_buffer_size von null („A zero value disables the JIT“), ab 8.4 durch ein explizites opcache.jit=disable (PHP-Doku). Du hast ihn für den Durchsatz angeschaltet, und BTR ist ein Grund, diese Entscheidung erneut zu prüfen, kein Notfall.

Die BTB-Einträge von SpiderMonkey haben auf Intel einen vollständigen Zyklus aus Freigabe und Neubefüllung überlebt und dabei 36 und 62 Byte pro Sekunde geleakt, auf Zen 4, A76 und X3 nicht. Die Site Isolation von Firefox ist nicht vollständig ausgerollt, ein anderer Tab kann den Adressraum teilen.

Der Juli-Fix und seine Reichweite#

Die tragende Korrektur liegt bereits im Mainline. x86-Kernel lösen seit Juli auf allen Kernen einen IBPB-Flush aus, wann immer ein cBPF-Programm eine zuvor ausgeführte cBPF- oder eBPF-Region wiederverwendet, unabhängig von IBT. Der Fix kam am 25. Juli 2026 als CVE-2026-64507, „x86/bugs: Enable IBPB flush on BPF JIT allocation“, angekündigt von Greg Kroah-Hartman, neben CVE-2026-64508, „bpf: Support for hardening against JIT spraying“. Die Fixes sind nach 6.6.145, 6.12.97, 6.18.39 und 7.1.4 zurückportiert und stecken in 7.2-rc2. Die Autoren zählen IBPB zu den wirksamen Gegenmaßnahmen. Ein Kernel mit dem Juli-Fix spült genau dort, wo diese Kette veraltete Predictions braucht.

Note

Hacker News schrieb, die Demo leake „within minutes from a fully patched Intel system with default protections enabled“. Das Paper sagt „default mitigations on stock Ubuntu“ und schweigt dazu, ob dieser Kernel den IBPB-Fix aus dem Juli bereits trug. Betrachte „fully patched“ als Medienformulierung.

Auch die Engine-Hersteller haben reagiert, uneinheitlich. GraalVM hat die Randomisierung der JIT-Code-Cache-Speicherorte gemergt (PR #14261). Mozilla hat IBPB erwogen und sich dafür entschieden, zuerst die Site Isolation fertigzustellen. Die Hardware-Hersteller haben erklärt, die Mechanismen existierten bereits, die Auslieferung sei Aufgabe der Software. Phoronix hat am Tag der Veröffentlichung bestätigt, dass der Juli bis heute die gesamte Kernel-Antwort bleibt. Das Paper stuft FineIBT allein als unzureichend ein, mit Constant Blinding als stark, und IBPB oder selektives Retpoline als wirksam, aber kostspielig.

Montagmorgen#

  • Aktualisiere die Kernel überall, auch auf dem VPS, den diese Seite per Hand mit docker compose up -d --build erreicht. Container erben den Kernel des Hosts, und die Juli-Backports tragen beide Fixes.
  • Lass den PHP-JIT aus, außer du hast den Gewinn gemessen. Die Defaults machen genau das.
  • bpf_jit_harden schaltet auf Hosts, die du noch nicht aktualisieren kannst, Constant Blinding ein. Die Autoren haben es umgangen, betrachte es als Übergangslösung.
  • Halte unprivilegierten Code aus Prozessen fern, die Geheimnisse halten. Das ist dieselbe Disziplin wie der Umgang mit untrusted Eingaben an der Grenze eines Agenten, eine Ebene tiefer.

BTR verlagert die transiente Ausführung aus dem Browser-Tab in jeden JIT, der seinen Code-Cache recycelt, über einen Seccomp-Filter, den unprivilegierte Prozesse den ganzen Tag über installieren. Die Antwort ist unspektakulär und liegt bereits bereit: Kernel ab den Juli-Branches, der JIT aus, wo niemand einen Gewinn gemessen hat, untrusted Code weg von Geheimnissen. Der Bau einer Datenbank pro Tenant hat mir gezeigt, dass Isolation hält, wenn sie strukturell ist, und ein Code-Cache war eine weitere Wand, die nur eine Konvention stützte. Patchen, und weiterarbeiten.