
Exploit per CVE-2026-46215, un use-after-free del kernel Linux DRM GEM per l'escalation dei privilegi locali. Utilizza racing, slab spraying e sovrascrittura di file in stile Dirty Pipe per trasformare un utente non privilegiato del nodo di rendering in root senza password.
change_handle Use-After-Free (LPE senza privilegi)Escalation di privilegi locali tramite un use-after-free nell'ioctl del core DRM DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, ioctl nr 0xD2). Raggiungibile da qualsiasi utente con accesso a un nodo di rendering (/dev/dri/renderD*, concesso alla sessione attiva da systemd-logind su tutte le principali distribuzioni desktop). La catena di exploit in questo repository porta dall'UAF a root senza password da un utente non privilegiato.
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), aggiunto per il lavoro CRIU di AMD5e28b7b94408). L'ioctl viene anche disabilitato upstream nella 7.1 a causa di questa e di altre race condition correlate.Questo bug è stato segnalato per la prima volta da Puttimet Thammasaeng, che detiene il credito Reported-by upstream sulla correzione. L'ho trovato e segnalato indipendentemente a [email protected] il 2026-04-12. Il mio report è stato riconosciuto e inoltrato ai manutentori, ma il report precedente è quello accreditato upstream. Questo repository contiene la mia analisi e il mio exploit.
Writeup: https://cyberstan.co.uk
La correzione è nei kernel stabili rilasciati (6.18.32 e 7.0.9 in poi). Questo repository è stato pubblicato dopo che le correzioni sono diventate ampiamente disponibili.
drm_gem_change_handle_ioctl() sposta un oggetto GEM da un handle a un altro ma non regola mai obj->handle_count. Salta anche drm_vma_node_allow/revoke e i callback driver open/close. Poiché handle_count rimane a 1, una GEM_CLOSE concorrente sul vecchio handle lo porta a 0 e libera l'oggetto mentre il nuovo handle lo referenzia ancora nell'IDR. Quel handle pendente è l'use-after-free, successivamente dereferenziato in drm_gem_object_release_handle().
GEM_CHANGE_HANDLE e GEM_CLOSE per ottenere un handle pendente.pipe_buffer spruzzato (feng shui con msg_msg per condizionare kmalloc-512, poi pipe riempite con splice).pipe_buf_ops tramite un ioctl info del driver: obj->size (offset 216) si sovrappone a pipe_buf[5].ops, fornendo un puntatore al kernel e la base di KASLR.obj->name (offset 224) cada su pipe_buf[5].flags e imposti PIPE_BUF_FLAG_CAN_MERGE (name = 16 = 0x10)./etc/passwd, root diventa senza password.Gli offset sono verificati tramite pahole e sono specifici del layout attraverso 6.18 fino a 7.0. Sovrascrivi le definizioni GEM_* / PIPEBUF_* per altri kernel.
poc.c - l'exploit. Compila staticamente con -lpthread.run_exploit.sh - costruisce il PoC e un initramfs minimo, lo avvia in QEMU.qemu-system-x86_64, gcc, busybox (statico), fakeroot, cpio, gzip.
KVM (/dev/kvm) è raccomandato; la race condition è molto più affidabile con esso.
L'exploit necessita di un target vulnerabile compilato con opzioni specifiche:
CONFIG_KASAN deve essere SPENTO. KASAN mette in quarantena gli slab liberati e blocca il recupero tramite pipe spray, quindi l'exploit non funzionerà contro una build con KASAN.virtio_gpu (usato nella demo) o nouveau.CONFIG_DRM=y, CONFIG_DRM_VIRTIO_GPU=y, CONFIG_DEVTMPFS=y,
CONFIG_BLK_DEV_INITRD=y.Passaggi:
CONFIG_KASAN non sia impostato.make -j"$(nproc)" bzImage, producendo arch/x86/boot/bzImage.La demo si avvia con nokaslr in modo che il puntatore trapelato sia deterministico. La trapelatura sconfigge KASLR da sola, quindi anche con KASLR acceso funziona, l'indirizzo varia semplicemente a ogni avvio.
Guarda in drivers/gpu/drm/drm_gem.c, funzione drm_gem_change_handle_ioctl().
Controllo rapido:
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 significa VULNERABILE (nessuna gestione del refcount).Per struttura:
file_priv->prime.lock, un singolo idr_alloc(&file_priv->object_idr, obj, ...), poi idr_remove() sul vecchio handle. Nessun drm_gem_object_handle_get.idr_alloc poi idr_replace(NULL) per staccare il vecchio handle), con il vero oggetto scambiato solo dopo che le operazioni prime hanno successo../run_exploit.sh /path/to/bzImage
Costruisce poc.c, lo impacchetta in un initramfs e avvia QEMU con -device virtio-gpu-pci e nokaslr. Il PoC viene eseguito come uid 1000 (non privilegiato), poi lo script stampa /etc/passwd prima e dopo e ti lascia una shell. Esci dalla 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 di sola lettura (chmod 444) di proprietà di root viene sovrascritto da un processo non privilegiato. La riga root: perde il campo password.
Ricostruisci contro un albero corretto (6.18.32, 7.0.9, 7.1-rc3 o successivo, o qualsiasi albero che superi il controllo di cui sopra) e ri-esegui:
./run_exploit.sh /path/to/patched-bzImage
Contro il kernel corretto l'exploit non dovrebbe mai raggiungere "EXPLOIT SUCCESSFUL": la race non libera più l'oggetto mentre il nuovo handle è in uso, quindi la trapelatura non restituisce mai un puntatore valido al kernel.
Pubblicato per scopi di ricerca e difensivi dopo che le correzioni upstream sono state distribuite. Fornito così com'è. Eseguilo solo su VM che controlli.