
Kernel Linux nf_tables Use-After-Free (CVE-2026-23111) — LPE PoC
Linux Kernel nf_tables Use-After-Free (Escalatazione Locale dei Privilegi) poc di baba01hacker
net/netfilter/nf_tables_api.c — nft_map_catchall_activate()| Intervallo di Versioni | Risolta In |
|---|---|
| 6.19-rc1 a 6.19-rc8 | 6.19-rc9+ |
| 6.13 a 6.18.9 | 6.18.10 |
| 6.7 a 6.12.69 | 6.12.70 |
| 6.4.1 a 6.5.x | EOL (nessuna fix stabile) |
| 6.3.10 a 6.3.x | EOL |
| 6.0.x, 6.2.x | EOL |
| 6.1.36 a 6.1.162 | 6.1.163 |
| 5.15.121 a 5.15.199 | 5.15.200 |
| 5.10.188+, 5.4.262+, 4.19.316+ | Vari LTS |
CVE-2026-23111 è un use-after-free di nf_tables causato da un controllo inversion della maschera di generazione in nft_map_catchall_activate(). Durante il rollback di una transazione fallita, gli elementi catchall delle mappe di verdetto che erano stati disattivati da NFT_MSG_DELSET devono essere riattivati in modo che i riferimenti, come i riferimenti alle catene NFT_GOTO, vengano ripristinati. Il percorso di attivazione catchall ha invece saltato gli elementi inattivi e ha processato quelli attivi, lasciando gli elementi catchall disattivati non ripristinati.
La logica di attivazione corretta (non catchall) è:
if (nft_set_elem_active(ext, iter->genmask))
return 0; /* salta attivo, processa inattivo */
Il percorso catchall vulnerabile utilizzava la condizione opposta:
if (!nft_set_elem_active(ext, genmask))
continue; /* salta inattivo, processa attivo */
Quando una mappa di verdetto pipapo cancellata viene ripristinata, questo impedisce l'esecuzione di nft_setelem_data_activate() per l'elemento catchall inattivo. Per i dati di verdetto NFT_JUMP/NFT_GOTO, il riferimento alla catena preso da nft_data_hold() non viene quindi ripristinato. Ripetendo la sequenza abort/toggle/cancellazione si può portare chain->use a zero mentre un'altra regola contiene ancora un riferimento di verdetto valido alla catena. Un successivo DELCHAIN può quindi liberare la catena mentre è ancora raggiungibile, creando il UAF.
Crea tabella, catena base, catena vittima e mappa di verdetto pipapo con catchall NFT_GOTO -> vittima
|
Batch 1: cancella il set pipapo, poi forza un errore di transazione
|
Il percorso di abort chiama nft_map_catchall_activate(), ma il controllo invertito salta il catchall inattivo
|
Il catchall rimane inattivo e il contatore di riferimento della catena vittima non viene ripristinato
|
Batch 2: commetti una transazione benigna per alternare il cursore di generazione
|
Batch 3: cancella con successo il set pipapo, decrementando nuovamente il riferimento della catena vittima
|
Batch 4: cancella la catena vittima mentre il verdetto della catena base ancora la riferisce
|
Use-after-free quando la valutazione del pacchetto o il dump delle regole segue il riferimento di verdetto dangling
La fix upstream è rimuovere la negazione in modo che nft_map_catchall_activate() corrisponda a nft_mapelem_activate(): gli elementi attivi vengono saltati, quelli inattivi vengono riattivati, e i riferimenti di verdetto/oggetto vengono ripristinati durante la gestione dell'abort.
python3 CVE-2026-23111-checker.py --detailed
python3 CVE-2026-23111-checker.py --json
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_full.c && gcc -Wall -O2 -o exploit_full exploit_full.c -lnftnl -lmnl && ./exploit_full -d
Usa questo se il PoC full/v1 fallisce sul kernel target.
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_v2.c && gcc -Wall -O2 -o exploit_v2 exploit_v2.c -lnftnl -lmnl && ./exploit_v2 -d
Installa prima le dipendenze di build se il compilatore non trova gli header libmnl/libmnl.h o libnftnl:
scripts/install-build-deps.sh
# oppure
make deps
make # Build exploit (solo PoC)
make v2 # Build exploit v2 (approccio FuzzingLabs — prova quando v1 fallisce)
make full # Build exploit LPE completo
make v3 # Build v3 variante calibrata eval-path
make run-d # Esegui PoC con output di debug
make run-v2-d # Esegui v2 con output di debug
make run-v3 # Mostra uso/aiuto v3
make run-full # Esegui LPE completo
Crea un set di mappe pipapo con un elemento catchall (goto catena vittima), quindi usa il bug della genmask invertita durante un abort di DELSET per corrompere chain->use. Dopo aver avanzato il contatore di generazione, DELCHAIN ha successo nonostante i riferimenti dangling, liberando la catena.
Dopo il UAF, la memoria di chain->name viene liberata. Spargiamo strutture seq_operations (32 byte) aprendo /proc/self/stat per reclamare lo slot della slab cache liberato. Leggendo la regola di verdetto immediato della catena base tramite NFT_MSG_GETRULE si ottiene il dump del nome della catena reclamato, rivelando puntatori a funzioni del kernel. La validazione dei puntatori assicura che siano accettati solo indirizzi canonici del testo del kernel.
Riattiva il UAF con un nome di catena più lungo (140 byte → kmalloc-cg-192), poi sparge oggetti nft_rule per reclamare la memoria. I puntatori list_head trapelati rivelano indirizzi dell'heap nella regione della mappa diretta.
nft_expr_ops e nft_rule_blob fittiziblob_gen_0 della catena liberatacommit_creds(&init_cred) → switch_task_namespaces → swapgs; iretq/proc/kcoreexpr->ops->eval dirottatoL'exploit v2 (exploit_v2.c) utilizza le tecniche descritte nel writeup di FuzzingLabs e può essere usato quando il PoC v1 fallisce. Differenze chiave:
| Componente | v1 (exploit.c) | v2 (exploit_v2.c) |
|---|---|---|
| Attivazione UAF | Singolo set + regola goto immediata | Due set (stile FuzzingLabs) + regola lookup |
| Lettura KASLR | NFT_MSG_GETRULE (dump regole) | NFT_MSG_GETELEM (dump elementi) + fallback GETRULE |
| Leak heap | Spray nft_rule + leak list_head | Catena di lettura arbitraria init_ipc_ns |
| Host ROP | nft_rules + userdata tabella | msg_msg-2k (messaggio da 2048 byte) |
| Attivazione hijack | Valutazione pacchetto su goto immediata | Valutazione pacchetto su catchall del set superstite |
| Escalation privilegi | commit_creds + switch_task_namespaces | Sovrascrittura modprobe_path + disabilitazione SELinux |
| Percorso di ritorno | swapgs; iretq | msleep() (rimane nel kernel) |
init_ipc_ns + 0x118 → msg_ids.xa_head → msg_queue (puntatore taggato)
msg_queue + 0xc0 → q_messages.next → indirizzo heap msg_msg