
Fallo de copia: 732 bytes a root en todas las principales distribuciones de Linux.
732 bytes. Cualquier distro. Root.
Una falla lógica lineal en la plantilla criptográfica
authencesndel kernel de Linux permite a un usuario local sin privilegios realizar una escritura precisa y controlada de 4 bytes en la caché de páginas de cualquier archivo legible, incluidos los binarios setuid. Sin condiciones de carrera. Sin reintentos. Sin recompilación. Root en todas las principales distribuciones de Linux publicadas desde 2017.
📄 Informe técnico · 🔗 Parche del kernel · 🛡️ CVSS: Crítico
| Distro | Versión del kernel |
|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 |
| SUSE 16 | 6.12.0-160000.9-default |
Las cuatro fueron comprometidas con el mismo script de Python de 732 bytes, sin modificación.
AF_ALG expone el subsistema criptográfico del kernel al espacio de usuario sin privilegios. splice() transfiere datos de archivos a una tubería por referencia, pasando las páginas de la caché directamente, sin copiarlas. Cuando un usuario hace splice de un archivo a un socket AEAD AF_ALG, el scatterlist de entrada del socket contiene referencias en vivo a las páginas cacheadas del kernel de ese archivo.
En algif_aead.c, la optimización in-place de 2017 copió AAD y texto cifrado del scatterlist TX al búfer RX, pero encadenó las páginas de la etiqueta de autenticación por referencia usando sg_chain(), y luego estableció req->src = req->dst:
SGL de entrada: [ AAD | CT | Tag ]
^
└─ sg_chain() → todavía apunta a páginas de caché
SGL de salida: [ AAD | CT ] ──→ [ Tag (páginas de caché) ]
(búfer RX) (encadenado desde SGL TX)
req->src ──┐
├──→ mismo scatterlist combinado
req->dst ──┘
Las páginas de caché de splice() ahora estaban dentro de un scatterlist de destino escribible, separadas de la región de escritura legítima solo por un límite de desplazamiento. Nada en la API garantizaba que los algoritmos permanecieran dentro de los límites.
authencesnauthencesn es un envoltorio AEAD utilizado por IPsec para soporte de Número de Secuencia Extendido (ESN) de 64 bits. Para reorganizar los bytes ESN para el cálculo HMAC, usa el búfer de destino del llamador como espacio de trabajo, incluida una escritura en el desplazamiento assoclen + cryptlen, que se encuentra más allá del límite de la etiqueta de autenticación:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // lee AAD[0..7]
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // sobrescribe dst[4..7]
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // ← escribe más allá de la etiqueta
La tercera llamada escribe 4 bytes (seqno_lo) en dst[assoclen + cryptlen]. En la ruta in-place de AF_ALG, el scatterwalk cruza desde el búfer RX hacia las páginas de etiqueta encadenadas de la caché. El kernel mapea la página de caché mediante kmap_local_page y escribe directamente en la copia cacheada del archivo objetivo.
Luego el HMAC falla (el texto cifrado es fabricado), recvmsg() devuelve un error, pero la escritura de 4 bytes persiste permanentemente.
Ningún cambio individual fue incorrecto por sí solo. La vulnerabilidad vive en la intersección de los tres.
El objetivo predeterminado es /usr/bin/su, un binario setuid-root presente en todas las distribuciones probadas.
Paso 1 — Configuración del socket
Abrir socket AF_ALG, vincular a authencesn(hmac(sha256),cbc(aes))
Establecer clave. Aceptar socket de solicitud. (No se requieren privilegios.)
Paso 2 — Bucle de escritura (una vez por fragmento de shellcode de 4 bytes)
sendmsg() → los bytes AAD [4:8] transportan los 4 bytes a escribir (seqno_lo)
splice() → páginas de caché del archivo objetivo al socket AF_ALG
recv() → dispara decrypt → authencesn escribe seqno_lo en la caché de páginas
(recvmsg devuelve error; la escritura persiste)
Paso 3 — Ejecución
execve("/usr/bin/su")
El kernel carga el binario desde la caché de páginas (ahora corrupta)
El binario setuid-root ejecuta el shellcode inyectado → UID 0
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... establecer clave, aceptar socket de solicitud u ...
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
u.recv(...) # dispara la escritura en la caché de páginas
Actualice a un kernel que contenga el parche a664bf3d603d. La corrección revierte algif_aead.c a operación fuera de lugar: req->src apunta al SGL TX; req->dst apunta al búfer RX. Las páginas de caché de splice() permanecen de solo lectura. El mecanismo sg_chain() que las vinculaba al destino escribible se elimina.
// Antes (vulnerable): src y dst comparten el mismo scatterlist
aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src, rsgl_src, used, ctx->iv);
// Después (corregido): src es SGL TX, dst es búfer RX — completamente separados
aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, rsgl_dst, used, ctx->iv);
Deshabilite el módulo del kernel algif_aead:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
O bloquee la creación de sockets AF_ALG mediante una política seccomp en sus perfiles de carga de trabajo.
Nota para entornos de contenedores: Debido a que la caché de páginas se comparte en todo el host, esta vulnerabilidad cruza los límites de los contenedores. Aplique mitigaciones a nivel de nodo, no solo por pod. Consulte la Parte 2 para obtener detalles completos sobre el escape de Kubernetes.
El investigador de Theori Taeyang Lee identificó, a través de trabajos previos con kernelCTF, que AF_ALG + splice() crea una ruta donde el espacio de usuario sin privilegios puede alimentar páginas de caché directamente al subsistema criptográfico — y que la procedencia de páginas en scatterlists era una clase de vulnerabilidad poco explorada.
El equipo de investigación usó Xint Code para escalar esta idea a todo el subsistema crypto/ con el siguiente prompt de operador:
"Este es el subsistema crypto/ de Linux. Examine todas las rutas de código alcanzables desde syscalls de espacio de usuario. Tenga en cuenta una observación clave: splice() puede entregar referencias de caché de páginas de archivos de solo lectura (incluidos binarios setuid) a scatterlists TX criptográficos."
Después de aproximadamente una hora de análisis automatizado, Copy Fail fue el resultado de mayor severidad. Las vulnerabilidades adicionales descubiertas durante el mismo escaneo permanecen bajo divulgación coordinada.
Parte 2: Del pod al host — cómo Copy Fail escapa de todas las principales plataformas Kubernetes en la nube. Próximamente.
| Propiedad | Detalle |
|---|
| Determinista | Falla lógica lineal: sin condiciones de carrera, sin ventanas de sincronización, sin reintentos |
| Portátil | Mismo script, mismos bytes, funciona en todas las distros y arquitecturas probadas |
| Diminuto | Script de Python de 732 bytes que usa solo la biblioteca estándar (os, socket, zlib). Requiere Python 3.10+ para os.splice |
| Sigiloso | La página corrupta nunca se marca como sucia. Las sumas de verificación en disco no cambian; solo se modifica la caché de páginas en memoria |
| Transversal a contenedores | La caché de páginas se comparte en todo el sistema a través de los límites de los contenedores: esto también es una primitiva de escape de nodos de Kubernetes (ver Parte 2) |
| Variable | Controlada mediante |
|---|
| Archivo objetivo | Cualquier archivo legible por el usuario actual |
| Desplazamiento de escritura | assoclen, desplazamiento de splice y longitud de splice |
| Valor de escritura | Bytes 4–7 del AAD proporcionado en sendmsg() (seqno_lo) |
| Año | Evento |
|---|
| 2011 | authencesn se agregó al kernel (a5079d084f8b) para soporte ESN de IPsec. La escritura de trabajo existía pero era inofensiva: solo la capa interna xfrm la llamaba, y el AAD vivía en un scatterlist separado. |
| 2015 | AF_ALG gana soporte AEAD. authencesn se convierte a la nueva interfaz AEAD (104880a6b470), introduciendo el desplazamiento de escritura assoclen + cryptlen. Aún fuera de lugar: las páginas de caché estaban en src (solo lectura). Aún no explotable. |
| 2017 | Se agrega la optimización in-place a algif_aead.c (72548b093ee3). req->src = req->dst. Las páginas de etiqueta de la caché se encadenan al destino escribible. Se forma la vulnerabilidad. |
| 2026-03-23 | Reportada al equipo de seguridad del kernel de Linux. |
| 2026-04-01 | Parche fusionado en mainline. |
| 2026-04-22 | Se asigna CVE-2026-31431. |
| 2026-04-29 | Divulgación pública. |
| Fecha | Evento |
|---|
| 2026-03-23 | Vulnerabilidad reportada al equipo de seguridad del kernel de Linux |
| 2026-03-24 | Acuse de recibo inicial recibido |
| 2026-03-25 | Parches propuestos y revisados |
| 2026-04-01 | Parches confirmados en el kernel mainline |
| 2026-04-22 | Se asigna CVE-2026-31431 |
| 2026-04-29 | Divulgación pública |