
PoC di exploit in Rust per LPE del kernel Linux CVE-2026-31431 (scrittura page-cache AF_ALG) più difesa runtime eBPF che blocca la creazione di socket AF_ALG tramite LSM o kprobe senza riavvio.
CVE-2026-31431 (Copy Fail) è una vulnerabilità di escalation dei privilegi locali nel modulo algif_aead del kernel Linux (sottosistema AF_ALG). Un difetto logico introdotto ad agosto 2017 consente a qualsiasi utente locale non privilegiato di scrivere 4 byte controllati nella cache delle pagine di qualsiasi file leggibile tramite AF_ALG + splice(), per poi eseguire un binario setuid corrotto e ottenere i privilegi di root. L'exploit è deterministico — nessuna race condition, nessun offset del kernel, nessun crash di sistema. Colpisce tutte le principali distribuzioni Linux che includono kernel dalla versione 4.14 alla 7.0-rc.
CVSS: 7.8 | Corretto in: kernel 7.0, 6.19.12, 6.18.22 | Fix mainline: commit a664bf3d603d
Fonti: copy.fail, The Hacker News, avviso CloudLinux
crates/
├── exp/ # Exploit PoC (reimplementazione in Rust)
├── copy_fail_guard/ # Loader eBPF in userspace — modalità LSM
├── copy_fail_guard-ebpf/ # Programma eBPF LSM (non nel workspace)
├── copy_fail_guard_kprobe/ # Loader eBPF in userspace — modalità kprobe
└── copy_fail_guard_kprobe-ebpf/ # Programma eBPF kprobe (non nel workspace)
scripts/
└── build_guard.sh # Build con un clic → output in dist/
La causa principale è una catena di tre funzionalità indipendenti del kernel che interagiscono in modo non sicuro:
AF_ALG — espone l'API crittografica del kernel a utenti non privilegiatisplice() — trasferisce i dati dei file a copia zero come riferimenti alla cache delle pagine (non copie) nello scatterlist crittograficoauthencesn — usa il buffer di output del chiamante come spazio di lavoro, scrivendo 4 byte in dst[assoclen + cryptlen]Nel 2017, un'ottimizzazione in-place in algif_aead.c (72548b093ee3) ha reso req->src == req->dst, concatenando le pagine della cache delle pagine nello scatterlist di destinazione scrivibile. Quando authencesn scrive i suoi byte di lavoro, oltrepassa il buffer di output entrando nelle pagine della cache delle pagine concatenate. L'attaccante controlla:
La pagina corrotta non viene mai marcata come sporca — il file su disco non viene toccato, ma execve() legge dalla cache delle pagine. Corrompi un binario setuid → root.
Reimplementazione in Rust del PoC Python pubblico di 732 byte. Prende di mira /usr/bin/su, esegue lo splice delle sue pagine nella cache delle pagine in un socket AEAD AF_ALG e le sovrascrive con un payload shell compresso.
Richiede Rust 1.85+ (edition 2024).
cargo build --release -p copy_fail
Il binario è solo per Linux. Su altre piattaforme termina con un errore Unsupported.
Avvertenza: questo sfrutta una reale vulnerabilità del kernel. Eseguilo solo su sistemi di tua proprietà e sotto il tuo controllo, idealmente una VM usa e getta.
Avvia una VM con un kernel vulnerabile (qualsiasi distribuzione mainstream con kernel < 7.0 / < 6.19.12 / < 6.18.22).
Copia il binario compilato nella VM ed eseguilo come utente non root:
./target/release/copy_fail
Vulnerabile: appare una shell root (#). Esegui whoami per confermare root.
Non vulnerabile (kernel patchato): l'operazione AF_ALG fallisce oppure il binario su si comporta normalmente. Vedrai un errore o il normale prompt della password di su.
Una difesa runtime del kernel che blocca CVE-2026-31431 senza aggiornare il kernel o riavviare. Usa eBPF per intercettare la creazione dei socket AF_ALG, interrompendo il primo passo dell'exploit.
Sono disponibili due modalità. Il loader con un clic seleziona automaticamente la migliore modalità disponibile:
| Modalità LSM | Modalità kprobe | |
|---|---|---|
| Come blocca | Restituisce -EPERM (creazione socket negata) | SIGKILL (processo terminato) |
| Requisito kernel | ≥ 5.7 con parametro di avvio lsm=bpf | ≥ 5.3, nessun parametro speciale |
| Richiede riavvio per attivarsi | Forse (se lsm=bpf non è già impostato) | No |
| Punto di hook | Hook LSM socket_create | kprobe __sys_socket |
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • Rileva il supporto BPF LSM │
│ • LSM disponibile → copy_fail_guard (EPERM) │
│ • LSM non disponibile → copy_fail_guard_kprobe (KILL)│
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ Programma eBPF │
│ se famiglia socket == 38 (AF_ALG) │
│ → blocca (EPERM o SIGKILL) │
│ altrimenti │
│ → consenti │
└──────────────────────────────────────────────────┘
Bloccare AF_ALG ha un impatto quasi nullo sui sistemi tipici:
AF_ALGAF_ALG (ad es. OpenSSL con l'engine afalg abilitato, alcuni percorsi di offload crittografico embedded)socket(AF_ALG, ...)Build con un clic (consigliata):
./scripts/build_guard.sh
Questo installa automaticamente le toolchain mancanti (nightly, bpf-linker), compila tutti i programmi eBPF e i loader in userspace e produce tutto in dist/:
dist/
├── copy_fail_guard.bpf.o # Programma eBPF LSM
├── copy_fail_guard_kprobe.bpf.o # Programma eBPF kprobe
├── copy_fail_guard # Loader in userspace (LSM)
├── copy_fail_guard_kprobe # Loader in userspace (kprobe)
└── run_guard.sh # Selezione automatica: sudo ./run_guard.sh
Copia la directory dist/ su qualsiasi macchina di destinazione ed esegui sudo ./run_guard.sh per attivare la protezione.
Build manuale (passo per passo):
Passo 1: Compila i programmi eBPF (deve essere fatto su Linux):
# Variante LSM
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# Variante kprobe
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
Passo 2: Compila i loader in userspace:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
Consigliato — seleziona automaticamente la modalità migliore:
sudo ./dist/run_guard.sh
Manuale — esegui una modalità specifica:
# Modalità LSM (richiede lsm=bpf)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# Modalità kprobe (funziona ovunque)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Premi Ctrl-C per scollegare il programma eBPF e ripristinare il comportamento normale.
Con la guardia attiva in un terminale:
# In un altro terminale, prova l'exploit:
./target/release/copy_fail
# Modalità LSM: "error: Operation not permitted"
# Modalità kprobe: "已杀死" / "Killed"
# Oppure testa direttamente con Python:
python3 -c "import socket; socket.socket(38, 5, 0)"
# Modalità LSM: PermissionError: [Errno 1] Operation not permitted
# Modalità kprobe: Killed
AF_ALG in userspace. Non esiste alcun meccanismo per esentare processi specifici per PID, cgroup o nome del comando. Questo è previsto per una versione futura (tramite mappe eBPF HashMap). Per la stragrande maggioranza dei sistemi va bene, poiché quasi nulla usa AF_ALG.bpf-linker (hanno come target bpfel-unknown-none e non possono essere normali membri del workspace).La maggior parte delle distribuzioni non abilita BPF LSM per impostazione predefinita. Se vuoi la modalità LSM più pulita (EPERM invece di SIGKILL):
# Controlla gli LSM attuali:
cat /sys/kernel/security/lsm
# Se "bpf" manca, aggiungilo:
sudo sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,yama,apparmor,bpf"/' /etc/default/grub
sudo update-grub && sudo reboot
La modalità kprobe funziona senza questo passaggio.
Dopo aver patchato il kernel a ≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22:
# Conferma la versione del kernel
uname -r
# Riesegui l'exploit — non dovrebbe più produrre una shell root
./target/release/copy_fail
In alternativa, conferma che il modulo vulnerabile sia neutralizzato:
modinfo algif_aead | grep filename
# Se è integrato, metti in blacklist l'initcall (richiede riavvio)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot