Blog
Kernel LinuxPrivilege EscalationBad EpollGenerative Pentest

Bad Epoll (CVE-2026-46242): una race di 6 istruzioni che dà root a qualsiasi utente Linux

Bad Epoll (CVE-2026-46242) è un use-after-free del kernel Linux che dà root a qualsiasi utente non privilegiato — su server e su Android. La patch esisteva da aprile ma è rimasta muta per 70 giorni. Ecco il runbook di remediation.

Zero Hunt Research··8 min di lettura

Un use-after-free nel sottosistema epoll del kernel Linux, reso pubblico il 3 luglio, consente a qualsiasi processo locale non privilegiato di diventare root — su server e desktop Linux e sugli smartphone Android con kernel serie 6.6 o più recenti. L'exploit è affidabile al 99% sul target di riferimento, nonostante una finestra di race larga appena sei istruzioni macchina. La correzione upstream è nell'albero mainline dal 24 aprile, ma è arrivata in silenzio, è rimasta senza annuncio per circa 70 giorni e molte distribuzioni non avevano ancora rilasciato un backport quando è uscito il writeup. Non esiste un flag di configurazione che disattivi epoll. Questa combinazione — local-to-root banale, base installata enorme e una patch che esisteva ma che nessuno diceva di applicare — è il motivo per cui CVE-2026-46242, soprannominata "Bad Epoll", merita di fermarsi ad analizzarla.

Cosa fa davvero Bad Epoll al kernel

Epoll è l'interfaccia di notifica degli eventi di I/O su cui si appoggia praticamente ogni servizio di rete Linux moderno — web server, database, message broker, container runtime. Il bug vive in ep_remove(). La funzione azzera file->f_ep sotto file->f_lock, poi continua a usare lo stesso file object dentro la sezione critica attraverso hlist_del_rcu() e spin_unlock(). Una __fput() concorrente — il percorso di rilascio del file del kernel — può osservare un NULL transitorio, saltare eventpoll_release_file() e procedere dritta a f_op->release. Questo libera una struct eventpoll ancora in uso. Use-after-free da manuale, in uno dei code path più esercitati del kernel.

La logica difettosa risale a un singolo commit dell'aprile 2023: ecco perché i kernel precedenti al mainline 6.4 — e i dispositivi Android ancora su 6.1, come il Pixel 8 — sono anteriori al bug e non sono vulnerabili. Tutto dal 6.4 in poi lo è.

Il ricercatore, Jaeyoung Chung del CompSec Lab della Seoul National University, ha trasformato quell'UAF in un exploit armato e lo ha sottomesso al programma kernelCTF di Google (bounty: 71.337 dollari). Il writeup pubblico con proof-of-concept concatena quattro file descriptor epoll collegati, ripete la race finché non la vince, poi usa l'oggetto liberato per ottenere una lettura arbitraria della memoria del kernel tramite /proc/self/fdinfo e si aggancia a una catena ROP. Affidabilità dichiarata: 99% sul target kernelCTF LTS-6.12.67, 98% su COS-121. Una finestra di sei istruzioni sembra invincibile a mano; in un loop stretto, ci pensa la statistica.

Perché l'auditing AI statico non ha visto la race di Bad Epoll

Ecco la parte che dovrebbe interessare chiunque scommetta il proprio programma di sicurezza sulla code review automatica. Lo stesso codice epoll è stato analizzato da un modello AI di frontiera — Mythos di Anthropic — che ha individuato una race vicina, oggi tracciata come CVE-2026-43074. Non ha trovato Bad Epoll. Le due ragioni indicate sono entrambe strutturali, non accidentali:

  • La finestra è larga sei istruzioni. Ragionare staticamente su quale esatto interleaving dei thread liberi l'oggetto mentre un altro percorso lo tiene ancora è quasi intrattabile a ispezione. Il codice vulnerabile sembra corretto, a meno di immaginare lo scheduling preciso che lo rompe.
  • C'era pochissimo segnale a runtime. Una volta corretta CVE-2026-43074, lo use-after-free di Bad Epoll di solito non fa scattare KASAN, il principale rilevatore di errori di memoria del kernel. Un fuzzer o un auditor assistito da runtime non riceve alcun campanello d'allarme nella maggior parte delle esecuzioni.

"Abbiamo trovato quella rumorosa e rilasciato una patch. Quella silenziosa, nelle stesse venti righe, richiedeva uno specifico interleaving di sei istruzioni che né il ragionamento statico né KASAN fanno emergere in modo affidabile."

È la lezione onesta, e va contro una storia rassicurante che l'industria continua a raccontarsi. L'analisi statica — umana o AI — è pattern-matching su codice che sembra pericoloso. Una race così stretta non sembra pericolosa. Ciò che la trova davvero è eseguire la race, migliaia di volte, contro lo scheduler reale, e misurare se ne esce root. Qui il rilevamento è empirico, non analitico.

La patch rimasta in bella vista per 70 giorni

La timeline è un caso di studio su perché "corretto upstream" non significa "protetto":

Data Evento
Aprile 2023 Il commit introduce le due race di epoll
2026-02-17 Chung segnala il difetto
2026-04-02 Prima fix merged — incompleta, non lo chiude del tutto
2026-04-22 Segnalato di nuovo il difetto residuo
2026-04-24 Arriva la fix corretta: commit mainline a6dc643c6931
~2026-07-03 Writeup pubblico + PoC; circa 70 giorni dopo la fix vera

Per quei 70 giorni la fix è stata in mainline, non etichettata come critica per la sicurezza, mentre i kernel delle distribuzioni erano in ritardo e nessun advisory diceva agli operatori di darle priorità. Canonical, Red Hat e il Debian Security Team si muovono in fretta una volta che un CVE di questa portata è pubblico — ma la finestra di esposizione la definisce la cadenza di patching della tua flotta, non la data del commit upstream. E poiché Bad Epoll è una privilege escalation locale, non una RCE remota, raramente arriva per prima. È il secondo stadio: un bug in una web app, una chiave SSH trapelata o una dipendenza avvelenata dà all'attaccante una shell come account di servizio a basso privilegio, e Bad Epoll trasforma quella shell in root — in silenzio, senza crash, senza callback di rete, senza splat di KASAN.

Remediation

Trattala come un evento di priorità-patch su qualsiasi host dove utenti non fidati, codice non fidato o servizi esposti su internet possano ottenere una shell locale — cioè la maggior parte.

1. Sono vulnerabile?

Controlla la versione del kernel in esecuzione:

uname -r

