
Mitigación de Docker para CVE-2026-31431 ('Copy Fail'). Incluye también plantillas de Kubernetes.
Script idempotente para bloquear la creación de sockets AF_ALG para todos los contenedores Docker
en un host como mitigación para CVE-2026-31431 ("Copy Fail").
CVE-2026-31431 es una escalada de privilegios local en la plantilla criptográfica
authencesn del kernel de Linux, presente en kernels compilados entre 2017 y
la disponibilidad del parche oficial (commit de mainline a664bf3d603d). Un
usuario sin privilegios puede encadenar una operación de socket AF_ALG con splice() para
realizar una escritura controlada de 4 bytes en la caché de páginas de cualquier archivo legible,
apuntando a un binario setuid para obtener una shell root. Una prueba de concepto en Python de 732 bytes
explota esto de forma fiable, sin condiciones de carrera ni offsets por distribución, en todas
las principales distribuciones de Linux que incluyen un kernel afectado.
El primer paso obligatorio del exploit es abrir un socket AF_ALG
(socket(AF_ALG, SOCK_SEQPACKET, 0)). Bloquear esa syscall mediante seccomp
previene la explotación incluso en kernels sin parche. El perfil seccomp predeterminado integrado
de Docker no bloquea AF_ALG, y RuntimeDefault no es
suficiente: los clústeres probados mostraron que los pods admitidos bajo PSS Restricted
aún podían abrir sockets AF_ALG.
Consulte el aviso del investigador original en https://copy.fail y el aviso de CERT-EU en https://cert.europa.eu/publications/security-advisories/2026-005/ para obtener detalles técnicos completos y disponibilidad de parches por distribución.
Este script cubre la configuración global del daemon de Docker Engine. Para
Kubernetes, consulte la sección de Kubernetes a continuación. Para cargas de trabajo
en bare-metal o VM (no contenedorizadas), deshabilite el módulo del kernel algif_aead
en su lugar:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Ese enfoque no tiene impacto en dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS o SSH.
Extrae el perfil seccomp integrado activo de Docker inspeccionando el
HostConfig.SecurityOpt de un contenedor de corta duración. Esto evita cualquier
dependencia de una URL remota y garantiza que el perfil base coincida con la
versión de Docker realmente instalada. Una descarga de GitHub desde
moby/profiles se utiliza como respaldo
solo si la inspección del contenedor no produce resultados.
Parchea el perfil eliminando socket de la entrada de lista blanca de Docker
y re-agregándolo con un filtro de argumentos que permite todas las familias de direcciones
excepto AF_ALG (valor 38) usando SCMP_CMP_NE. Todo el resto del comportamiento
seccomp predeterminado de Docker se conserva.
Escribe el perfil parcheado de forma atómica en
/etc/seccomp/docker-block-af-alg.json (archivo temporal + renombrado). Se omite si
el contenido en disco ya es idéntico.
Actualiza /etc/docker/daemon.json para establecer "seccomp-profile" en la
ruta del perfil parcheado. El archivo original se respalda en daemon.json.bak
en la primera modificación. Se omite si ya está configurado correctamente.
Recarga dockerd mediante systemctl reload docker (SIGHUP: no se requiere
reinicio). Se omite si ninguno de los archivos cambió.
Verifica que el bloqueo esté activo ejecutando una sonda dentro de un contenedor, independientemente de si se realizaron cambios en los pasos anteriores.
El script es idempotente: ejecutarlo varias veces produce el mismo resultado y solo recarga Docker cuando algo realmente cambió.
Nota: Los contenedores
--privilegedomiten todos los perfiles seccomp independientemente de esta configuración. Audite sus archivos Compose y comandos de ejecución para contenedores privilegiados por separado.
docker en PATHcurl en PATH (solo respaldo)systemctl (host con systemd)/etc/seccomp y /etc/docker, y para
systemctl reload docker# Aplicar la mitigación y verificar (uso normal)
sudo python3 harden-docker-seccomp.py
# Mostrar qué cambiaría sin escribir nada ni recargar Docker
sudo python3 harden-docker-seccomp.py --dry-run
# Re-ejecutar solo la verificación del contenedor (sin cambios de configuración)
python3 harden-docker-seccomp.py --verify-only
# Salida detallada
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.
| Código | Significado |
|---|---|
0 | Éxito: la mitigación está activa |
1 | El script no se ejecutó como root (al parchear), o error irrecuperable |
2 | Falló la verificación: AF_ALG no está bloqueado |
Los pods de Kubernetes comparten el kernel del host, por lo que la misma primitiva de socket
AF_ALG es accesible desde cualquier pod en un nodo afectado. El seccomp RuntimeDefault no es
suficiente: los clústeres probados mostraron que los pods admitidos bajo PSS Restricted
aún podían abrir sockets AF_ALG. Se requiere un perfil Localhost con una regla de denegación explícita.
La mitigación requiere dos cosas: el JSON del perfil presente en el sistema de archivos de cada nodo, y cada especificación de pod que lo referencie. Las secciones a continuación cubren ambas, incluyendo cómo inyectar el perfil globalmente sin modificar las especificaciones individuales de los pods.
El kubelet resuelve los perfiles seccomp Localhost en relación con su raíz de seccomp,
que por defecto es /var/lib/kubelet/seccomp. El perfil debe existir en esa
ruta en cada nodo que pueda programar una carga de trabajo.
Aplique el ConfigMap y el DaemonSet de este repositorio:
kubectl apply -f templates/configmap-seccomp-profile.yaml
kubectl apply -f templates/daemonset-distribute-profile.yaml
El DaemonSet ejecuta un contenedor init que copia el perfil desde el ConfigMap a la raíz de seccomp del kubelet del nodo, y luego estaciona un contenedor pause mínimo para que el pod permanezca visible para el monitoreo de salud. Tolera todos los taints para que también se ejecute en nodos del plano de control.
Raíz de seccomp del kubelet no estándar: RKE2 usa
/var/lib/rancher/rke2/agent/kubelet/seccomp. Sobrescriba la ruta estableciendoNODE_SECCOMP_ROOTen el entorno del contenedor init del DaemonSet antes de aplicar.
Verifique que el archivo esté presente en 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