
Exploit para CVE-2026-46215, una escalada de privilegios local por use-after-free en DRM GEM del kernel de Linux. Utiliza carreras, slab spraying y sobrescritura de archivos estilo Dirty Pipe para convertir a un usuario no privilegiado del nodo de renderizado en root sin contraseña.
change_handle Use-After-Free (LPE sin privilegios)Escalada de privilegios local a través de un use-after-free en el ioctl del núcleo DRM
DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, ioctl nr 0xD2).
Accesible por cualquier usuario con acceso a un nodo de renderizado (/dev/dri/renderD*,
otorgado a la sesión activa por systemd-logind en todas las principales distribuciones
de escritorio). La cadena en este repositorio lleva el UAF a root sin contraseña desde
un usuario sin privilegios.
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)53096728b891 ("drm: Add DRM prime interface to reassign GEM handle", David Francis / AMD), añadido para el trabajo CRIU de AMD5e28b7b94408). El ioctl también se está desactivando en upstream 7.1 debido a esto y a race conditions relacionadas.Este error fue reportado por primera vez por Puttimet Thammasaeng, quien tiene el crédito
Reported-by en la corrección upstream. Lo encontré y lo reporté de forma independiente a
[email protected] el 12 de abril de 2026. Mi reporte fue reconocido y reenviado a
los mantenedores, pero el reporte anterior es el que se acredita upstream. Este repositorio
contiene mi propio análisis y exploit.
Writeup: https://cyberstan.co.uk
La corrección está en kernels estables publicados (6.18.32 y 7.0.9 en adelante). Este repositorio fue publicado después de que las correcciones estuvieran ampliamente disponibles.
drm_gem_change_handle_ioctl() mueve un objeto GEM de un identificador a otro
pero nunca ajusta obj->handle_count. También omite drm_vma_node_allow/revoke
y los callbacks de open/close del driver. Debido a que handle_count permanece en 1, un
GEM_CLOSE concurrente en el identificador antiguo lo reduce a 0 y libera el objeto
mientras el nuevo identificador aún lo referencia en el IDR. Ese identificador colgante es el
use-after-free, posteriormente desreferenciado en drm_gem_object_release_handle().
GEM_CHANGE_HANDLE y GEM_CLOSE para obtener un identificador colgante.pipe_buffer rociado
(feng shui de msg_msg para condicionar kmalloc-512, luego pipes llenados con splice).pipe_buf_ops a través de un ioctl de información del driver: obj->size (offset 216)
se superpone con pipe_buf[5].ops, dando un puntero del kernel y la base de KASLR.obj->name (offset 224) caiga sobre
pipe_buf[5].flags y establezca PIPE_BUF_FLAG_CAN_MERGE (name = 16 = 0x10).Los offsets están verificados mediante pahole y son específicos del diseño entre 6.18 y
7.0. Sobrescribir las definiciones GEM_* / PIPEBUF_* para otros kernels.
poc.c - el exploit. Compilar estático con -lpthread.run_exploit.sh - construye el PoC y un initramfs mínimo, lo arranca en QEMU.qemu-system-x86_64, gcc, busybox (estático), fakeroot, cpio, gzip.
Se recomienda KVM (/dev/kvm); la race condition es mucho más fiable con él.
El exploit necesita un objetivo vulnerable construido con opciones específicas:
CONFIG_KASAN debe estar DESACTIVADO. KASAN pone en cuarentena los slabs liberados y bloquea la
reclamación de pipe-spray, por lo que el exploit no funcionará contra una compilación con KASAN.virtio_gpu (usado en la demo) o nouveau.CONFIG_DRM=y, CONFIG_DRM_VIRTIO_GPU=y, CONFIG_DEVTMPFS=y,
CONFIG_BLK_DEV_INITRD=y.Pasos:
CONFIG_KASAN no esté
activado.make -j"$(nproc)" bzImage, produciendo arch/x86/boot/bzImage.La demo arranca con nokaslr para que el puntero filtrado sea determinista. La filtración
vence a KASLR por sí sola, así que KASLR activado también funciona, la dirección varía en cada
arranque.
Mirar drivers/gpu/drm/drm_gem.c, función drm_gem_change_handle_ioctl().
Comprobación rápida:
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 significa VULNERABLE (sin manejo de refcount).Por estructura:
file_priv->prime.lock, un único
idr_alloc(&file_priv->object_idr, obj, ...), luego idr_remove() en el identificador
antiguo. No hay drm_gem_object_handle_get.idr_alloc luego idr_replace(NULL) para desvincular el
identificador antiguo), con el objeto real intercambiado solo cuando las operaciones prime
tienen éxito../run_exploit.sh /path/to/bzImage
Construye poc.c, lo envuelve en un initramfs y arranca QEMU con
-device virtio-gpu-pci y nokaslr. El PoC se ejecuta como uid 1000 (sin privilegios),
luego el script imprime /etc/passwd antes y después y te deja en un shell.
Sal de la VM con poweroff -f o Ctrl-A X.
[!] Race won (iter 977): handle=132049
[!] KASLR: pipe_buf_ops = 0xffffffff82428400
[!] EXPLOIT SUCCESSFUL
[!] FLINK: 16 = 0x10
[*] /etc/passwd:
root::0:0:pwned:/root:/bin/sh
[!] LPE CONFIRMED
[!] root account is now passwordless
Un /etc/passwd de solo lectura (chmod 444) propiedad de root es sobrescrito por un
proceso sin privilegios. La línea root: pierde su campo de contraseña.
Reconstruir contra un árbol parcheado (6.18.32, 7.0.9, 7.1-rc3 o posterior, o cualquier árbol que pase la comprobación de parcheo anterior) y volver a ejecutar:
./run_exploit.sh /path/to/patched-bzImage
Contra el kernel parcheado el exploit nunca debería alcanzar "EXPLOIT SUCCESSFUL": la race ya no libera el objeto por debajo del nuevo identificador, por lo que la filtración nunca devuelve un puntero válido del kernel.
Publicado con fines de investigación y defensa después de que las correcciones upstream hayan sido distribuidas. Se proporciona tal cual. Ejecutar solo contra VMs que controles.
/etc/passwd