
Docker-Mitigation für CVE-2026-31431 („Copy Fail“). Enthält auch Kubernetes-Vorlagen.
Idempotentes Skript, um die Erstellung von AF_ALG-Sockets für alle Docker-Container
auf einem Host zu blockieren, als Abschwächung für CVE-2026-31431 („Copy Fail").
CVE-2026-31431 ist eine lokale Privilegieneskalation in der kryptografischen
authencesn-Vorlage des Linux-Kernels, die in Kernen vorhanden ist, die zwischen 2017 und
der Verfügbarkeit des Upstream-Fixes (Mainline-Commit a664bf3d603d) gebaut wurden. Ein
unprivilegierter Benutzer kann eine AF_ALG-Socket-Operation mit splice() verketten, um
einen kontrollierten 4-Byte-Schreibvorgang in den Page-Cache einer beliebigen lesbaren Datei
durchzuführen, die auf ein setuid-Binärprogramm abzielt, um eine Root-Shell zu erhalten. Ein
732-Byte-Python-Proof-of-Concept nutzt dies zuverlässig aus, ohne Races oder
distributionsspezifische Offsets, auf jeder großen Linux-Distribution, die einen betroffenen
Kernel ausliefert.
Der obligatorische erste Schritt des Exploits ist das Öffnen eines AF_ALG-Sockets
(socket(AF_ALG, SOCK_SEQPACKET, 0)). Das Blockieren dieses Syscalls über seccomp
verhindert die Ausnutzung selbst auf ungepatchten Kernen. Das in Docker integrierte
Standard-seccomp-Profil blockiert nicht, und ist nicht
ausreichend — getestete Cluster zeigten, dass Pods, die unter PSS Restricted zugelassen
wurden, weiterhin -Sockets öffnen konnten.
AF_ALGRuntimeDefaultAF_ALGSiehe die ursprüngliche Forscher-Beratung unter https://copy.fail und die CERT-EU- Beratung unter https://cert.europa.eu/publications/security-advisories/2026-005/ für vollständige technische Details und Patch-Verfügbarkeit nach Distribution.
Dieses Skript deckt die globale Daemon-Konfiguration der Docker Engine ab. Für
Kubernetes siehe den Kubernetes-Abschnitt unten. Für Bare-Metal- oder
VM-Workloads (nicht containerisiert) deaktivieren Sie stattdessen das Kernelmodul algif_aead:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Dieser Ansatz hat keine Auswirkungen auf dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS oder SSH.
Extrahiert Docks aktives integriertes seccomp-Profil, indem es die
HostConfig.SecurityOpt eines kurzlebigen Containers inspiziert. Dies vermeidet jede
Abhängigkeit von einer entfernten URL und garantiert, dass das Basisprofil mit der
tatsächlich installierten Docker-Version übereinstimmt. Ein GitHub-Abruf von
moby/profiles wird nur als Fallback verwendet,
wenn die Container-Inspektion nichts ergibt.
Patched das Profil, indem socket aus dem Allowlist-Eintrag von Docker entfernt
und mit einem Argumentfilter wieder hinzugefügt wird, der alle Adressfamilien außer
AF_ALG (Wert 38) unter Verwendung von SCMP_CMP_NE zulässt. Das gesamte übrige
Standard-seccomp-Verhalten von Docker bleibt erhalten.
Schreibt das gepatchte Profil atomar nach
/etc/seccomp/docker-block-af-alg.json (Temp-Datei + Umbenennung). Übersprungen, wenn
der Inhalt auf der Festplatte bereits identisch ist.
Aktualisiert /etc/docker/daemon.json, um "seccomp-profile" auf den Pfad des
gepatchten Profils zu setzen. Die Originaldatei wird bei der ersten Änderung als
daemon.json.bak gesichert. Übersprungen, wenn bereits korrekt konfiguriert.
Lädt dockerd neu über systemctl reload docker (SIGHUP — kein Neustart
erforderlich). Übersprungen, wenn keine der Dateien geändert wurde.
Verifiziert, dass der Block aktiv ist, indem ein Probe innerhalb eines Containers ausgeführt wird, unabhängig davon, ob in den obigen Schritten Änderungen vorgenommen wurden.
Das Skript ist idempotent: Mehrfaches Ausführen erzeugt dasselbe Ergebnis und lädt Docker nur neu, wenn sich tatsächlich etwas geändert hat.
Hinweis:
--privileged-Container umgehen alle seccomp-Profile unabhängig von dieser Konfiguration. Prüfen Sie Ihre Compose-Dateien und führen Sie Befehle für privilegierte Container separat aus.
docker-CLI im PATHcurl im PATH (nur Fallback)systemctl (systemd-Host)/etc/seccomp und /etc/docker sowie für
systemctl reload docker# Abschwächung anwenden und verifizieren (normale Verwendung)
sudo python3 harden-docker-seccomp.py
# Anzeigen, was sich ändern würde, ohne etwas zu schreiben oder Docker neu zu laden
sudo python3 harden-docker-seccomp.py --dry-run
# Nur die Container-Verifizierung erneut ausführen (keine Konfigurationsänderungen)
python3 harden-docker-seccomp.py --verify-only
# Ausführliche Ausgabe
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.
| Code | Bedeutung |
|---|---|
0 | Erfolg — Abschwächung ist aktiv |
1 | Skript nicht als Root ausgeführt (beim Patchen) oder nicht behebbarer Fehler |
2 | Verifizierung fehlgeschlagen — AF_ALG ist nicht blockiert |
Kubernetes-Pods teilen sich den Host-Kernel, daher ist dieselbe AF_ALG-Socket-Primitive
von jedem Pod auf einem betroffenen Knoten erreichbar. RuntimeDefault-seccomp ist nicht
ausreichend — getestete Cluster zeigten, dass Pods, die unter PSS Restricted zugelassen
wurden, weiterhin AF_ALG-Sockets öffnen konnten. Ein Localhost-Profil mit einer
expliziten Deny-Regel ist erforderlich.
Die Abschwächung erfordert zwei Dinge: die Profil-JSON auf dem Dateisystem jedes Knotens und jede Pod-Spezifikation, die darauf verweist. Die folgenden Abschnitte behandeln beides, einschließlich der globalen Injektion des Profils ohne Änderung einzelner Pod-Spezifikationen.
Der Kubelet löst Localhost-seccomp-Profile relativ zu seinem seccomp-
Root auf, der standardmäßig /var/lib/kubelet/seccomp ist. Das Profil muss an
diesem Pfad auf jedem Knoten vorhanden sein, der einen Workload planen kann.
Wenden Sie das ConfigMap und das DaemonSet aus diesem Repository an:
kubectl apply -f templates/configmap-seccomp-profile.yaml
kubectl apply -f templates/daemonset-distribute-profile.yaml
Das DaemonSet führt einen Init-Container aus, der das Profil aus dem ConfigMap in den seccomp-Root des Knotens kopiert und dann einen minimalen Pause- Container parkt, sodass der Pod für die Gesundheitsüberwachung sichtbar bleibt. Es toleriert alle Taints, sodass es auch auf Control-Plane-Knoten läuft.
Nicht standardmäßiger Kubelet-seccomp-Root: RKE2 verwendet
/var/lib/rancher/rke2/agent/kubelet/seccomp. Überschreiben Sie den Pfad, indem SieNODE_SECCOMP_ROOTin der Umgebung des Init-Containers des DaemonSets setzen, bevor Sie es anwenden.
Verifizieren Sie, dass die Datei auf einem Knoten vorhanden ist:
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
Anstatt einzelne Pod-Spezifikationen oder Helm-Charts zu ändern, verwenden Sie einen
mutierenden Admission-Webhook, um seccompProfile automatisch zum Zeitpunkt der Zulassung zu injizieren.
Zwei Optionen werden bereitgestellt: Kyverno und OPA Gatekeeper.
Beide Ansätze injizieren das Profil nur, wenn ein Pod noch keines deklariert, sodass Pods mit expliziten Profilen unangetastet bleiben.
Wichtig: Bestehende laufende Pods werden nicht rückwirkend mutiert. Nach dem Anwenden der Richtlinie rollen Sie Ihre Deployments neu, um das injizierte Profil zu übernehmen:
kubectl rollout restart deployment -A
Diese Vorlage verwendet die MutatingPolicy-API (policies.kyverno.io/v1),
die in Kyverno 1.17 GA erreicht hat. Die veraltete ClusterPolicy-API
(kyverno.io/v1) wurde in Kyverno 1.17 (Januar 2026) als veraltet markiert und ist
für die Entfernung in 1.20 (Oktober 2026) geplant; verwenden Sie sie nicht für neue Richtlinien.
Der CEL-Ausdruck matchConditions prüft, dass seccompProfile fehlt,
bevor mutiert wird, sodass Pods, die bereits ein Profil deklarieren, unangetastet bleiben.
# Kyverno installieren (falls nicht bereits vorhanden)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm upgrade --install kyverno kyverno/kyverno -n kyverno --create-namespace
# Die Richtlinie anwenden
kubectl apply -f templates/kyverno-mutate-seccomp.yaml
Verifizieren Sie, dass ein neuer Pod das injizierte Profil erhält:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# Erwartet: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
Die Assign-Mutations-CRD von Gatekeeper verwendet eine pathTests-Bedingung, um das
Profil nur zu injizieren, wenn spec.securityContext.seccompProfile nicht bereits existiert.
Mutation ist seit Gatekeeper 3.10+ stabil; kein Feature-Flag ist erforderlich.
# Gatekeeper installieren (falls nicht bereits vorhanden)
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
# Die Mutation anwenden
kubectl apply -f templates/gatekeeper-assign-seccomp.yaml
Verifizieren:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# Erwartet: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
Gatekeeper vs. Kyverno: Der
Assign-Ansatz arbeitet auf Feldebene und erfordert, dass der Gatekeeper-Mutations-Webhook aktiviert ist. KyvernosMutatingPolicymit einer CEL-matchConditionbehandelt die Bedingung inline. Beides erzielt dasselbe Ergebnis — bevorzugen Sie, was bereits in Ihrem Cluster bereitgestellt ist.
Pods mit hostPID: true, hostNetwork: true oder
securityContext.privileged: true haben erhöhten Zugriff, den seccomp allein nicht vollständig
eindämmt. Prüfen Sie diese Workloads separat und entfernen Sie Privilegien, wo möglich.
ConfigMap als Quelle der Wahrheit. Die Profil-JSON lebt in
configmap-seccomp-profile.yaml, anstatt im DaemonSet eingebettet oder über Dateien dupliziert zu werden. Das DaemonSet mountet sie und kopiert sie auf den Knoten.
Das Aktualisieren des Profils bedeutet, ein ConfigMap zu bearbeiten und die DaemonSet-Pods
neu zu starten — keine anderen Dateien ändern sich.
DaemonSet verwendet system-node-critical-Priorität. Dies stellt sicher, dass der
Verteilungspod nicht evakuiert wird, bevor die Workloads, die er schützt, geplant werden,
was Knoten mit einer fehlenden Profildatei und Pods, die in
CreateContainerError stecken, hinterlassen würde.
Gatekeeper schließt kube-system und gatekeeper-system aus. Das Injizieren eines
Localhost-Profils in System-Pods, die möglicherweise vor der DaemonSet-Installation
existieren, riskiert eine defekte Profilreferenz, wenn die Datei noch nicht auf dem
Knoten vorhanden ist. Die Kyverno-Richtlinie benötigt diesen Ausschluss nicht, da Kyverno
die Webhook-Reihenfolge eleganter handhabt, aber exclude-Regeln können dort ebenfalls
hinzugefügt werden, falls erforderlich.
Bedingte Injektion, keine Überschreibung. Sowohl die CEL-matchCondition der Kyverno-MutatingPolicy
als auch der MustNotExist-Pfadtest von Gatekeeper bedeuten, dass die
Admissionsrichtlinie nur handelt, wenn ein Pod kein vorhandenes seccompProfile hat.
Workloads, die bereits ihr eigenes Profil deklarieren — einschließlich solcher, die
legitim AF_ALG über eine benutzerdefinierte Allowlist benötigen — bleiben unangetastet.