../blog

23 de mayo de 2026

Copy Fail y Dirty Frag: escribir donde solo podías leer

Una nota práctica sobre Copy Fail, Dirty Frag y por qué las escrituras en page cache rompen una frontera peligrosa dentro del kernel de Linux.

Nota: este post es divulgativo. Evita pasos de explotación y simplifica algunos detalles internos del kernel.

Hay bugs que parecen errores aislados. Otros parecen una advertencia.

Copy Fail y Dirty Frag pertenecen al segundo grupo.

A primera vista, se pueden resumir de forma bastante seca: dos problemas del kernel de Linux que pueden acabar en escalada local de privilegios. Otra LPE, otro proof of concept, otro día en internet.

Ese resumen sirve, pero se queda corto.

Lo importante no es solo que estos bugs puedan terminar en root. Lo interesante es dónde rompen el modelo mental: en la zona donde el kernel mueve datos de forma eficiente, evita copias, reutiliza páginas de memoria y asume que cada subsistema sigue recordando qué representan esas páginas.

A veces esa suposición falla.

La idea central

El usuario solo puede leer un fichero, pero consigue que una ruta legítima del kernel escriba sobre la versión cacheada de ese fichero en memoria.

No escribe necesariamente en disco.

No abre el fichero con permisos de escritura.

No rompe la puerta principal.

Convence al edificio de que le deje pintar la pared por dentro.

La pared, en este caso, es la page cache.

La page cache, ese sitio donde pasan demasiadas cosas

La page cache existe para que acceder a ficheros sea rápido. Si un binario, una librería compartida o un fichero leído con frecuencia ya está en memoria, Linux puede servir futuras lecturas sin volver al disco.

Hasta aquí, todo bien.

El problema empieza cuando esas páginas cacheadas circulan por rutas del kernel que dejan de tratarlas como “contenido de un fichero que este usuario solo puede leer” y empiezan a tratarlas como buffers normales.

Y cuando un buffer se puede modificar, la frontera de seguridad se vuelve borrosa.

usuario sin privilegios
lee
fichero solo lectura
se cachea como
page cache
pasa por
crypto / red / otro subsistema
escritura in-place inesperada
página cacheada modificada

La parte importante no es tener permisos de escritura sobre el fichero. La parte importante es conseguir que el kernel escriba en una página que representa ese fichero.

Copy Fail: el bug que enseña la forma

Copy Fail está en la intersección entre splice(), AF_ALG y el subsistema criptográfico del kernel.

La forma general es elegante, como suelen serlo algunos bugs de kernel: splice() puede conectar páginas de la page cache respaldadas por un fichero con una ruta pensada para mover datos de forma eficiente. Después, una operación criptográfica que no debería mutar ese contenido termina provocando una escritura controlada en la page cache.

Cuatro bytes no suenan a mucho.

Pero cuatro bytes en el sitio correcto son una palanca.

En seguridad, “solo puedo escribir cuatro bytes” muchas veces significa “puedo escribir cuatro bytes donde nadie esperaba que pudiera escribir absolutamente nada”. Esa diferencia es enorme.

splice() páginas de page cache socket AF_ALG ruta crypto escritura in-place ups

Lo más interesante de Copy Fail no es solo el bug. Es cómo se planteó la investigación.

El punto de partida fue una hipótesis humana: ¿qué ocurre cuando se combinan AF_ALG, splice() y páginas cuya procedencia real importa? Después, la automatización ayudó a escalar esa hipótesis sobre una superficie de código demasiado grande para revisarla cómodamente a mano.

Esa es una imagen realista de la IA aplicada a vulnerability research.

No es:

“La IA encontró root.”

Es más bien:

“Alguien tuvo una intuición concreta, la convirtió en una hipótesis buscable y usó herramientas para encontrar variantes.”

Menos cinematográfico. Mucho más útil.

Dirty Frag: cuando cambia la pregunta

Dirty Frag es la continuación natural de esa pregunta.

Después de ver Copy Fail, lo importante no es solo:

“¿Cómo parcheamos este bug?”

Es:

“¿Dónde más está haciendo el kernel algo parecido?”

Ahí empieza el trabajo real de investigación.

Dirty Frag lleva una clase de problema similar a rutas relacionadas con networking, especialmente xfrm/ESP y RxRPC.

No es el mismo bug. No vive en el mismo subsistema. No se explota exactamente igual.

Pero rima.

Y cuando dos bugs de kernel riman, conviene escuchar.

Copy Fail

splice() + AF_ALG + ruta crypto

Resultado: primitiva de escritura en page cache

Dirty Frag

rutas xfrm/ESP y RxRPC

Resultado: patrón similar de escritura donde solo debía haber lectura

La conexión profunda no está en un nombre de función concreto. Está en el patrón:

  1. Un usuario puede leer algo.
  2. El kernel representa ese contenido en la page cache.
  3. Una ruta eficiente evita copiar datos.
  4. Otro subsistema recibe esas páginas como si fueran buffers normales.
  5. Ese subsistema escribe in-place.
  6. La frontera entre "leer" y "modificar" se rompe.

Ese patrón importa más que cualquier CVE individual, porque cuando lo entiendes puedes empezar a buscar variantes.

No es solo memoria. No es solo disco.

Esta es la parte que me parece especialmente interesante.

Cuando hablamos de corrupción de memoria, solemos imaginar heap, stack, punteros, objetos liberados o estructuras internas del kernel.

Cuando hablamos de permisos de ficheros, pensamos en disco: propietarios, mode bits, binarios setuid, hashes, comprobaciones de integridad.

Estos bugs viven entre esos dos mundos.

no es solo un fichero page cache no es solo memoria anónima

La page cache es un puente. Atacar el puente puede ser más interesante que atacar cualquiera de las dos orillas.

El fichero en disco puede seguir intacto. Las herramientas que solo verifican hashes en disco pueden no ver nada extraño. Los permisos pueden parecer normales. Pero la vista cacheada que el sistema usa realmente puede haber sido modificada.

Ese detalle es lo que hace que estos problemas recuerden inevitablemente a Dirty Pipe. No son el mismo bug, pero obligan a aceptar la misma lección incómoda: la page cache no es un detalle pasivo de implementación. Es una superficie de ataque.

Fallos de composición

Hay una forma sencilla de escribir bugs en C: equivocarte tú solo.

Luego está la forma más interesante: que dos subsistemas sean razonables por separado y peligrosos juntos.

Copy Fail y Dirty Frag huelen a eso.

Un mecanismo dice:

“Voy a evitar una copia, porque copiar es caro.”

Otro dice:

“Voy a procesar estos datos in-place, porque es eficiente.”

Otro ya había decidido:

“Este usuario solo puede leer este fichero.”

Cada decisión aislada puede tener sentido. El problema aparece cuando se juntan.

rendimiento + abstracciones reutilizables + permisos decididos antes + origen ambiguo del buffer = fallo de frontera

Este tipo de fallo es atractivo para un investigador porque rara vez está en el sitio obvio. No es necesariamente un memcpy() sospechoso pidiendo atención.

Es una propiedad emergente del sistema.

Y las propiedades emergentes son donde el kernel se pone interesante.

Publicar también es parte del bug

La publicación de estas vulnerabilidades cuenta una segunda historia.

Copy Fail tuvo una narrativa relativamente limpia: investigación, reporte, parche y write-up. El ángulo de búsqueda asistida por IA lo hizo especialmente visible, pero la lección de seguridad es más antigua y profunda que la herramienta.

Dirty Frag fue más caótica. La información pública describe un proceso de disclosure que se volvió más urgente de lo esperado, y eso cambió la dinámica para maintainers, distribuciones, vendors y equipos de defensa.

La vida de una vulnerabilidad no acaba cuando alguien consigue root en una máquina de pruebas.

Después viene la parte social del bug:

researcher maintainers distros vendors detecciones admins intentando dormir

Cuando un bug de kernel es práctico, afecta a configuraciones comunes y tiene detalles técnicos públicos, el disclosure deja de ser un trámite. Se convierte en una carrera entre parches, mitigaciones, proof-of-concept, detecciones y gente copiando comandos de GitHub con demasiada confianza.

Una nota incómoda sobre impacto

La forma tentadora de pensar en estos bugs es verlos como una ruta rápida para “editar algo sensible y que funcione”.

Un binario privilegiado, una librería usada por root, una decisión de autenticación o cualquier otro punto de confianza del sistema pueden parecer objetivos naturales cuando tienes una primitiva de escritura sobre page cache.

Pero el truco no consiste necesariamente en cambiar el disco para siempre.

A veces lo interesante es justo lo contrario: alterar temporalmente la vista cacheada que el sistema usa en ese momento mientras el fichero real sigue pareciendo intacto. El atacante no necesita que el cambio sea elegante ni duradero si solo tiene que cruzar una frontera una vez.

Desde defensa, la pregunta no debería ser solo:

“¿Ha cambiado este fichero en disco?”

También debería ser:

“¿Qué proceso ha conseguido que el kernel trate contenido de solo lectura como un buffer escribible?”

Esa diferencia importa.

El disco puede parecer limpio. Los permisos pueden parecer normales. Los hashes pueden encajar. Y aun así el comportamiento observado puede no tener sentido.

Ahí está la parte incómoda.

Ideas defensivas

Para revisar kernel

Hay que distinguir si una página son solo bytes a procesar o contenido respaldado por un fichero con semántica de permisos.

Para defender sistemas

No basta con mirar integridad en disco si el comportamiento sospechoso puede venir de una vista cacheada y transitoria.

Para investigar

Busca rutas eficientes que pasen páginas de fichero a subsistemas capaces de mutarlas in-place.

Conclusión

Copy Fail y Dirty Frag son buenos recordatorios de que los bugs más interesantes no siempre viven en una línea concreta. A veces viven entre fronteras.

Entre crypto y filesystem.

Entre networking y memoria.

Entre rendimiento y seguridad.

Entre “esto solo evita una copia” y “acabamos de escribir donde solo podíamos leer”.

La page cache no es solo una optimización transparente. Es una zona compartida, caliente y sensible del kernel.

Cada vez que una página cacheada entra en una ruta que puede escribir in-place, merece la pena hacerse la pregunta incómoda:

¿Estamos modificando un buffer, o estamos modificando algo que representa un fichero que nadie debería poder tocar?

Copy Fail hizo visible la pregunta.

Dirty Frag demostró que merecía la pena seguir preguntando.

Lecturas