Mainline 6.4 e successivi sono vulnerabili; 6.1 e precedenti no. Su Android sono vulnerabili i kernel serie 6.6 e successivi (incluso l'hardware Pixel attuale). Le stringhe di versione sulle distribuzioni sono alterate dai backport, quindi il segnale affidabile è l'advisory della tua distro per CVE-2026-46242, non il numero grezzo. Verifica se la fix è presente:

# Debian/Ubuntu — il kernel corretto è installato e avviato?
apt-changelog linux-image-$(uname -r) 2>/dev/null | grep -i CVE-2026-46242 || \
  echo "Nessuna fix CVE-2026-46242 registrata per il kernel in esecuzione"

2. Patch — la correzione esatta

Non c'è un singolo numero di versione magico; la fix è il commit upstream a6dc643c6931 (arrivato il 2026-04-24). L'azione pratica è installare il kernel backportato della tua distribuzione e riavviare — un kernel vivo mantiene il codice vulnerabile residente finché non viene sostituito. Segui direttamente gli advisory dei vendor: Ubuntu USN, Debian DSA, Red Hat RHSA, SUSE, e il bollettino di sicurezza mensile del tuo vendor Android. Non accettare "pacchetto aggiornato" come fatto: verifica che il kernel avviato contenga la fix.

3. Non puoi riavviare subito? Controlli compensativi

Non esiste un workaround che disattivi epoll — il kernel non può funzionare senza. Riduci invece chi può raggiungere la primitiva:

  • Riduci la superficie locale: niente shell non fidate, niente container non fidati che condividono il kernel dell'host, SSH irrigidito.
  • Rafforza il confine dei container — un kernel host condiviso significa che il root locale di un container è un problema dell'host. Valuta un isolamento più forte (gVisor, Kata, nodi dedicati) per i workload non fidati.
  • Applica kernel.yama.ptrace_scope=2 e profili seccomp per rallentare il pivoting post-exploitation; non fermano Bad Epoll ma alzano il costo di ciò che viene dopo.

4. Caccia alla compromissione

Bad Epoll è privilege escalation post-accesso — MITRE ATT&CK T1068: Exploitation for Privilege Escalation. Segnali da cacciare:

  • Un processo non setuid che passa a euid 0 senza un corrispondente evento sudo/su/PAM — la spia di un'escalation in memoria. Correla i record auditd execve/setresuid con i log di autenticazione.
  • Apertura/lettura ripetuta e insolita di /proc/self/fdinfo da un processo non-root, insieme a raffiche dense di epoll_create1/epoll_ctl — l'impronta dell'exploit. eBPF (ad es. una probe bpftrace su sys_enter_epoll_ctl per PID) coglie il fan-out anomalo.
  • Log del kernel: voci sporadiche KASAN o general protection fault nei path di epoll su build hardened, o oops del kernel inspiegabili.
  • Follow-through ATT&CK post-escalation: nuove unit root cron/systemd (T1053), creazione di account (T1136) o accesso a credenziali (T1003) poco dopo un cambio di privilegio inspiegabile.

5. Bonifica e verifica

Se trovi evidenza di sfruttamento: ricostruisci l'host — un attaccante che ha raggiunto root può nascondersi in stato del kernel che non puoi auditare in modo affidabile dallo userland. Ruota ogni credenziale, chiave e token che ha toccato la macchina (chiavi SSH, token di service account, credenziali dell'istanza cloud, segreti in cache). Applica la patch al kernel, riavvia, e solo allora conferma la pulizia — verificare prima che il kernel vulnerabile sia sparito non verifica nulla.

Dove ci lascia il rilevamento — e Zero Hunt

Bad Epoll è un argomento compatto contro un'assunzione comoda: che leggere il codice — anche con un'AI capace che lo legge — dica se è sfruttabile. Una race di sei istruzioni che un modello di frontiera ha analizzato e superato senza vederla è la prova che alcune classi di bug si rivelano solo quando esegui l'exploit contro il kernel reale e conti le vittorie. Qui il rilevamento è empirico.

È la premessa progettuale dello swarm di pentest generativo a 10 agenti di Zero Hunt. I suoi agenti Post-Exploit e Pivot non fanno grep di firme CVE; scrivono un tentativo di escalation fresco contro l'ambiente target, lo eseguono in una sandbox Docker effimera e hardened con gVisor — così la race gira contro il kernel reale, mai contro l'host dell'appliance — e registrano se root è davvero uscito. Ogni skill candidata è retro-testata nell'AI Gym (142+ skill auto-evolutive, validate contro Vulhub, NYU CTF Bench e i 314 task black-box derivati da CVE di Vulhub-Bench) prima di toccare un engagement reale, così un exploit probabilistico come Bad Epoll è caratterizzato dal suo tasso di successo reale, non da un banner statico "sembra patchato" letto da una stringa di versione. Quando lo swarm conferma un percorso di escalation, il finding è firmato ECDSA al momento della scrittura e mappato su ATT&CK T1068 — un record difendibile del fatto che quell'host era raggiungibile a root in quella data, non l'ipotesi di un analista. La review statica, umana o automatica, ti ha detto che il codice sembrava a posto per 70 giorni. Eseguirlo ti ha detto la verità in un pomeriggio.

Validare in continuo se un punto d'appoggio a basso privilegio può raggiungere root — prima che lo faccia un attaccante al posto tuo — è la differenza tra conoscere la propria cadenza di patch e conoscere la propria esposizione. Scopri come lo swarm generativo lo esegue sulla piattaforma, oppure contattaci.