
Proof-of-Concept-Exploit für eine lokale Privilege-Escalation bei einer Race Condition in der Linux-qdisc-Rate-Table, die mithilfe von BPF-Heap-Grooming und einem Pipe-Leak Root-Rechte erlangt.

Dieses Repository enthält einen Proof-of-Concept-Exploit zur lokalen Privilegieneskalation für CVE-2026-68138, eine Race-Bedingung im Rate-Table-Code der Linux-Traffic-Control. In der getesteten QEMU-Umgebung eskaliert der PoC von einem normalen Prozess mit äußerer UID 1000 zu einer Shell mit UID 0 im initialen User-Namespace.
Warnung
Dieser Code beschädigt absichtlich den Zustand des Kernel-Heaps. Verwenden Sie ihn ausschließlich in einer isolierten, wegwerfbaren VM, die Ihnen gehört. Eine verpasste Race oder eine verfrühte Bereinigung kann den Gast zum Absturz bringen (Kernel-Panic). Führen Sie ihn nicht auf einer Workstation, einem Server oder einem System Dritter aus.
qdisc_get_rtab() und qdisc_put_rtab() verwalten eine prozessglobale einfach verkettete Liste, qdisc_rtab_list, sowie einen einfachen nicht-atomaren int refcnt. Historisch hielten die Aufrufer den RTNL-Mutex, der den Zugriff auf die Liste und den Referenzzähler serialisierte.
Der Flower-Klassifizierer setzt TCF_PROTO_OPS_DOIT_UNLOCKED. Eine RTM_NEWTFILTER-Anfrage für eine Flower-Regel kann folglich eine Police-Action erreichen und die qdisc-Rate-Table-Helfer ohne RTNL aufrufen:
tc_new_tfilter()
-> fl_change()
-> tcf_exts_validate_ex()
-> tcf_action_init()
-> tcf_police_init()
-> qdisc_get_rtab()/qdisc_put_rtab()
Parallele Anfragen, die dieselbe Rate-Tabelle verwenden, können zu einer Race-Bedingung auf der globalen Liste und ihrem Referenzzähler führen. Das Ergebnis ist ein Use-after-Free oder Double-Free von struct qdisc_rate_table, einem 1056-Byte-Objekt, das auf dem getesteten x86-64-Kernel aus kmalloc-2k alloziert wird.
Da die Liste global und nicht pro Netzwerk-Namespace ist, konkurrieren Anfragen aus getrennten Netzwerk-Namespaces weiterhin um dasselbe Objekt.
Der Linux-CNA-Eintrag identifiziert den Einführungs-Commit 470502de5bdb, der in Linux 5.1 veröffentlicht wurde.
Distributionskernel backporten Korrekturen häufig, ohne auf die oben gezeigte Upstream-Version zu wechseln. Prüfen Sie, ob einer der beiden Fixing-Commits – oder die entsprechende Änderung des qdisc-Rate-Table-Spinlocks – im exakt verwendeten Kernelquelltext des Systems vorhanden ist.
Der Exploit wurde gegen den verwundbaren Commit 92d3817649df2b0b6a008a686c8275c88d7ef594, dem direkten Parent des Mainline-Fixes, entwickelt und validiert. Als Kontrolle mit behobenem Kernel diente f43ee0c0730d6191629b5ee1ceae27b1ebfdc047.
Stand 2026-08-12 lieferte die Ubuntu-CVE-Tracker-Suche keinen Eintrag für diese CVE. Die folgende Tabelle ist daher eine direkte Inspektion des Quellcodes und keine Sicherheitsstatus-Bewertung durch Canonical. Jeder verlinkte Ubuntu-Tag enthält weiterhin das ungeschützte qdisc_rtab_list und weist den korrigierenden qdisc_rtab_lock nicht auf.
Zum Zeitpunkt der Inspektion wurde kein korrigiertes Ubuntu-Paket identifiziert. Zukünftige Ubuntu-Pakete sollten auf ein Backport-Äquivalent zu den verlinkten Upstream-Fixes geprüft werden, anstatt nur anhand ihrer Versionsnummer beurteilt zu werden.
Der buildspezifische Ubuntu-22.04-Exploit, das QEMU-Labor, die exakte Image-Checksumme und die Bedingungen sind in ubuntu/README.md dokumentiert. Er wurde gegen den offiziellen Kernel 5.15.0-187-generic #197-Ubuntu mit aktiviertem Memory-Cgroup-Accounting validiert.
Der zugrunde liegende Fehler und diese spezielle Exploit-Kette haben unterschiedliche Anforderungen. Der PoC wurde validiert mit:
CONFIG_USER_NS=y und CONFIG_NET_NS=y;CONFIG_NET_CLS=y, CONFIG_NET_CLS_FLOWER=y, CONFIG_NET_CLS_ACT=y und CONFIG_NET_ACT_POLICE=y;CONFIG_TMPFS_XATTR=y für den simple_xattr-Heap-Spray;CONFIG_MODULES=y und ein nutzbares /sbin/modprobe für den finalen Root-Helfer;CONFIG_MEMCG=n, sodass sich die von dieser Kette verwendeten qdisc/BPF/pipe/xattr-Allokationen den erwarteten kmalloc-2k-Cache teilen;CONFIG_SLAB_BUCKETS=y, Freelist-Randomisierung und Freelist-Härtung waren im erfolgreichen Testkernel aktiviert. KASLR wird nicht grundsätzlich mit einer hartcodierten Adresse umgangen: Der PoC erhält die benötigten Page- und Operations-Zeiger aus dem Pipe-Leak. Das mitgelieferte Labor verwendete nokaslr, um das Debugging zu vereinfachen.
Kernel mit aktiviertem Memory-Cgroup-Accounting oder anderen Allocator-/Cache-Layouts erfordern eine andere Reclaim-Strategie. Der PoC erhebt bewusst keinen Anspruch auf Portabilität über beliebige Distributionskonfigurationen hinweg.
Die separate Ubuntu-Variante implementiert diese andere Reclaim-Strategie; ihre Anforderungen sind bewusst enger gefasst und in ubuntu/README.md aufgelistet.
Vier Worker-Threads betreten verschiedene Netzwerk-Namespaces und erzeugen Flower-Filter mit Police-Actions. Drei Worker durchlaufen den erfolgreichen Aktionspfad; einer liefert nach dem Erwerb beider Rate-Table-Referenzen einen absichtlich ungültigen Estimator und erzwingt so den Bereinigungspfad. Diese Kombination macht die gleichzeitige Manipulation von Referenzzähler und Liste reproduzierbar, ohne den Flower-Klassifizierer-Zustand zwischen den Workern zu teilen.
qdisc_rate_table mit klassischem BPF wiederverwendenNach jeder Netlink-Anfrage hängt dieselbe CPU sofort einen klassischen BPF-Filter mit 133 Instruktionen an. Sein 1064-Byte-Instruktionsarray wird aus kmalloc-2k alloziert und ist so geformt, dass die Bytes, die qdisc_rate_table.next und qdisc_rate_table.refcnt überlappen, zunächst gültig bleiben.
Die letzte Instruktion enthält einen Pro-Socket-Marker. SO_GET_FILTER ermöglicht es dem PoC zu erkennen, wann der orig_prog->filter-Zeiger eines Sockets auf eine andere aktive BPF-Allokation umgeleitet wurde. Dadurch ergeben sich zwei Socket-Objekte, die auf denselben Instruktionspuffer verweisen.
Das Schließen eines Besitzers verschiebt die tatsächliche Freigabe über sk_filter_release_rcu(). Nach der Grace-Periode alloziert der PoC Pipe-Ringe mit 32 Slots:
32 * sizeof(struct pipe_buffer) = 32 * 40 = 1280 bytes -> kmalloc-2k
Ein Ring übernimmt den aliased BPF-Puffer. Das Lesen des anderen Sockets mit SO_GET_FILTER legt einen aktiven pipe_buffer offen, einschließlich seiner page- und ops-Zeiger sowie des Offsets des Page-Cache-Eintrags von /sbin/modprobe.
Das Schließen des zweiten Sockets gibt den aktiven Pipe-Ring nach einer weiteren RCU-Grace-Periode frei, während das zugehörige pipe_inode_info weiterhin darauf verweist. Ein simple_xattr-Spray übernimmt den 1280-Byte-Ring und platziert den geleakten pipe_buffer mit gesetztem PIPE_BUF_FLAG_CAN_MERGE im erwarteten Slot.
Das Schreiben auf jede Kandidaten-Pipe hängt schließlich angreiferkontrollierte Bytes an die Page-Cache-Seite an, die den ELF-Einstiegspunkt von /sbin/modprobe enthält. Der PoC liest die Datei erneut und fährt erst fort, nachdem er die exakte Überschreibung bestätigt hat.
Aktuelle Kernel verwenden nicht mehr den historischen Invalid-Binfmt-Modul-Autoload-Fallback in search_binary_handler(). Dieser PoC erzeugt stattdessen einen AF_INET-Datagramm-Socket mit dem nicht unterstützten Protokoll 253. inet_create() fordert das fehlende Modul net-pf-2-proto-253-type-2 an, wodurch der Kernel das überschriebene /sbin/modprobe als Root im initialen Namespace ausführt.
Das injizierte Entry-Point-Payload schreibt eine Proof-Datei und öffnet eine Root-Shell auf der QEMU-Serienkonsole.
Das Gast-Binary ist statisch gelinkt:
./build.sh
Entsprechender Befehl:
gcc -O2 -static -pthread -Wall -Wextra -Werror \
-o build/exploit exploit.c
Das resultierende Binary muss im Gast als /exploit/CVE-2026-68138 bereitgestellt werden. Das getestete Labor verwendete QEMUs lesbar-schreibbaren 9p-Share. Der Root-Helper erwartet diesen Share unter /chroot/exploit aus dem initialen Mount-Namespace; passen Sie helper_command in exploit.c an, falls das Labor ein anderes Layout verwendet.
Die relevanten Testkernel-Optionen sind in config.fragment festgehalten. Sie sind ein Fragment, keine vollständige Produktionskernel-Konfiguration.
Aus der unprivilegierten Gast-Shell:
id
/exploit/CVE-2026-68138
Die Race ist probabilistisch. Bei einem Fehlschlag gibt der PoC Folgendes aus:
[-] no BPF alias found in this attempt
Starten Sie die VM vor einem erneuten Versuch neu. Führen Sie ihn nicht erneut in demselben Gast aus: Die globale Qdisc-Liste könnte bereits korrumpiert sein.
Bei erfolgreicher Ausführung erscheint:
[+] confirmed dangling orig_prog->filter pointer
[+] pipe leak: page=<kernel pointer> ops=<kernel pointer> off=0x60f len=1 flags=0
[+] page-cache overwrite via dangling pipe
[+] controlled page-cache write confirmed; triggering helper
[+] CVE-2026-68138 initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
[+] ROOT PROOF: uid=0(root) gid=0(root) groups=0(root)
Das vollständige bereinigte Transkript befindet sich in docs/example-output.txt.
Die vollständige Kette wurde dreimal von frischen, verwundbaren VM-Boots aus durchlaufen. Das finale Binary erzeugte:
outer identity: uid=1000(user)
alias: classic-BPF orig_prog->filter overlap
leak: live pipe_buffer page and ops pointers
write: verified /sbin/modprobe page-cache modification
final identity: uid=0(root) gid=0(root) groups=0(root)
Dasselbe Binary wurde mit derselben Vier-vCPU-Topologie gegen den behobenen Commit ausgeführt. Ungefähr 126.000 Race-Anfragen liefen ohne BPF-Alias durch, und weder die Pipe- noch die Page-Cache-Phase wurde erreicht.
Die Ubuntu-spezifische Variante wurde ebenfalls gegen den exakten offiziellen generischen Kernel 5.15.0-187.197 ausgeführt. Der Gast bootete mit vier vCPUs, normalem Memory-Cgroup-Accounting und nokaslr als einzigem exploithubspezifischem Kernel-Argument.
Der Ubuntu-Kernel wurde nicht neu kompiliert: Der Test verwendete das unveränderte vmlinuz-5.15.0-187-generic der Distribution und die dazugehörigen offiziellen Module. Die Ubuntu-Voraussetzungstabelle trennt die Standard-Kernelfunktionen von den Laufzeit- und QEMU-Labor-Anpassungen.
Das Startkonto hatte keinen Sudo-Zugriff und keine zusätzlichen Gruppen:
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
Ausgehend von einem frischen Snapshot erlangte der Exploit den BPF-Alias, übernahm ihn mithilfe von cgroup-v2-simple_xattr-Objekten und blockierenden select()-Bitmaps, verifizierte, dass /proc/sys/kernel/modprobe /tmp/x enthielt, und erreichte:
[+] CVE-2026-68138 Ubuntu initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
root@cve-2026-68138-jammy:/# id
uid=0(root) gid=0(root) groups=0(root)
Siehe das echte QEMU-Transkript und die Ubuntu-Laboranleitung. Die Variante hartcodiert die modprobe_path-Adresse für dieses Paket und erfordert daher nokaslr; sie erhebt keinen Anspruch auf einen KASLR-Bypass oder Kompatibilität mit einem anderen Ubuntu-Build.
Der Patch ist bereits Teil des Linux-Kernels und wird daher in diesem Repository nicht dupliziert. Die kanonischen Stable- und Mainline-Fixing-Commits sind in den folgenden Referenzen verlinkt.
Forschung und Exploit-Implementierung: A. Ramos <[email protected]> (Twitter: @aramosf).
| Linie | Status | Commit/Version |
|---|
| Linux vor 5.1 | Nicht betroffen | Die einführende Änderung fehlt |
| Linux 5.1 bis 7.1.5 | Betroffen, sofern kein Hersteller-Backport vorhanden ist | 470502de5bdb bis zu dem Commit vor dem stabilen Fix |
| Linux 7.1.y | Behoben | 7.1.6, fb29e1b41052 |
| Linux-7.2-Entwicklungsreihe | Betroffen vor rc5 | rc1 bis rc4 |
| Mainline | Behoben | 7.2-rc5, f43ee0c0730d |
| Ubuntu-Linie | UntersuCHtes Paket/Tag | Quellcode-Ergebnis |
|---|
| Ubuntu 22.04 GA | 5.15.0-187.197 | Verwundbarer Code vorhanden; vollständiger Exploit in QEMU reproduziert |
| Ubuntu 22.04 HWE | 6.8.0-136.136~22.04.1 | Verwundbarer Code vorhanden; Exploit-Kette nicht getestet |
| Ubuntu 24.04 HWE | 7.0.0-28.28~24.04.1 | Verwundbarer Code vorhanden; diese Exploit-Kette ist mit dessen Allocator-Härtung nicht kompatibel |
| Ubuntu 26.04 | 7.0.0-28.28 | Verwundbarer Code vorhanden; diese Exploit-Kette ist mit dessen Allocator-Härtung nicht kompatibel |