
Mitigazione Docker per CVE-2026-31431 ('Copy Fail'). Include anche template Kubernetes.
Script idempotente per bloccare la creazione di socket AF_ALG per tutti i container Docker
su un host come mitigazione per CVE-2026-31431 ("Copy Fail").
CVE-2026-31431 è un'escalation di privilegi locale nel template crittografico
authencesn del kernel Linux, presente nei kernel compilati tra il 2017 e
la disponibilità della correzione upstream (commit mainline a664bf3d603d). Un
utente non privilegiato può concatenare un'operazione su socket AF_ALG con splice() per
eseguire una scrittura controllata di 4 byte nella cache delle pagine di qualsiasi file leggibile,
prendendo di mira un binario setuid per ottenere una shell di root. Una proof of concept
Python di 732 byte sfrutta questa vulnerabilità in modo affidabile, senza race condition o offset
per distribuzione, su ogni principale distribuzione Linux che include un kernel vulnerabile.
Il primo passo obbligatorio dell'exploit è l'apertura di un socket AF_ALG
(socket(AF_ALG, SOCK_SEQPACKET, 0)). Bloccare quella syscall tramite seccomp
previene lo sfruttamento anche su kernel non patchati. Il profilo seccomp predefinito
integrato di Docker non blocca , e non è
sufficiente — i cluster testati hanno mostrato che i pod ammessi sotto PSS Restricted
potevano comunque aprire socket .
AF_ALGRuntimeDefaultAF_ALGVedi l'advisory del ricercatore originale su https://copy.fail e l'advisory CERT-EU su https://cert.europa.eu/publications/security-advisories/2026-005/ per i dettagli tecnici completi e la disponibilità delle patch per distribuzione.
Questo script copre la configurazione globale del daemon Docker Engine. Per
Kubernetes, vedi la sezione Kubernetes qui sotto. Per carichi di lavoro
bare-metal o VM (non containerizzati), disabilita invece il modulo del kernel algif_aead:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Questo approccio non ha alcun impatto su dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS o SSH.
Estrae il profilo seccomp predefinito attivo di Docker ispezionando il
HostConfig.SecurityOpt di un container di breve durata. Questo evita qualsiasi
dipendenza da un URL remoto e garantisce che il profilo di base corrisponda alla
versione di Docker effettivamente installata. Un fetch da GitHub da
moby/profiles viene usato come fallback
solo se l'ispezione del container non produce risultati.
Applica una patch al profilo rimuovendo socket dalla voce allowlist di Docker
e riaggiungendolo con un filtro sugli argomenti che consente tutte le famiglie di
indirizzi tranne AF_ALG (valore 38) usando SCMP_CMP_NE. Tutto il resto del
comportamento seccomp predefinito di Docker viene preservato.
Scrive il profilo patchato atomicamente in
/etc/seccomp/docker-block-af-alg.json (file temporaneo + rinomina). Viene saltato se
il contenuto su disco è già identico.
Aggiorna /etc/docker/daemon.json per impostare "seccomp-profile" sul
percorso del profilo patchato. Il file originale viene salvato come backup in daemon.json.bak
alla prima modifica. Viene saltato se già configurato correttamente.
Ricarica dockerd tramite systemctl reload docker (SIGHUP — nessun riavvio
richiesto). Viene saltato se nessuno dei due file è cambiato.
Verifica che il blocco sia attivo eseguendo una sonda all'interno di un container, indipendentemente dal fatto che siano state apportate modifiche nei passaggi precedenti.
Lo script è idempotente: eseguirlo più volte produce lo stesso risultato e ricarica Docker solo quando qualcosa è effettivamente cambiato.
Nota: i container
--privilegedbypassano tutti i profili seccomp indipendentemente da questa configurazione. Controlla separatamente i tuoi file Compose e i comandi di esecuzione per i container privilegiati.
docker nel PATHcurl nel PATH (solo fallback)systemctl (host systemd)/etc/seccomp e /etc/docker, e per
systemctl reload docker# Applica la mitigazione e verifica (utilizzo normale)
sudo python3 harden-docker-seccomp.py
# Mostra cosa cambierebbe senza scrivere nulla o ricaricare Docker
sudo python3 harden-docker-seccomp.py --dry-run
# Esegui solo la verifica del container (nessuna modifica alla configurazione)
python3 harden-docker-seccomp.py --verify-only
# Output verboso
sudo python3 harden-docker-seccomp.py --verbose
INFO Written: /etc/seccomp/docker-block-af-alg.json
INFO Updated: /etc/docker/daemon.json
INFO Reloading Docker daemon (SIGHUP)…
INFO Docker daemon reloaded.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
INFO Profile unchanged: /etc/seccomp/docker-block-af-alg.json
INFO daemon.json already configured correctly.
INFO No changes — Docker daemon reload not required.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
| Codice | Significato |
|---|---|
0 | Successo — la mitigazione è attiva |
1 | Script non eseguito come root (durante la patch), o errore irreversibile |
2 | Verifica fallita — AF_ALG non è bloccato |
I pod Kubernetes condividono il kernel dell'host, quindi la stessa primitiva del socket AF_ALG
è raggiungibile da qualsiasi pod su un nodo vulnerabile. Il seccomp RuntimeDefault non è
sufficiente — i cluster testati hanno mostrato che i pod ammessi sotto PSS Restricted
potevano comunque aprire socket AF_ALG. È richiesto un profilo Localhost con una regola di
deny esplicita.
La mitigazione richiede due cose: il JSON del profilo presente sul filesystem di ogni nodo, e ogni spec di pod che lo referenzi. Le sezioni seguenti coprono entrambe, incluso come iniettare il profilo globalmente senza modificare gli spec dei singoli pod.
Il kubelet risolve i profili seccomp Localhost relativamente alla sua root seccomp,
che per impostazione predefinita è /var/lib/kubelet/seccomp. Il profilo deve esistere a
quel percorso su ogni nodo che può pianificare un carico di lavoro.
Applica il ConfigMap e il DaemonSet da questo repository:
kubectl apply -f templates/configmap-seccomp-profile.yaml
kubectl apply -f templates/daemonset-distribute-profile.yaml
Il DaemonSet esegue un container init che copia il profilo dal ConfigMap alla root seccomp del kubelet del nodo, poi parcheggia un container pause minimale così il pod rimane visibile per il monitoraggio della salute. Tollera tutti i taint così viene eseguito anche sui nodi control-plane.
Root seccomp kubelet non standard: RKE2 usa
/var/lib/rancher/rke2/agent/kubelet/seccomp. Sovrascrivi il percorso impostandoNODE_SECCOMP_ROOTnell'env del container init del DaemonSet prima di applicare.
Verifica che il file sia presente su un nodo:
kubectl -n kube-system exec -it \
$(kubectl -n kube-system get pod -l app.kubernetes.io/name=distribute-af-alg-seccomp \
-o jsonpath='{.items[0].metadata.name}') -- \
cat /var/lib/kubelet/seccomp/block-af-alg.json
Invece di modificare i singoli spec dei pod o i chart Helm, usa un webhook
di ammissione mutante per iniettare seccompProfile automaticamente al momento dell'ammissione.
Sono fornite due opzioni: Kyverno e OPA Gatekeeper.
Entrambi gli approcci iniettano il profilo solo quando un pod non ne dichiara già uno, quindi i pod con profili espliciti vengono lasciati intatti.
Importante: I pod esistenti in esecuzione non vengono mutati retroattivamente. Dopo aver applicato la policy, fai un rollout dei tuoi deployment per applicare il profilo iniettato:
kubectl rollout restart deployment -A
Questo template usa l'API MutatingPolicy (policies.kyverno.io/v1),
che ha raggiunto la GA in Kyverno 1.17. L'API legacy ClusterPolicy
(kyverno.io/v1) è stata deprecata in Kyverno 1.17 (gennaio 2026) ed è
prevista per la rimozione in 1.20 (ottobre 2026); non usarla per nuove policy.
L'espressione CEL matchConditions verifica che seccompProfile sia assente
prima di mutare, quindi i pod che dichiarano già un profilo vengono lasciati intatti.
# Installa Kyverno (se non già presente)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm upgrade --install kyverno kyverno/kyverno -n kyverno --create-namespace
# Applica la policy
kubectl apply -f templates/kyverno-mutate-seccomp.yaml
Verifica che un nuovo pod riceva il profilo iniettato:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# Previsto: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
Il CRD di mutazione Assign di Gatekeeper usa una condizione pathTests per iniettare il
profilo solo quando spec.securityContext.seccompProfile non esiste già.
La mutazione è stabile da Gatekeeper 3.10+; nessun flag di funzionalità è richiesto.
# Installa Gatekeeper (se non già presente)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install -n gatekeeper-system gatekeeper gatekeeper/gatekeeper \
--create-namespace
# Applica la mutazione
kubectl apply -f templates/gatekeeper-assign-seccomp.yaml
Verifica:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# Previsto: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
Gatekeeper vs. Kyverno: L'approccio
Assignopera a livello di campo e richiede che il webhook di mutazione di Gatekeeper sia abilitato. IlMutatingPolicydi Kyverno con unmatchConditionCEL gestisce la condizione inline. Entrambi ottengono lo stesso risultato — preferisci quello già distribuito nel tuo cluster.
I pod con hostPID: true, hostNetwork: true o
securityContext.privileged: true hanno accesso elevato che il solo seccomp non
contiene completamente. Controlla quei carichi di lavoro separatamente e rimuovi i privilegi dove
possibile.
ConfigMap come fonte di verità. Il JSON del profilo vive in
configmap-seccomp-profile.yaml invece di essere incorporato nel DaemonSet
o duplicato tra i file. Il DaemonSet lo monta e lo copia sul nodo.
Aggiornare il profilo significa modificare un ConfigMap e riavviare i pod del DaemonSet
— nessun altro file cambia.
Il DaemonSet usa la priorità system-node-critical. Questo garantisce che il pod di
distribuzione non venga sfrattato prima che i carichi di lavoro che protegge vengano pianificati,
il che lascerebbe i nodi con un file di profilo mancante e i pod bloccati in
CreateContainerError.
Gatekeeper esclude kube-system e gatekeeper-system. Iniettare un
profilo Localhost nei pod di sistema che potrebbero precedere l'installazione del DaemonSet
rischia un riferimento a un profilo rotto se il file non è ancora presente sul
nodo. La policy Kyverno non ha bisogno di questa esclusione perché Kyverno
gestisce l'ordinamento dei webhook in modo più elegante, ma le regole exclude possono essere aggiunte
anche lì se necessario.
Iniezione condizionale, non sovrascrittura. Sia il matchCondition CEL del
MutatingPolicy di Kyverno che il test di percorso MustNotExist di Gatekeeper significano che la
policy di ammissione agisce solo quando un pod non ha un seccompProfile esistente.
I carichi di lavoro che dichiarano già il proprio profilo — inclusi quelli che
legittimamente necessitano di AF_ALG tramite una allow-list personalizzata — vengono lasciati intatti.