
Exploit PoC en Rust para LPE del kernel de Linux CVE-2026-31431 (escritura en page-cache de AF_ALG) más defensa en tiempo de ejecución con eBPF que bloquea la creación de sockets AF_ALG mediante LSM o kprobe sin reiniciar.
CVE-2026-31431 (Copy Fail) es una vulnerabilidad de escalada de privilegios local en el módulo algif_aead del kernel de Linux (subsistema AF_ALG). Un fallo lógico introducido en agosto de 2017 permite a cualquier usuario local sin privilegios escribir 4 bytes controlados en la caché de páginas de cualquier archivo legible mediante AF_ALG + splice(), y luego ejecutar un binario setuid corrupto para obtener acceso root. El exploit es determinista: sin condiciones de carrera, sin offsets del kernel, sin bloqueos del sistema. Afecta a todas las principales distribuciones de Linux con kernels desde 4.14 hasta 7.0-rc.
CVSS: 7.8 | Corregido en: kernel 7.0, 6.19.12, 6.18.22 | Corrección en mainline: commit a664bf3d603d
Fuentes: copy.fail, The Hacker News, Aviso de CloudLinux
crates/
├── exp/ # Exploit PoC (reimplementación en Rust)
├── copy_fail_guard/ # Cargador eBPF en espacio de usuario — modo LSM
├── copy_fail_guard-ebpf/ # Programa eBPF LSM (fuera del workspace)
├── copy_fail_guard_kprobe/ # Cargador eBPF en espacio de usuario — modo kprobe
└── copy_fail_guard_kprobe-ebpf/ # Programa eBPF kprobe (fuera del workspace)
scripts/
└── build_guard.sh # Compilación con un clic → salida en dist/
La causa raíz es una cadena de tres características independientes del kernel que interactúan de forma insegura:
AF_ALG — expone la API criptográfica del kernel al espacio de usuario sin privilegiossplice() — transfiere datos de archivos con copia cero como referencias a la caché de páginas (no copias) hacia el scatterlist criptográficoauthencesn — usa el buffer de salida del llamador como espacio temporal, escribiendo 4 bytes en dst[assoclen + cryptlen]En 2017, una optimización in-place en algif_aead.c (72548b093ee3) hizo que req->src == req->dst, encadenando páginas de la caché de páginas en el scatterlist de destino escribible. Cuando authencesn escribe sus bytes temporales, sobrepasa el buffer de salida hacia las páginas encadenadas de la caché de páginas. El atacante controla:
La página corrupta nunca se marca como sucia: el archivo en disco no se toca, pero execve() lee desde la caché de páginas. Corrompe un binario setuid → root.
Reimplementación en Rust del PoC público de 732 bytes en Python. Apunta a /usr/bin/su, hace splice de sus páginas de caché hacia un socket AEAD AF_ALG y las sobrescribe con una carga útil de shell comprimida.
Requiere Rust 1.85+ (edición 2024).
cargo build --release -p copy_fail
El binario es solo para Linux. En otras plataformas sale con un error Unsupported.
Advertencia: Esto explota una falla real del kernel. Ejecútalo solo en sistemas que poseas y controles, idealmente una VM desechable.
Inicia una VM con un kernel vulnerable (cualquier distribución convencional con kernel < 7.0 / < 6.19.12 / < 6.18.22).
Copia el binario compilado a la VM y ejecútalo como usuario sin privilegios:
./target/release/copy_fail
Vulnerable: aparece un shell root (#). Ejecuta whoami para confirmar root.
No vulnerable (kernel parcheado): la operación AF_ALG falla o el binario su se comporta con normalidad. Verás un error o un prompt normal de contraseña de su.
Una defensa del kernel en tiempo de ejecución que bloquea CVE-2026-31431 sin actualizar el kernel ni reiniciar. Usa eBPF para interceptar la creación de sockets AF_ALG, cortando el primer paso del exploit.
Se proporcionan dos modos. El cargador con un clic selecciona automáticamente el mejor modo disponible:
| Modo LSM | Modo kprobe | |
|---|---|---|
| Cómo bloquea | Devuelve -EPERM (creación de socket denegada) | SIGKILL (proceso eliminado) |
| Requisito del kernel | ≥ 5.7 con parámetro de arranque lsm=bpf | ≥ 5.3, sin parámetros especiales |
| Requiere reinicio para activar | Quizás (si lsm=bpf no está configurado) | No |
| Punto de enganche | Hook LSM socket_create | kprobe __sys_socket |
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • Detecta soporte BPF LSM │
│ • LSM disponible → copy_fail_guard (EPERM) │
│ • LSM no disponible → copy_fail_guard_kprobe (KILL)│
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ Programa eBPF │
│ si familia de socket == 38 (AF_ALG) │
│ → bloquear (EPERM o SIGKILL) │
│ si no │
│ → permitir │
└──────────────────────────────────────────────────┘
Bloquear AF_ALG tiene un impacto casi nulo en sistemas típicos:
AF_ALGAF_ALG (p. ej., OpenSSL con el motor afalg habilitado, algunas rutas de descarga criptográfica embebida)socket(AF_ALG, ...)Compilación con un clic (recomendada):
./scripts/build_guard.sh
Esto instala automáticamente las toolchains faltantes (nightly, bpf-linker), compila todos los programas eBPF y los cargadores de espacio de usuario, y genera todo en dist/:
dist/
├── copy_fail_guard.bpf.o # Programa eBPF LSM
├── copy_fail_guard_kprobe.bpf.o # Programa eBPF kprobe
├── copy_fail_guard # Cargador de espacio de usuario (LSM)
├── copy_fail_guard_kprobe # Cargador de espacio de usuario (kprobe)
└── run_guard.sh # Auto-selección: sudo ./run_guard.sh
Copia el directorio dist/ a cualquier máquina de destino y ejecuta sudo ./run_guard.sh para activar la protección.
Compilación manual (paso a paso):
Paso 1: Compilar los programas eBPF (debe hacerse en Linux):
# Variante LSM
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# Variante kprobe
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
Paso 2: Compilar los cargadores de espacio de usuario:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
Recomendado — selecciona automáticamente el mejor modo:
sudo ./dist/run_guard.sh
Manual — ejecuta un modo específico:
# Modo LSM (requiere lsm=bpf)
sudo GUARD_BPF_OBJ=ruta/al/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# Modo kprobe (funciona en todas partes)
sudo GUARD_BPF_OBJ=ruta/al/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Pulsa Ctrl-C para desacoplar el programa eBPF y restaurar el comportamiento normal.
Con el guard ejecutándose en una terminal:
# En otra terminal, intenta el exploit:
./target/release/copy_fail
# Modo LSM: "error: Operation not permitted"
# Modo kprobe: "已杀死" / "Killed"
# O prueba directamente con Python:
python3 -c "import socket; socket.socket(38, 5, 0)"
# Modo LSM: PermissionError: [Errno 1] Operation not permitted
# Modo kprobe: Killed
AF_ALG de espacio de usuario de forma incondicional. No hay mecanismo para eximir procesos específicos por PID, cgroup o nombre de comando. Esto está planificado para una versión futura (mediante mapas HashMap de eBPF). Para la gran mayoría de sistemas esto es aceptable, ya que casi nada usa AF_ALG.bpf-linker (apuntan a bpfel-unknown-none y no pueden ser miembros normales del workspace).La mayoría de las distribuciones no habilitan BPF LSM de forma predeterminada. Si deseas el modo LSM más limpio (EPERM en lugar de SIGKILL):
# Verifica los LSM actuales:
cat /sys/kernel/security/lsm
# Si falta "bpf", agrégalo:
sudo sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,yama,apparmor,bpf"/' /etc/default/grub
sudo update-grub && sudo reboot
El modo kprobe funciona sin este paso.
Después de parchear el kernel a ≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22:
# Confirma la versión del kernel
uname -r
# Vuelve a ejecutar el exploit — ya no debería producir un shell root
./target/release/copy_fail
Alternativamente, confirma que el módulo vulnerable está neutralizado:
modinfo algif_aead | grep filename
# Si está integrado, bloquea el initcall (requiere reinicio)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot