Foto de Blai Peidro
Hola

Soy Blai Peidro

Senior Infrastructure Engineer

  • Stack Linux · Bash · Python · Ansible
  • Intereses Automatización · IA · Network
  • Idiomas Español · Català · English
  • Ubicación Barcelona, España
  • Web https://www.blai.blog

Análisis de Copy Fail

Copy Fail (CVE-2026-31431): cómo una corrupción de Page Cache permite obtener root en Linux mediante AF_ALG y splice()

En abril de 2026, la empresa surcoreana Theori hizo público el CVE-2026-31431, más conocido como "Copy Fail". Se trata de una vulnerabilidad crítica del kernel de Linux que permite a un usuario sin privilegios modificar un programa cargado en memoria y utilizar esa modificación para obtener privilegios de root.

Lo interesante de Copy Fail es que afecta a la mayoría de distribuciones actuales de Linux y que no explota un buffer overflow, un race condition o un Use After Free (UAF), sino que se vale de la interacción inesperada entre varios componentes internos del kernel: AF_ALG, AEAD, Scatterlists, splice(), authencesn y Page Cache.

A continuación, veamos qué es cada uno de estos componentes y qué función desempeñan dentro del kernel de Linux. Entender su comportamiento y la relación entre ellos nos permitirá comprender realmente el funcionamiento interno de Copy Fail y cómo este fallo ha podido llegar a ocurrir.

¿Qué es Page Cache?


Page Cache es el mecanismo del kernel de Linux que almacena en RAM las páginas de datos de archivos recientemente accedidos. Su objetivo es reducir accesos a disco (proceso lento) reutilizando información ya cargada en memoria.

Cuando una aplicación solicita datos ya presentes en la Page Cache, el kernel los sirve directamente desde RAM y varios mecanismos pueden reutilizar esas páginas sin copiarlas: mmap(), execve(), sendfile() o splice().

Por ello, cuando Copy Fail modifica la Page Cache, está alterando temporalmente el binario ejecutado en memoria, mientras que el archivo real permanece intacto en el disco, es decir, su contenido físico no cambia durante el ataque.

¿Qué es AF_ALG?


AF_ALG (Algorithm Family) es una interfaz del kernel Linux que expone algoritmos criptográficos (SHA256, HMAC, AEAD…) mediante sockets, permitiendo acceder a la Crypto API desde el user space (el espacio de ejecución de las aplicaciones en modo no privilegiado, aislado del kernel y con acceso únicamente a recursos controlados mediante system calls):

socket(AF_ALG, SOCK_SEQPACKET, 0)

Introducida en el kernel en 2015, AF_ALG abrió la Crypto API del kernel a cualquier proceso de user space sin necesidad de privilegios especiales. Esa accesibilidad, pensada para simplificar el uso de algoritmos criptográficos, es precisamente lo que la convierte en un componente clave de Copy Fail.

¿Qué es AEAD?


AEAD (Authenticated Encryption With Associated Data) es un esquema de cifrado que proporciona confidencialidad, integridad y autenticación de los datos en un único mecanismo criptográfico. Se emplea en TLS 1.3, VPNs y APIs.

Su estructura típica es:

AEAD structure: AAD, Ciphertext, Authentication Tag AAD Datos autenticados, no cifrados Ciphertext Datos cifrados Tag Verifica integridad del mensaje

¿Qué es authencesn?


authencesn es un template AEAD utilizado principalmente por IPsec para manejar Extended Sequence Numbers (ESN).

El nombre authencesn proviene de:

AUTHentication + ENCryption + Extended Sequence Number.

Los ESN son números de secuencia de 64 bits que permiten evitar replay attacks, reutilización de paquetes y manipulación de tráfico. Son especialmente importantes en enlaces de alto rendimiento y túneles VPN de larga duración.

Introducido en el kernel en 2011, authencesn realiza operaciones sobre buffers asumidos como seguros. Esa suposición se mantuvo sin cuestionarse durante años, pero la combinación posterior con AEAD in-place y referencias a Page Cache acabó rompiéndola. Ahí nace Copy Fail.

¿Qué es authencesn? Estructura interna de authencesn: componentes AUTH, ENC y ESN y su función en IPsec. authencesn – estructura interna AUTH Autenticación del paquete IPsec ENC Cifrado del contenido del paquete ESN Número de secuencia de 64 bits Suposición rota Buffers asumidos seguros – no siempre lo son

¿Qué son las Scatterlists?


Las Scatterlists son estructuras del kernel que permiten representar memoria fragmentada como si fuese contigua, evitando copias adicionales en operaciones de I/O, DMA (Direct Memory Access) o procesamiento criptográfico.

En Copy Fail existen dos scatterlists principales:

¿Qué son las Scatterlists? Estructura y uso de TX SGL y RX SGL en Copy Fail, y el fallo cuando comparten páginas. TX SGL – Entrada TX SGL Scatterlist de entrada AAD Datos autenticados, no cifrados Ciphertext Datos a descifrar Tag Verifica integridad Memoria fragmentada Representada como contigua al kernel RX SGL – Salida RX SGL Scatterlist de salida AAD Datos autenticados, no cifrados Plaintext Resultado del descifrado Fallo TX y RX comparten páginas en memoria Memoria fragmentada Representada como contigua al kernel Página compartida de /usr/bin/su Entrada y salida simultáneas – corrupción de Page Cache

El fallo aparece cuando ambas estructuras comparten referencias a páginas en memoria que no deberían ser interpretadas simultáneamente como entrada y salida en el mismo contexto operativo.

¿Qué hace splice()?


splice() es una llamada del sistema que mueve datos entre file descriptors (identificadores numéricos que representan recursos abiertos, como archivos o pipes) sin copiarlos a espacio de usuario (técnica conocida como Zero-Copy):

¿Qué hace splice()? Dos flujos en vertical: uso normal de splice a la izquierda, flujo Copy Fail a la derecha. Uso normal — Zero-Copy Disco Fuente de datos original Page Cache Páginas cargadas en RAM Pipe Via splice() Sin paso por espacio de usuario Cero copias en RAM Flujo en Copy Fail Disco Fuente de datos original Page Cache Páginas cargadas en RAM Pipe Via splice() AF_ALG Socket criptográfico del kernel Scatterlist Referencia páginas reales del archivo Punto crítico Crypto accede directo a la Page Cache

Las páginas reales del archivo terminan siendo referenciadas directamente por el subsistema criptográfico. Ese es el punto crítico de la vulnerabilidad.

El origen real del fallo


Durante AEAD, el kernel crea una scatterlist de entrada y otra de salida. En el modelo original, estas dos estructuras apuntaban a regiones de memoria independientes: la operación criptográfica leía de una y escribía en la otra, sin solapamiento posible entre ambas.

En 2017, Linux introdujo una optimización llamada In-Place AEAD. La idea era sencilla: si entrada y salida compartían el mismo buffer, se evitaba una copia intermedia de memoria, reduciendo la presión sobre la caché del procesador y mejorando el rendimiento en operaciones criptográficas de alto volumen.

In-Place AEAD era una optimización legítima y bien intencionada, que funcionó correctamente durante años. El problema apareció cuando interactuó con componentes que nadie había combinado de esa forma, convirtiendo una mejora de rendimiento en la raíz de una escalada de privilegios:

El origen real del fallo — In-Place AEAD Comparación entre el comportamiento AEAD antes y después de la optimización In-Place introducida en 2017. Antes – req → src ≠ req → dst AEAD request Operación criptográfica autenticada src scatterlist – entrada Buffer de lectura independiente dst scatterlist – salida Buffer de escritura independiente Buffers separados en memoria src y dst no comparten páginas Operación criptográfica Lectura de src, escritura en dst Page Cache no modificada dst nunca apunta a páginas del binario Resultado seguro Binario en memoria intacto Después – req → src = req → dst AEAD request Operación criptográfica autenticada src = dst scatterlist Optimización In-Place – Linux 2017 Referencias compartidas src y dst apuntan a las mismas páginas splice() inyecta páginas de Page Cache Páginas del binario en el scatterlist Operación criptográfica in-place Lectura y escritura sobre las mismas páginas Página NO marcada dirty El kernel no detecta la modificación Page Cache corrompida Disco intacto – memoria alterada Copy Fail Corrupción silenciosa del binario

El resultado es que ambas scatterlists comparten referencias, y esas referencias pueden apuntar a páginas de la Page Cache tratadas como memoria modificable. Ahí aparece Copy Fail.

Cadena completa de explotación


Hasta ahora hemos visto cada componente por separado; la explotación de Copy Fail aparece cuando todos ellos interactúan dentro de la misma ruta de ejecución:

Copy Fail (CVE-2026-31431) — cadena completa de explotación Diagrama de flujo con dos fases: preparación y explotación. Fase 1 – Preparación Usuario sin privilegios execve("/usr/bin/su") Primera ejecución – carga en Page Cache Page Cache de /usr/bin/su Páginas del binario en RAM Fase 2 – Explotación splice() Page Cache → Pipe (zero-copy) AF_ALG (authencesn) Scatterlist referencia páginas reales AEAD In-Place src = dst → lectura y escritura solapadas Escritura controlada de 4 bytes Kernel no marca la página como dirty Page Cache corrupta Disco intacto – memoria alterada execve("/usr/bin/su") Segunda ejecución – código alterado root Escalada de privilegios
La clave está en que el atacante nunca modifica el archivo físico. Toda la alteración ocurre únicamente sobre la copia en memoria, y desaparece en el momento en que el sistema se reinicia o la página es descartada de la Page Cache.

Los 4 bytes sobreescritos no son arbitrarios: el atacante los elige para reemplazar el prólogo de la función de su que verifica si el usuario tiene permisos suficientes. La sustitución más habitual es un NOP (instrucción de no operación) o un salto incondicional que omite la comprobación por completo.

Cuatro bytes son suficientes porque en arquitecturas x86-64 una instrucción de salto corto (JMP rel8) ocupa exactamente 2 bytes y un NOP un único byte. Modificar el inicio de la función es suficiente para desviar o eliminar toda la lógica de verificación que la sigue.

El resultado es que en la segunda ejecución de su, la comprobación de identidad nunca llega a producirse y el proceso continúa con privilegios de root sin haberlos verificado en ningún momento.

Cómo Copy Fail evade los controles habituales


La corrupción del código de su empleada por Copy Fail no utiliza rutas tradicionales de escritura (write(), pwrite(), vfs_write()). La modificación ocurre directamente sobre la Page Cache y el kernel nunca marca la página como dirty, por lo que no se ejecuta el writeback hacia disco.

Una página dirty es una página de memoria cuyo contenido ha sido modificado respecto al almacenado en disco. Cuando el kernel marca una página como dirty, sabe que debe sincronizarla posteriormente mediante writeback.

Ruta tradicional VS Ruta Copy Fail:

Cómo Copy Fail evade los controles habituales Comparación completa entre la ruta tradicional de escritura y la ruta de Copy Fail que evade los controles. Ruta tradicional de escritura execve("/usr/bin/su") Primera ejecución del binario Carga en Page Cache Binario leído desde disco write() / pwrite() Llamada de sistema estándar vfs_write() Capa del sistema de ficheros Página marcada dirty El kernel registra el cambio Writeback a disco Disco y memoria sincronizados execve("/usr/bin/su") Segunda ejecución del binario Disco: SHA256 correcto Memoria y disco coinciden Ruta de Copy Fail execve("/usr/bin/su") Primera ejecución del binario Carga en Page Cache Binario leído desde disco write() / pwrite() Ruta estándar – el atacante la evita AF_ALG + splice() Ruta alternativa sin auditoría Escritura directa en Page Cache Evita vfs_write() completamente Página NO marcada dirty El kernel no registra el cambio Sin writeback a disco Disco intacto – memoria alterada execve("/usr/bin/su") Segunda ejecución del binario Código alterado ejecutado Escalada de privilegios – root Disco: SHA256 correcto Memoria: contenido alterado

Las herramientas habituales de comprobación siguen mostrando el fichero como legítimo: sha256sum, rpm -V, debsums, AIDE o Tripwire, pero el binario ejecutado puede ser distinto del almacenado físicamente, lo que hace a Copy Fail sigiloso frente a mecanismos tradicionales de detección basados en disco.

Impacto en contenedores y Kubernetes


La Page Cache pertenece al kernel y se comparte entre procesos, espacios de nombres y contenedores. Una modificación realizada mediante Copy Fail no queda necesariamente limitada al proceso atacante:

Escape de contenedor via Page Cache compartida Flujo de ataque desde Contenedor A hasta ejecución en Contenedor B, Host y otros procesos. Contenedor A execve("/usr/bin/su") Ejecución desde el contenedor Carga de páginas en Page Cache Binario mapeado en RAM compartida AF_ALG + splice() Escritura controlada sin marcar dirty Corrupción de Page Cache Disco intacto – memoria alterada Página compartida de /usr/bin/su alterada Visible para todos los procesos que usen el binario Contenedor B Host Otros procesos execve("/usr/bin/su") Segunda ejecución Código alterado ejecutado Escape del contenedor

Por este motivo Copy Fail no se considera únicamente una escalada local de privilegios, sino que puede facilitar escapes de contenedor o compromisos del host dependiendo de permisos, namespaces, seccomp y exposición de AF_ALG.

Mitigación


La corrección oficial elimina el funcionamiento in-place dentro de algif_aead y vuelve al modelo out-of-place, separando las regiones de lectura y escritura.

Mientras los parches llegan a todas las distribuciones puede aplicarse una mitigación temporal deshabilitando el módulo vulnerable:

echo "install algif_aead /bin/false" \
> /etc/modprobe.d/disable-algif-aead.conf

Y luego eliminando el módulo del kernel:

rmmod algif_aead

También es recomendable bloquear AF_ALG en entornos Docker, Kubernetes, servidores multiusuario, infraestructuras compartidas y pipelines CI/CD.

Conclusión


Copy Fail demuestra que algunas de las vulnerabilidades de Linux más peligrosas no aparecen por errores clásicos de gestión de memoria. Aquí no se explotan buffer overflows, Use After Free ni race conditions.

El problema expuesto nació de una suposición incorrecta mantenida durante años: que determinadas operaciones AEAD solo trabajarían sobre regiones seguras y aisladas. Esa hipótesis sobrevivió a múltiples cambios del kernel: authencesn (2011), soporte AEAD en AF_ALG (2015) y optimización in-place (2017).

La combinación de todos estos cambios terminó permitiendo una escalada local a root basada únicamente en cuatro bytes escritos sobre la Page Cache. Una vulnerabilidad pequeña en tamaño, pero enorme en impacto.

Fuentes:

Copy Fail - Web oficial de Copy Fail
GitHub - Código fuente de Copy Fail en GitHub
Xint - Copy Fail: 732 Bytes to Root on Every Major Linux Distribution

No hay comentarios:

Publicar un comentario