
Docker mitigation for CVE-2026-31431 ('Copy Fail'). Includes Kubernetes templates as well.
Idempotent script to block AF_ALG socket creation for all Docker containers
on a host as mitigation for CVE-2026-31431 ("Copy Fail").
CVE-2026-31431 is a local privilege escalation in the Linux kernel's
authencesn cryptographic template, present in kernels built between 2017 and
the availability of the upstream fix (mainline commit a664bf3d603d). An
unprivileged user can chain an AF_ALG socket operation with splice() to
perform a controlled 4-byte write into the page cache of any readable file,
targeting a setuid binary to obtain a root shell. A 732-byte Python proof of
concept exploits this reliably, without races or per-distro offsets, on every
major Linux distribution shipping an affected kernel.
The exploit's mandatory first step is opening an AF_ALG socket
(socket(AF_ALG, SOCK_SEQPACKET, 0)). Blocking that syscall via seccomp
prevents exploitation even on unpatched kernels. Docker's built-in default
seccomp profile does not block AF_ALG, and RuntimeDefault is not
sufficient — tested clusters showed that pods admitted under PSS Restricted
could still open AF_ALG sockets.
See the original researcher advisory at https://copy.fail and the CERT-EU advisory at https://cert.europa.eu/publications/security-advisories/2026-005/ for full technical detail and patch availability by distribution.
This script covers the Docker Engine global daemon configuration. For
Kubernetes, see the Kubernetes section below. For bare-metal or
VM workloads (non-containerised), disable the algif_aead kernel module
instead:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
That approach has no impact on dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS, or SSH.
Extracts Docker's active built-in seccomp profile by inspecting a
short-lived container's HostConfig.SecurityOpt. This avoids any
dependency on a remote URL and guarantees the base profile matches the
Docker version actually installed. A GitHub fetch from
moby/profiles is used as a fallback
only if container inspection yields nothing.
Patches the profile by removing socket from Docker's allowlist entry
and re-adding it with an argument filter that allows all address families
except AF_ALG (value 38) using SCMP_CMP_NE. All other Docker default
seccomp behaviour is preserved.
Writes the patched profile atomically to
/etc/seccomp/docker-block-af-alg.json (temp file + rename). Skipped if
the on-disk content is already identical.
Updates /etc/docker/daemon.json to set "seccomp-profile" to the
patched profile path. The original file is backed up to daemon.json.bak
on first modification. Skipped if already configured correctly.
Reloads dockerd via systemctl reload docker (SIGHUP — no restart
required). Skipped if neither file changed.
Verifies the block is active by running a probe inside a container, regardless of whether any changes were made in the steps above.
The script is idempotent: running it multiple times produces the same result and only reloads Docker when something actually changed.
Note:
--privilegedcontainers bypass all seccomp profiles regardless of this configuration. Audit your Compose files and run commands for privileged containers separately.
docker CLI in PATHcurl in PATH (fallback only)systemctl (systemd host)/etc/seccomp and /etc/docker, and for
systemctl reload docker# Apply the mitigation and verify (normal usage)
sudo python3 harden-docker-seccomp.py
# Show what would change without writing anything or reloading Docker
sudo python3 harden-docker-seccomp.py --dry-run
# Re-run the container verification only (no config changes)
python3 harden-docker-seccomp.py --verify-only
# Verbose output
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 | Meaning |
|---|---|
0 | Success — mitigation is active |
1 | Script not run as root (when patching), or unrecoverable error |
2 | Verification failed — AF_ALG is not blocked |
Kubernetes pods share the host kernel, so the same AF_ALG socket primitive
is reachable from any pod on an affected node. RuntimeDefault seccomp is not
sufficient — tested clusters showed that pods admitted under PSS Restricted
could still open AF_ALG sockets. A Localhost profile with an explicit deny
rule is required.
Mitigation requires two things: the profile JSON present on every node's filesystem, and every pod spec referencing it. The sections below cover both, including how to inject the profile globally without modifying individual pod specs.
The kubelet resolves Localhost seccomp profiles relative to its seccomp
root, which defaults to /var/lib/kubelet/seccomp. The profile must exist at
that path on every node that may schedule a workload.
Apply the ConfigMap and DaemonSet from this repository:
kubectl apply -f templates/configmap-seccomp-profile.yaml
kubectl apply -f templates/daemonset-distribute-profile.yaml
The DaemonSet runs an init container that copies the profile from the ConfigMap to the node's kubelet seccomp root, then parks a minimal pause container so the pod remains visible for health monitoring. It tolerates all taints so it also runs on control-plane nodes.
Non-standard kubelet seccomp root: RKE2 uses
/var/lib/rancher/rke2/agent/kubelet/seccomp. Override the path by settingNODE_SECCOMP_ROOTin the DaemonSet's init container env before applying.
Verify the file is present on a node:
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
Rather than modifying individual pod specs or Helm charts, use a mutating
admission webhook to inject seccompProfile automatically at admission time.
Two options are provided: Kyverno and OPA Gatekeeper.
Both approaches only inject the profile when a pod does not already declare one, so pods with explicit profiles are left untouched.
Important: Existing running pods are not retroactively mutated. After applying the policy, roll your deployments to pick up the injected profile:
kubectl rollout restart deployment -A
This template uses the MutatingPolicy API (policies.kyverno.io/v1),
which reached GA in Kyverno 1.17. The legacy ClusterPolicy API
(kyverno.io/v1) was deprecated in Kyverno 1.17 (January 2026) and is
planned for removal in 1.20 (October 2026); do not use it for new policies.