
Nur-Proof-PoC für CVE-2026-98432, eine Linux-IPv4/TCP-ireq_opt-Use-after-Free-Schwachstelle: Race-Trigger und Heap-Reuse-Oracle, die die UAF ohne Codeausführung bestätigen.
ireq_opt Use-After-Free PoCReiner Proof-of-Concept für CVE-2026-98432 (CVSS 9.8): ein aus der Ferne auslösbarer Use-After-Free im Linux-IPv4/TCP-Stack.
Nur für autorisierte Sicherheitstests. Führe ihn nur gegen Systeme aus, die dir gehören oder für die du eine ausdrückliche Testgenehmigung hast.
Der PoC löst das Race aus und erkennt, wenn der Kernel Bytes aus einem
freigegebenen ip_options_rcu-Objekt in ein SYN-ACK kopiert. Er erstellt keine Benutzer oder
Dateien und implementiert keine Code-Execution-Kette.
Ein Treffer bedeutet, dass der Server die Markierung einer anderen Allokation in seinem
SYN-ACK zurückgegeben hat — ein korrekter Kernel kann das nicht tun. In einem kontrollierten Test auf Ubuntu
24.04.5 mit Kernel 6.8.0-147-generic (von Ubuntu als verwundbar gelistet; Fix
in 6.8.0-149 vorgesehen) erzeugte ein 180-sekündiger Loopback-Lauf 21 Treffer bei
320.829 Versuchen, und ein 300-sekündiger Lauf erzeugte 22 Treffer bei 616.911
Versuchen. Der Kernel blieb sauber (tainted=0, kein Oops).
Es sind hier keine Paketmitschnitte oder Logs von diesem Host enthalten; die eigene Ausgabe des PoC ist der Beweis.
Ein SYN mit IPv4-Optionen veranlasst den Kernel, ein ip_options_rcu
(72 Bytes, kmalloc-96) für den Request-Socket zu erstellen. Wenn der Handshake abgeschlossen ist,
übergibt tcp_v4_syn_recv_sock() dieses Objekt an den Child-Socket, ohne
es zu kopieren, und löscht dann den Zeiger im Request-Socket. Eine zweite CPU, die
den Request-Socket bereits nachgeschlagen hat, kann sich noch innerhalb von
tcp_check_req() → tcp_v4_send_synack() → ip_build_and_send_pkt() →
ip_options_build() befinden und dasselbe Objekt lesen. Child-Sockets sind nicht
SOCK_RCU_FREE, und inet_sock_destruct() gibt inet_opt mit einem einfachen
kfree() frei, sodass der Leser, wenn das Child in diesem Fenster abgebaut wird, auf
freigegebenen Speicher zugreift.
Eine zweite Variante existiert mit Request-Socket-Migration
(net.ipv4.tcp_migrate_req=1 oder einem BPF-reuseport-Programm); der Fix verwendet
kfree_rcu() sowohl in inet_sock_destruct() als auch in tcp_v4_reqsk_destructor().
Eingeführt in Linux 4.4 (079096f103fa); behoben in Mainline 7.3 und
zurückportiert auf 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.56 und
7.2.10. Betroffen: Linux 4.4 und später, ungepatcht.
Die Auswirkung reicht von einem entfernten Absturz (DoS) bis zur Offenlegung von Kernel-Speicher und, mit einer vollständigen Exploit-Kette, zur Code-Ausführung. Dieses Repository endet beim Nachweis des Use-After-Free.
Pro Versuch:
ireq_opt; das Ziel spiegelt die Option in seinem SYN-ACK wider.inet_opt, der Request-
Socket verwirft seinen Zeiger.Orakel. Jeder Versuch bettet eine eindeutige 32-Bit-Magic plus ein Prüf-Tag an Options-Offsets 16..23 ein. Drei Umschreibungen erfolgen, bevor ein User-Space-Leser die Option sieht:
| Stufe | Slot |
|---|---|
Server ip_options_compile() beim SYN | 4..7 (TS) / 3..6 (RR) |
Server ip_options_build() für das SYN-ACK | 8..11 (TS) / 7..10 (RR) |
Empfangshost ip_options_compile() beim SYN-ACK | 12..15 (TS) / 11..14 (RR) |
Die Markierung liegt hinter allen drei. Ein SYN-ACK, das die Magic einer anderen
Allokation widerspiegelt (mit tag == magic ^ 0x5a5a5a5a), beweist, dass der Leser
aus einem freigegebenen und wiederverwendeten Objekt kopiert hat.
CAP_NET_RAW; der automatische RST-Filter benötigt
CAP_NET_ADMIN (root).accept() ausführt.iptables/nftables für den Bare-RST-Filter.--proof ist eigenständig auf einem Host: Es startet einen Loopback-Listener und
lässt ihn gegen sich selbst racen.
python3 poc_cve_2026_98432.py --selftest
sudo python3 poc_cve_2026_98432.py --check
Gibt den laufenden Kernel, die Tabelle der festen Releases und relevante sysctls aus.
sudo python3 poc_cve_2026_98432.py --proof 9000 \
--duration 180 --workers 6 --spray 2 --spray-rate 1000 \
--pcap /tmp/proof.pcap
Startet einen Listener auf 127.0.0.1:9000, führt das Race aus, gibt ein Urteil aus, stoppt
den Listener und entfernt die temporäre Firewall-Regel. Auf dem Host wird nichts erstellt
— nur transienter TCP-Zustand.
sudo python3 poc_cve_2026_98432.py --probe <TARGET>:<PORT>
Eine SYN: meldet, ob das Ziel ein MPTCP-Listener ist und ob es die IPv4-Option zurückspiegelt (vom Orakel benötigt).
sudo python3 poc_cve_2026_98432.py <TARGET>:<PORT> \
--workers 16 --spray 4 --duration 600 --pcap hits.pcap
Öffentliche Adressen werden abgelehnt, sofern nicht --allow-public angegeben wird. Der Bare-RST-Filter wird automatisch installiert und beim Beenden entfernt.
python3 poc_cve_2026_98432.py --listen 9000 --mptcp
Bindet an 127.0.0.1; füge --listen-any nur für einen entfernten Angreifer-Host hinzu. Der
Listener akzeptiert und schließt jede Verbindung sofort mit
SO_LINGER(0), wodurch das Kind schnell freigegeben wird.
| Feld | Bedeutung |
|---|---|
attempts | gestartete Handshakes |
synacks | beobachtete SYN-ACKs (etwa zwei pro Versuch) |
hits | SYN-ACK trug die Markierung einer anderen Allokation → UAF bestätigt |
anomalies | Markierung geparst, aber Tag stimmt nicht überein (teilweise Korruption) |
leaks | Wert, der wie ein Kernel-Pointer aussieht, in den zurückgespiegelten Bytes |
noopt | SYN-ACK ohne die zurückgespiegelte Option (Syncookies/Warteschlange voll/Stripping) |
stale | späte SYN-ACK-Neuübertragung herausgefiltert (kein Hit) |
malformed | SYN-ACK mit ihl < 5 (Signatur eines Stale-optlen-Unterlaufs) |
timeouts | kein SYN-ACK innerhalb von --timeout |
spray | gesendete Heap-Spray-SYNs |
Beispiel-Hit:
[!] UAF ORACLE HIT #1 at attempt sport=63042 (expected magic f815ca68 tag a24f9032)
tcp <TARGET>:9000 -> <CLIENT>:63042 seq=c20ed8ef ack=d212b2b1 flags=0x12
SYN-ACK echoed magic d4bcdcc7 tag 8ee6869d, belongs to another allocation
ip options:
44 28 11 00 03 71 b0 b5 03 71 cc 73 03 71 cc 73
d4 bc dc c7 8e e6 86 9d 00 00 00 00 00 00 00 00
0x8ee6869d == 0xd4bcdcc7 ^ 0x5a5a5a5a: Die Tag-Relation beweist, dass die zurückgespiegelten Bytes ein gültiger Marker aus einer anderen Allokation sind.
Exit-Codes: 0 = Evidenz beobachtet, 3 = keine, 2 = Setup-Fehler,
1 = Nutzungs-/Laufzeitfehler.
Das Race ist probabilistisch. Auf dem Testhost reproduzierte --workers 6 --spray 2 --spray-rate 1000 über mindestens 3 Minuten das Problem; ein 60-sekündiger Lauf mit
--workers 4 --spray 1 --spray-rate 500 erzielte auf demselben Host null Treffer.
Ziehen Sie aus einem kurzen Lauf keine Schlussfolgerung „nicht anfällig“.