
BPF-LSM mitigazione per CVE-2026-31431 (Copy Fail) — nega la creazione di socket AF_ALG a livello di cluster
Mitigazione BPF-LSM per CVE-2026-31431 ("Copy Fail") e
per la variante RxRPC di Dirty Frag, e
per vulnerabilità simili di escalation dei privilegi che dipendono dall'accesso
da spazio utente a un percorso crittografico in-place lato kernel raggiungibile
tramite le famiglie di socket AF_ALG o AF_RXRPC.
Un piccolo DaemonSet collega un singolo programma BPF-LSM all'hook socket_create
su ogni nodo. Il programma restituisce -EPERM per qualsiasi chiamata
da spazio utente a socket(AF_ALG, ...) o socket(AF_RXRPC, ...),
indipendentemente dalle capability del processo, dal namespace o dal profilo
seccomp. I chiamanti kernel-interni di sock_create_kern() (ad es. fs/afs,
lo stack IPsec) sono lasciati passare, quindi gli utenti kernel legittimi
continuano a funzionare.
Testato su Talos Linux (che include CONFIG_BPF_LSM=y e bpf nello stack LSM
predefinito dalla v1.10), funziona su qualsiasi distribuzione con la stessa
configurazione del kernel.
Copy Fail (CVE-2026-31431) è un difetto logico in algif_aead che consente
a un utente locale non privilegiato di eseguire una scrittura di 4 byte nella
page cache di qualsiasi binario setuid, ottenendo root con uno script Python
di 732 byte. L'exploit richiede solo AF_ALG + splice(), entrambi
raggiungibili da qualsiasi processo non privilegiato per impostazione
predefinita. La correzione mainline è a664bf3d603d.
Dirty Frag è una classe di vulnerabilità successiva, divulgata a maggio
2026 dalla stessa linea di ricerca. Combina due bug che "sporcano" il membro
frag di sk_buff — xfrm-ESP Page-Cache Write e RxRPC Page-Cache Write. La
variante RxRPC esegue una decrittazione in-place pcbc(fcrypt) su una pagina
della page cache bloccata con splice() all'interno di rxkad_verify_packet_1()
e raggiunge root senza bisogno di creare namespace utente, il che la rende la
metà della catena più universalmente sfruttabile sulle distribuzioni
indurite. La correzione per xfrm-ESP è arrivata in netdev come f4c50a4034e6
(2026-05-07); le distribuzioni stanno ancora facendo backport al momento della
scrittura, e RxRPC non ha ancora una correzione pubblica — vedi il
write-up upstream
per la cronologia della divulgazione.
Entrambi gli exploit dipendono dall'apertura di un socket nella famiglia
interessata. Finché le correzioni del kernel non arrivano nella tua
distribuzione, la superficie di attacco può essere rimossa impedendo allo
spazio utente di creare socket AF_ALG o AF_RXRPC. Rispetto alle alternative:
| Mitigazione | Copertura | Riavvio? | Persistente? |
|---|---|---|---|
Parametro kernel module_blacklist=af_alg,rxrpc (gestori di famiglia, non solo algif_aead) | a livello di host | sì | sì |
/etc/modprobe.d/*.conf con install af_alg /bin/false + install rxrpc /bin/false e rmmod dei moduli già caricati (in linea con le indicazioni upstream di Dirty Frag — nota che il semplice blacklist non ferma l'autoload request_module() nel kernel, solo install … /bin/false lo fa) | a livello di host | no | sì (finché il file è presente) |
Kernel personalizzato senza CRYPTO_USER_API / AF_RXRPC | a livello di host | sì | sì |
| Profilo seccomp personalizzato per pod | solo carichi di lavoro etichettati | no | sì |
| copy-fail-blocker (questo progetto) | spazio utente a livello di host | no | finché il DS è attivo |
Questo progetto è l'opzione senza riavvio. Eseguilo a livello di cluster, poi pianifica le correzioni permanenti del kernel con la tua normale cadenza di patch.
Nota sulla variante ESP di Dirty Frag. La metà
xfrm-ESP Page-Cache Writedi Dirty Frag non viene chiusa da questo DaemonSet — viene attivata tramite netlink XFRM +UDP_ENCAP_ESPINUDP, non tramite una famiglia di socket dedicata, e un filtro BPF-LSM pulito per essa romperebbe l'IPsec legittimo sull'host o richiederebbe una logica consapevole dei namespace utente. Non attualmente tracciata qui — i contributi sono benvenuti. Sulle distribuzioni indurite che bloccano i namespace utente non privilegiati (ad es. la policy AppArmor predefinita di Ubuntu), la variante ESP è irraggiungibile in primo luogo e il blocco RxRPC qui è sufficiente.
bpf/blocker.c è un breve programma BPF-LSM:
SEC("lsm/socket_create")
int BPF_PROG(block_socket_family, int family, int type, int protocol,
int kern, int ret)
{
if (ret)
return ret;
/* kern != 0 significa sock_create_kern() — lascia passare i chiamanti in-kernel. */
if (!kern && (family == AF_ALG || family == AF_RXRPC)) // 38, 33
return -EPERM;
return 0;
}
Il loader Go (main.go, ~40 righe) carica il programma e lo collega tramite
bpf(BPF_LINK_CREATE). Il link viene mantenuto per tutta la durata del pod. Su
SIGTERM, il link viene chiuso e l'hook si scollega.
Richiede un kernel compilato con CONFIG_BPF_LSM=y e bpf nello stack LSM
attivo (lsm=...,bpf sulla riga di comando del kernel). Talos Linux include
entrambi abilitati per impostazione predefinita dalla v1.10.
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/v0.3.0/manifests/copy-fail-blocker.yaml
Per l'ultimo commit su main (potrebbe includere modifiche non rilasciate):
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/main/manifests/copy-fail-blocker.yaml
Il chart non è pubblicato come artefatto OCI (il percorso del registry è condiviso con l'immagine del container). Installa da un checkout taggato:
git clone --branch v0.3.0 https://github.com/cozystack/copy-fail-blocker
cd copy-fail-blocker
helm upgrade --install copy-fail-blocker charts/copy-fail-blocker \
--namespace kube-system
Oppure tramite le scorciatoie del Makefile:
make apply # helm upgrade --install in kube-system
make diff # anteprima delle modifiche rispetto al cluster
make delete # disinstallazione
make manifest # rigenera manifests/copy-fail-blocker.yaml
Il DaemonSet deve essere eseguito con privilegi (carica programmi BPF e scrive
su bpffs). Posizionalo in un namespace con lo standard di sicurezza Pod
Security privilegiato, oppure in kube-system, che è privilegiato per
impostazione predefinita.
Da qualsiasi pod su un nodo coperto: