Neutron, nuestro motor de IA, obtuvo un 96.75% en el benchmark CyberGym de UC Berkeley. Más información

Seguridad

Seguridad

DirtyFrag: escalada de privilegios local universal en Linux mediante escritura en la caché de páginas

Análisis técnico de DirtyFrag, un par de vulnerabilidades de escalada de privilegios local en el kernel de Linux (CVE-2026-43284 y CVE-2026-43500, CVSS 7.8 ALTA) que permiten a cualquier usuario local sin privilegios obtener root en la mayoría de las distribuciones Linux principales. Al encadenar un fallo en la ruta de descifrado en el mismo lugar de xfrm-ESP y de RxRPC, ambos basados en la misma primitiva de escritura en la caché de páginas que Dirty Pipe y Copy Fail, el exploit sobrescribe páginas de caché de solo lectura sin condición de carrera, logrando una fiabilidad cercana al 100%.

DirtyFrag

Escalada de privilegios local universal en Linux mediante escritura en la caché de páginas

7 de mayo de 2026 · CVSS 7.8 ALTA · Kernel de Linux (2017-2026)

CVE ID CVSS Componente Afectado Corregido
CVE-2026-43284 7.8 ALTA xfrm / ESP (esp4, esp6) kernel ≥ cac2661c53f3 (2017-01-17) f4c50a4034e6 (2026-05-08)
CVE-2026-43500 7.8 ALTA RxRPC (rxrpc.ko) kernel ≥ 2dc334f1a63a (2023-06-08) aa54b1d27fe0 (2026-05-10)

Visión general de DirtyFrag: un nuevo miembro de la familia de escrituras «dirty» en la caché de páginas

DirtyFrag es una técnica de escalada de privilegios descubierta y reportada por Hyunwoo Kim (@v4bel) que logra obtener root en la gran mayoría de las distribuciones Linux sin condición de carrera. Pertenece a la misma clase de vulnerabilidades que Dirty Pipe y Copy Fail: un atacante utiliza splice() para fijar una página de la caché de páginas de solo lectura dentro de una estructura de datos del kernel, y el código del kernel realiza posteriormente una escritura en el mismo lugar sobre esa página, corrompiendo de forma permanente la copia en RAM de un archivo que el atacante solo puede leer.

El nombre hace referencia al miembro que queda «sucio» («dirtied»): mientras que Dirty Pipe corrompe struct pipe_buffer, DirtyFrag corrompe el miembro frag de struct sk_buff.

DirtyFrag encadena dos vulnerabilidades independientes para eliminar mutuamente sus puntos ciegos:

  • CVE-2026-43284 (escritura en la caché de páginas vía xfrm-ESP) — una potente primitiva de escritura (STORE) arbitraria de 4 bytes, activada a través de la ruta de entrada de ESP de IPsec. Disponible en la mayoría de las distribuciones, pero requiere el privilegio de crear un espacio de nombres de usuario (unshare(CLONE_NEWUSER)).
  • CVE-2026-43500 (escritura en la caché de páginas vía RxRPC) — una escritura (STORE) de 8 bytes activada a través de la ruta de descifrado en el mismo lugar de RxRPC. No requiere privilegio de espacio de nombres, pero depende de rxrpc.ko, que la mayoría de las distribuciones no incluyen, aunque Ubuntu sí lo carga de forma predeterminada.

Al ejecutar primero la variante ESP y recurrir a la variante RxRPC cuando se bloquea la creación de espacios de nombres (por ejemplo, Ubuntu con el bastionado de AppArmor), un único binario logra obtener root en todas las distribuciones principales.

CVE-2026-43284: escritura en la caché de páginas vía xfrm-ESP

Causa raíz

Antes de ejecutar el descifrado AEAD en el mismo lugar sobre un payload ESP, esp_input() debería llamar a skb_cow_data() en el caso de los skb no lineales, para asignar un búfer privado del kernel y copiar en él los datos del frag. Sin embargo, cuando el skb no está clonado y no tiene frag_list, la función pasa directamente al descifrado:

static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
    [...]
    if (!skb_cloned(skb)) {
        if (!skb_is_nonlinear(skb)) {    // [1]
            nfrags = 1;
            goto skip_cow;
        } else if (!skb_has_frag_list(skb)) {
            nfrags = skb_shinfo(skb)->nr_frags;
            nfrags++;
            goto skip_cow;               // [2] vulnerable path
        }
    }

    err = skb_cow_data(skb, 0, &trailer);
    [...]

En [2], el frag evita la copia por escritura (copy-on-write). Si el atacante fijó una página de la caché de páginas en ese frag mediante splice, el motor AEAD interno del subsistema ESP opera directamente sobre esa página (nota: esto es distinto de algif_aead; AF_ALG no interviene aquí).

La función de descifrado crypto_authenc_esn_decrypt() realiza una escritura (STORE) de 4 bytes durante su etapa de preprocesamiento del número de secuencia ESN, antes de que se ejecute la autenticación:

static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
    [...]
    scatterwalk_map_and_copy(tmp, src, 0, 8, 0);
    if (src == dst) {
        scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
        scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);   // [3] 4-byte STORE
        dst = scatterwalk_ffwd(areq_ctx->dst, dst, 4);
    [...]

El valor de 4 bytes escrito en [3] corresponde a los 32 bits superiores del número de secuencia ESN, un valor que el atacante proporciona al registrar la SA mediante el atributo netlink XFRMA_REPLAY_ESN_VAL. Esto significa que el atacante controla tanto el desplazamiento del archivo de destino (mediante el posicionamiento de splice) como los 4 bytes escritos (mediante el seq_hi de la SA). La autenticación AEAD falla con -EBADMSG, pero la escritura (STORE) ya se ha confirmado en la caché de páginas.

Estrategia de explotación (variante ESP)

El objetivo es /usr/bin/su. El exploit sobrescribe los primeros 192 bytes de su copia en la caché de páginas con un ELF mínimo de shell root:

  • El ELF mapea 0xb8 bytes como R+X en la dirección virtual 0x400000 mediante PT_LOAD.
  • El punto de entrada 0x400078 llama a setgid(0); setuid(0); setgroups(0, NULL); execve("/bin/sh", ...).
  • El flujo de PAM se evita por completo.

Los 192 bytes se dividen en 48 fragmentos de 4 bytes cada uno, entregados cada uno mediante una SA XFRM distinta cuyo seq_hi transporta los bytes del shellcode objetivo.

Un proceso hijo llama primero a unshare(CLONE_NEWUSER | CLONE_NEWNET) para obtener CAP_NET_ADMIN dentro del nuevo espacio de nombres, y después registra las 48 SA de una sola vez. Cada activación envía mediante pipe una cabecera ESP falsificada más 16 bytes de la página de /usr/bin/su (vía vmsplice + splice) a un socket UDP de loopback con UDP_ENCAP_ESPINUDP. El skb resultante transporta la página de la caché de páginas como frags[0], pasa por esp_input y recibe su escritura (STORE) de 4 bytes. Tras las 48 iteraciones, el ELF completo queda ensamblado en RAM, y execve("/usr/bin/su") desde el proceso padre (en el espacio de nombres init) lanza la shell root.

Parche (CVE-2026-43284)

La corrección añade un indicador SKBFL_SHARED_FRAG a los frags de página que llegan mediante splice en las rutas de anexado de datagramas IPv4/IPv6, y amplía la comprobación skip_cow en esp_input / esp6_input para tener en cuenta este indicador:

-} else if (!skb_has_frag_list(skb)) {
+} else if (!skb_has_frag_list(skb) &&
+           !skb_has_shared_frag(skb)) {

Las páginas de la caché de páginas fijadas por el atacante ahora siempre llegan a skb_cow_data() y ya no pueden entrar en la SGL de destino del AEAD en el mismo lugar.

CVE-2026-43500: escritura en la caché de páginas vía RxRPC

Causa raíz

rxkad_verify_packet_1() verifica los paquetes DATA de RxRPC en el nivel RXRPC_SECURITY_AUTH realizando un descifrado pcbc(fcrypt) en el mismo lugar sobre los primeros 8 bytes del payload de rxrpc:

sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
[...]
skcipher_request_set_crypt(req, sg, sg, 8, iv.x);   // [4] src == dst: in-place
ret = crypto_skcipher_decrypt(req);                  // [5] 8-byte STORE

En [4], las SGL de src y dst son idénticas. skb_to_sgvec() convierte el frag del skb directamente en la SGL, de modo que la página de la caché de páginas fijada por el atacante mediante splice se convierte a la vez en src y en dst. La escritura (STORE) de 8 bytes en [5] escribe fcrypt_decrypt(C, K), el resultado de descifrar el texto cifrado en el desplazamiento del frag con la clave de sesión del atacante K.

La clave de sesión se puede plantar libremente, sin ningún privilegio, mediante add_key("rxrpc", ...). A diferencia de la variante ESP, no se necesita ningún espacio de nombres de usuario.

Estrategia de explotación (variante RxRPC)

Dado que el valor de la escritura (STORE) es fcrypt_decrypt(C, K) y no un valor directamente controlado, el atacante prueba K por fuerza bruta en el espacio de usuario hasta obtener el texto plano de 8 bytes deseado. fcrypt es un cifrado AFS con clave de 56 bits; una implementación en espacio de usuario alcanza unos 18 M/s, lo que produce una clave en unos 5 ms por cada bloque poco restringido.

El objetivo es la línea 1 de /etc/passwd. El exploit aplica tres escrituras (STORE) de 8 bytes superpuestas en los desplazamientos de archivo 4, 6 y 8 para transformar "root:x:0:0:root:/root:/bin/bash" en "root::0:0:GGGGGG:/root:/bin/bash". El campo de contraseña se convierte en una cadena vacía; pam_unix.so con nullok la acepta y devuelve PAM_SUCCESS sin pedir contraseña.

La escritura (STORE) en cada posición requiere:

  1. Calcular el texto cifrado encadenado (teniendo en cuenta las escrituras anteriores ya aplicadas a la página).
  2. Probar K por fuerza bruta de modo que fcrypt_decrypt(C_actual, K) produzca el texto plano deseado.
  3. Registrar la clave con add_key, configurar un intercambio (handshake) de AF_RXRPC por loopback, precalcular la suma de comprobación del paquete y entregar un paquete DATA falsificado mediante vmsplice + splice.

Esta variante no llama a unshare(); todas las llamadas al sistema (add_key, socket(AF_RXRPC), socket(AF_ALG), splice, recvmsg) están disponibles para usuarios sin privilegios.

Parche (CVE-2026-43500)

La corrección amplía la comprobación previa al descifrado en el mismo lugar en call_event.c y conn_event.c, pasando de una única comprobación skb_cloned a detectar también los skb compartidos y los que tienen lista de frags:

-if (skb_cloned(skb)) {
+if (skb_cloned(skb) || skb_has_frag_list(skb) ||
+   skb_has_shared_frag(skb)) {

Los skb que transportan frags paginados fijados externamente ahora se aíslan mediante skb_copy() antes de llegar al destino del descifrado.

DirtyFrag: cadena de explotación: un único binario, root universal

La lógica de encadenamiento es sencilla:

1. Attempt ESP variant (child: unshare USER+NET → register XFRM SAs → splice → patch /usr/bin/su)
2. Read back the first shellcode byte at the entry offset of /usr/bin/su.
   - Success → parent: forkpty + execve("/usr/bin/su") → root shell.
3. Failure (unshare denied, esp4.ko absent, or SA registration fails):
   - Fall back to RxRPC variant:
     K brute-force × 3 → splice triggers → /etc/passwd line 1 cleared
     forkpty + execve("/usr/bin/su") → PAM nullok → root shell.

La cadena combinada se ha confirmado en: Ubuntu 24.04.4 (kernel 6.17.0-23), RHEL 10.1 (6.12.0-124.49.1), openSUSE Tumbleweed (7.0.2-1), CentOS Stream 10, AlmaLinux 10 y Fedora 44. No hay ninguna condición de carrera implicada, el kernel no entra en pánico en caso de fallo, y la tasa de éxito es cercana al 100%.

Prueba de concepto de DirtyFrag

La PoC pública es un único archivo en C. Compile y ejecútela con:

git clone https://github.com/V4bel/dirtyfrag.git && cd dirtyfrag && gcc -O0 -Wall -o exp exp.c -lutil && ./exp

Entorno de pruebas

El exploit se probó en una máquina virtual local de VirtualBox con Ubuntu 24.04.1 y un kernel sin parchear:

Linux aziz-VirtualBox 6.17.0-23-generic #23~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 14 16:11:48 UTC 2 x86_64 GNU/Linux

Información del sistema: Ubuntu 24.04.1, kernel 6.17.0-23-generic
Figura 1: sistema objetivo: Ubuntu 24.04.1, kernel 6.17.0-23-generic (sin parchear)

Explotación

La cadena de explotación ejecutó primero la variante ESP (la creación de espacios de nombres de usuario está permitida en este kernel), sobrescribió la caché de páginas de /usr/bin/su con el ELF de la shell root, y devolvió una shell root en menos de un segundo:

PoC de DirtyFrag: shell root obtenida
Figura 2: un usuario sin privilegios obtiene uid=0(root) gid=0(root) groups=0(root) mediante DirtyFrag

Mitigación inmediata

Hasta que haya disponible una actualización del kernel, deshabilite los módulos vulnerables y vacíe la caché de páginas:

sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' \
  > /etc/modprobe.d/dirtyfrag.conf; \
  rmmod esp4 esp6 rxrpc 2>/dev/null; \
  echo 3 > /proc/sys/vm/drop_caches; true"

Corrección

  • Aplique las actualizaciones del kernel de su distribución en cuanto haya un backport disponible.
  • Verifique que los módulos esp4, esp6 y rxrpc no estén cargados en los sistemas que no necesitan IPsec ni AFS (lsmod | grep -E 'esp4|esp6|rxrpc').
  • En Ubuntu, revise la política de AppArmor relativa a la creación de espacios de nombres de usuario sin privilegios; aunque esto bloquea la ruta ESP, la ruta RxRPC sigue abierta de todos modos.
  • Supervise si se producen anomalías en la caché de páginas en binarios setuid sensibles y en /etc/passwd.

Referencias

Recurso Enlace
PoC en GitHub (V4bel/dirtyfrag) https://github.com/V4bel/dirtyfrag
Parche del kernel CVE-2026-43284 (f4c50a4034e6) https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4
Parche del kernel CVE-2026-43500 (aa54b1d27fe0) https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aa54b1d27fe0c2b78e664a34fd0fdf7cd1960d71
NVD CVE-2026-43284 https://nvd.nist.gov/vuln/detail/CVE-2026-43284
Aviso de Red Hat RHSB-2026-003 https://access.redhat.com/security/vulnerabilities/RHSB-2026-003
Aviso de seguridad de Ubuntu https://ubuntu.com/blog/dirty-frag-linux-vulnerability-fixes-available