
Copia Fallita: 732 Byte per Root su Ogni Principale Distribuzione Linux.
732 byte. Qualsiasi distro. Root.
Un difetto logico lineare nel template crittografico
authencesndel kernel Linux consente a un utente locale non privilegiato di eseguire una scrittura precisa e controllata di 4 byte nella page cache di qualsiasi file leggibile — inclusi i binari setuid. Nessuna race condition. Nessun tentativo. Nessuna ricompilazione. Root su ogni distribuzione Linux principale rilasciata dal 2017.
📄 Write-up tecnico · 🔗 Patch del kernel · 🛡️ CVSS: Critico
| Distro | Versione Kernel |
|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 |
| SUSE 16 | 6.12.0-160000.9-default |
Tutte e quattro sono state compromesse utilizzando lo stesso identico script Python di 732 byte, senza modifiche.
AF_ALG espone il sottosistema crittografico del kernel a utenti non privilegiati. splice() trasferisce i dati dei file in una pipe per riferimento — passando le pagine della page cache direttamente, senza copiarle. Quando un utente esegue lo splice di un file in un socket AEAD AF_ALG, lo scatterlist di input del socket contiene riferimenti live alle pagine cache del kernel di quel file.
In algif_aead.c, l'ottimizzazione in-place del 2017 copiava AAD e ciphertext dallo scatterlist TX nel buffer RX, ma concatenava le pagine del tag di autenticazione per riferimento usando sg_chain(), quindi impostava req->src = req->dst:
Input SGL: [ AAD | CT | Tag ]
^
└─ sg_chain() → punta ancora alle pagine della page cache
Output SGL: [ AAD | CT ] ──→ [ Tag (pagine della page cache) ]
(buffer RX) (concatenate dall'SGL TX)
req->src ──┐
├──→ stesso scatterlist combinato
req->dst ──┘
Le pagine della page cache provenienti da splice() ora si trovavano all'interno di uno scatterlist di destinazione scrivibile, separate dalla regione di scrittura legittima solo da un confine di offset. Nulla nell'API imponeva che gli algoritmi rimanessero entro i limiti.
authencesnauthencesn è un wrapper AEAD utilizzato da IPsec per il supporto Extended Sequence Number (ESN) a 64 bit. Per riorganizzare i byte ESN per il calcolo HMAC, utilizza il buffer di destinazione del chiamante come spazio scratch — inclusa una scrittura all'offset assoclen + cryptlen, che si trova oltre il confine del tag di autenticazione:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // legge AAD[0..7]
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // sovrascrive dst[4..7]
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // ← scrive oltre il tag
La terza chiamata scrive 4 byte (seqno_lo) a dst[assoclen + cryptlen]. Nel percorso in-place di AF_ALG, lo scatterwalk attraversa dal buffer RX alle pagine del tag della page cache concatenate. Il kernel mappa la pagina della page cache tramite kmap_local_page e scrive direttamente nella copia cache del file di destinazione.
L'HMAC quindi fallisce (il ciphertext è fabbricato), recvmsg() restituisce un errore — ma la scrittura di 4 byte persiste permanentemente.
Nessuna singola modifica era individualmente sbagliata. La vulnerabilità vive all'intersezione di tutte e tre.
Il target predefinito è /usr/bin/su, un binario setuid-root presente su tutte le distribuzioni testate.
Passo 1 — Configurazione del socket
Apri socket AF_ALG, bind a authencesn(hmac(sha256),cbc(aes))
Imposta la chiave. Accetta il socket di richiesta. (Nessun privilegio richiesto.)
Passo 2 — Ciclo di scrittura (una volta per ogni blocco di shellcode di 4 byte)
sendmsg() → i byte AAD [4:8] trasportano i 4 byte da scrivere (seqno_lo)
splice() → le pagine della page cache del file di destinazione nel socket AF_ALG
recv() → attiva decrypt → authencesn scrive seqno_lo nella page cache
(recvmsg restituisce un errore; la scrittura persiste)
Passo 3 — Esecuzione
execve("/usr/bin/su")
Il kernel carica il binario dalla page cache (ora corrotta)
Il binario setuid-root esegue lo shellcode iniettato → UID 0
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... imposta chiave, accetta socket di richiesta u ...
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
u.recv(...) # attiva la scrittura nella page cache
Aggiorna a un kernel contenente la patch a664bf3d603d. Il fix riporta algif_aead.c al funzionamento out-of-place: req->src punta all'SGL TX; req->dst punta al buffer RX. Le pagine della page cache provenienti da splice() rimangono in sola lettura. Il meccanismo sg_chain() che le collegava alla destinazione scrivibile viene rimosso.
// Prima (vulnerabile): src e dst condividono lo stesso scatterlist
aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src, rsgl_src, used, ctx->iv);
// Dopo (corretto): src è l'SGL TX, dst è il buffer RX — completamente separati
aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, rsgl_dst, used, ctx->iv);
Disabilita il modulo kernel algif_aead:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
Oppure blocca la creazione di socket AF_ALG tramite una policy seccomp nei tuoi profili di workload.
Nota per ambienti container: Poiché la page cache è condivisa a livello di host, questa vulnerabilità attraversa i confini dei container. Applica le mitigazioni a livello di nodo, non solo per-pod. Vedi la Parte 2 per i dettagli completi sulla fuga da Kubernetes.
Il ricercatore Theori Taeyang Lee ha identificato, attraverso precedenti lavori su kernelCTF, che AF_ALG + splice() crea un percorso in cui utenti non privilegiati possono fornire pagine della page cache direttamente al sottosistema crittografico — e che la provenienza delle pagine nello scatterlist era una classe di vulnerabilità poco esplorata.
Il team di ricerca ha utilizzato Xint Code per scalare questa intuizione sull'intero sottosistema crypto/ con il seguente prompt operatore:
"Questo è il sottosistema crypto/ di Linux. Esamina tutti i percorsi di codice raggiungibili dalle syscall userspace. Nota un'osservazione chiave: splice() può consegnare riferimenti alla page cache di file in sola lettura (inclusi i binari setuid) agli scatterlist TX crittografici."
Dopo circa un'ora di analisi automatizzata, Copy Fail è stato l'output a più alta severità. Ulteriori vulnerabilità scoperte durante la stessa scansione rimangono in divulgazione coordinata.
Parte 2: Da Pod a Host — come Copy Fail evade ogni piattaforma Kubernetes cloud principale. In arrivo.
| Proprietà | Dettaglio |
|---|
| Deterministico | Difetto logico lineare — nessuna race condition, nessuna finestra temporale, nessun tentativo |
| Portabile | Stesso script, stessi byte, funziona su tutte le distro e architetture testate |
| Minuscolo | Script Python di 732 byte che utilizza solo la libreria standard (os, socket, zlib). Richiede Python 3.10+ per os.splice |
| Furtivo | La pagina corrotta non viene mai marcata come dirty. I checksum su disco rimangono invariati; solo la page cache in memoria viene modificata |
| Cross-container | La page cache è condivisa a livello di sistema attraverso i confini dei container — questo è anche una primitiva di fuga dal nodo Kubernetes (vedi Parte 2) |
| Variabile | Controllata Tramite |
|---|
| File di destinazione | Qualsiasi file leggibile dall'utente corrente |
| Offset di scrittura | assoclen, offset di splice e lunghezza di splice |
| Valore scritto | Byte 4–7 dell'AAD fornito in sendmsg() (seqno_lo) |
| Anno | Evento |
|---|
| 2011 | authencesn aggiunto al kernel (a5079d084f8b) per il supporto ESN di IPsec. La scrittura scratch esisteva ma era innocua — solo il layer interno xfrm la chiamava, e l'AAD viveva in uno scatterlist separato. |
| 2015 | AF_ALG acquisisce il supporto AEAD. authencesn convertito alla nuova interfaccia AEAD (104880a6b470), introducendo l'offset di scrittura assoclen + cryptlen. Ancora out-of-place: le pagine della page cache erano in src (sola lettura). Non ancora sfruttabile. |
| 2017 | Ottimizzazione in-place aggiunta a algif_aead.c (72548b093ee3). req->src = req->dst. Le pagine del tag della page cache concatenate nella destinazione scrivibile. Vulnerabilità formata. |
| 2026-03-23 | Segnalata al team di sicurezza del kernel Linux. |
| 2026-04-01 | Patch integrata in mainline. |
| 2026-04-22 | Assegnato CVE-2026-31431. |
| 2026-04-29 | Divulgazione pubblica. |
| Data | Evento |
|---|
| 2026-03-23 | Vulnerabilità segnalata al team di sicurezza del kernel Linux |
| 2026-03-24 | Ricevuta conferma iniziale |
| 2026-03-25 | Patch proposte e revisionate |
| 2026-04-01 | Patch committate nel kernel mainline |
| 2026-04-22 | Assegnato CVE-2026-31431 |
| 2026-04-29 | Divulgazione pubblica |