
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.
| 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) |
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.
| 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. |
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);