
PoC proof-only per CVE-2026-98432, un use-after-free in ireq_opt su Linux IPv4/TCP: trigger di race e oracle di riutilizzo dell'heap che conferma la UAF senza esecuzione di codice.
ireq_opt IPv4/TCP di LinuxProof of concept a solo scopo dimostrativo per CVE-2026-98432 (CVSS 9.8): un use-after-free attivabile da remoto nello stack IPv4/TCP di Linux.
Solo per test di sicurezza autorizzati. Eseguirlo contro sistemi di tua proprietà o per i quali hai esplicita autorizzazione al test.
Il PoC attiva la race e rileva quando il kernel copia byte da un oggetto
ip_options_rcu liberato in un SYN-ACK. Non crea utenti o file e non
implementa una catena di esecuzione di codice.
Un hit significa che il server ha restituito il marker di un'altra allocazione
all'interno del proprio SYN-ACK — un kernel corretto non può farlo. In un test
controllato su Ubuntu 24.04.5 con kernel 6.8.0-147-generic (indicato come
vulnerabile da Ubuntu; fix in coda in 6.8.0-149), un'esecuzione di 180
secondi su loopback ha prodotto 21 hit su 320.829 tentativi, e
un'esecuzione di 300 secondi ha prodotto 22 hit su 616.911 tentativi. Il
kernel è rimasto pulito (tainted=0, nessun oops).
Nessuna cattura di pacchetti o log da quell'host è inclusa qui; l'output del PoC stesso costituisce la prova.
Un SYN che trasporta opzioni IPv4 fa sì che il kernel costruisca un
ip_options_rcu (72 byte, kmalloc-96) per il request socket. Quando
l'handshake si completa, tcp_v4_syn_recv_sock() passa quell'oggetto al child
socket senza copiarlo, poi azzera il puntatore nel request socket. Una
seconda CPU che ha già risolto il request socket può trovarsi ancora dentro
tcp_check_req() → tcp_v4_send_synack() → ip_build_and_send_pkt() →
ip_options_build(), leggendo lo stesso oggetto. I child socket non sono
SOCK_RCU_FREE, e inet_sock_destruct() libera inet_opt con un semplice
kfree(), quindi se il child viene distrutto in quella finestra il lettore
dereferenzia memoria liberata.
Esiste una seconda variante con la migrazione del request socket
(net.ipv4.tcp_migrate_req=1 o un programma BPF reuseport); il fix usa
kfree_rcu() sia in inet_sock_destruct() che in tcp_v4_reqsk_destructor().
Introdotto in Linux 4.4 (079096f103fa); corretto in mainline 7.3 e
backportato a 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.56 e
7.2.10. Affetti: Linux 4.4 e successivi, non patchati.
L'impatto va da un crash remoto (DoS) alla divulgazione di memoria del kernel e, con una catena di exploit completa, all'esecuzione di codice. Questo repository si ferma alla dimostrazione dell'use-after-free.
Per ogni tentativo:
ireq_opt; il target ripete l'opzione nel proprio SYN-ACK.inet_opt, il request
socket elimina il proprio puntatore.Oracolo. Ogni tentativo incorpora un magic univoco a 32 bit più un tag di controllo agli offset 16..23 dell'opzione. Tre riscritture avvengono prima che un lettore in user space veda l'opzione:
| fase | slot |
|---|---|
ip_options_compile() del server sul SYN | 4..7 (TS) / 3..6 (RR) |
ip_options_build() del server per il SYN-ACK | 8..11 (TS) / 7..10 (RR) |
ip_options_compile() dell'host ricevente sul SYN-ACK | 12..15 (TS) / 11..14 (RR) |
Il marker si trova oltre tutte e tre. Un SYN-ACK che ripete il magic di una
tag == magic ^ 0x5a5a5a5a) prova che il lettore
ha copiato da un oggetto liberato e riutilizzato.CAP_NET_RAW; il filtro RST automatico richiede
CAP_NET_ADMIN (root).accept().iptables/nftables per il filtro bare-RST.--proof è autosufficiente su un singolo host: avvia un listener su loopback
e lo mette in race.
python3 poc_cve_2026_98432.py --selftest
sudo python3 poc_cve_2026_98432.py --check
Stampa il kernel in esecuzione, la tabella delle release corrette e i sysctl rilevanti.
sudo python3 poc_cve_2026_98432.py --proof 9000 \
--duration 180 --workers 6 --spray 2 --spray-rate 1000 \
--pcap /tmp/proof.pcap
Avvia un listener su 127.0.0.1:9000, esegue la race, stampa un verdetto, arresta
il listener e rimuove la regola firewall temporanea. Non viene creato nulla
sull'host — solo stato TCP transitorio.
sudo python3 poc_cve_2026_98432.py --probe <TARGET>:<PORT>
Un SYN: riporta se il target è un listener MPTCP e se restituisce l'opzione IPv4 (richiesta dall'oracolo).
sudo python3 poc_cve_2026_98432.py <TARGET>:<PORT> \
--workers 16 --spray 4 --duration 600 --pcap hits.pcap
Gli indirizzi pubblici vengono rifiutati a meno che non venga specificato --allow-public. Il filtro bare-RST viene installato automaticamente e rimosso all'uscita.
python3 poc_cve_2026_98432.py --listen 9000 --mptcp
Si associa a 127.0.0.1; aggiungi --listen-any solo per un host attaccante remoto. Il listener accetta e chiude immediatamente ogni connessione con SO_LINGER(0), liberando rapidamente il processo figlio.
| campo | significato |
|---|---|
attempts | handshake avviati |
synacks | SYN-ACK osservati (circa due per tentativo) |
hits | il SYN-ACK conteneva il marker di un'altra allocazione → UAF confermato |
anomalies | marker analizzato ma tag non corrispondente (corruzione parziale) |
leaks | valore simile a un puntatore del kernel nei byte restituiti |
noopt | SYN-ACK senza l'opzione restituita (syncookies/coda piena/rimozione) |
stale | ritrasmissione tardiva di SYN-ACK filtrata (non un hit) |
malformed | SYN-ACK con ihl < 5 (firma di underflow da optlen obsoleto) |
timeouts | nessun SYN-ACK entro --timeout |
spray | SYN di heap-spray inviati |
Esempio di 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: la relazione tra i tag dimostra che i byte restituiti sono un marker valido proveniente da un'allocazione diversa.
Codici di uscita: 0 = evidenza osservata, 3 = nessuna, 2 = errore di configurazione, 1 = errore di utilizzo/runtime.
La race è probabilistica. Sull'host di test, --workers 6 --spray 2 --spray-rate 1000 per almeno 3 minuti l'ha riprodotta; un'esecuzione di 60 secondi con --workers 4 --spray 1 --spray-rate 500 ha prodotto zero riscontri sullo stesso host. Non concludere "non vulnerabile" da un'esecuzione breve.