
Proof-of-Concept Exploit für CVE-2026-46215, eine Use-After-Free-Schwachstelle im Linux DRM GEM change_handle ioctl. Zeigt eine unprivilegierte lokale Privilegieneskalation zu root durch Slab-Reclamation und DirtyPipe-ähnlichen Page-Cache-Überschreiben.
change_handle Use-After-Free (unprivileged LPE)Lokale Privilegienerweiterung durch einen Use-After-Free im DRM-Core-IOCTL
DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, ioctl-Nr. 0xD2).
Erreichbar für jeden Benutzer mit Zugriff auf einen Render-Node (/dev/dri/renderD*,
gewährt der aktiven Sitzung durch systemd-logind auf allen großen Desktop-
Distributionen). Die Kette in diesem Repository führt die UAF zu einem passwortlosen
Root-Zugriff von einem unprivilegierten Benutzer.
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), hinzugefügt für AMDs CRIU-Arbeit5e28b7b94408). Das IOCTL wird upstream in 7.1 aufgrund dieses und verwandter Race-Conditions ebenfalls deaktiviert.Dieser Fehler wurde zuerst von Puttimet Thammasaeng gemeldet, der die upstream-
Reported-by-Anerkennung im Fix erhält. Ich habe ihn unabhängig gefunden und am
2026-04-12 an [email protected] gemeldet. Meine Meldung wurde bestätigt und an die
Maintainer weitergeleitet, aber die frühere Meldung wird upstream anerkannt. Dieses
Repository enthält meine eigene Analyse und meinen Exploit.
Writeup: https://cyberstan.co.uk
Der Fix befindet sich in veröffentlichten stabilen Kerneln (6.18.32 und 7.0.9 aufwärts). Dieses Repository wurde veröffentlicht, nachdem Korrekturen allgemein verfügbar waren.
drm_gem_change_handle_ioctl() verschiebt ein GEM-Objekt von einem Handle zu einem anderen,
passt aber nie obj->handle_count an. Es überspringt auch drm_vma_node_allow/revoke
und die Treiber-Open/Close-Callbacks. Da handle_count bei 1 bleibt, senkt ein
gleichzeitiges GEM_CLOSE auf dem alten Handle es auf 0 und gibt das Objekt frei,
während das neue Handle noch darauf im IDR verweist. Dieses hängende Handle ist der
Use-After-Free, der später in drm_gem_object_release_handle() dereferenziert wird.
GEM_CHANGE_HANDLE gegen GEM_CLOSE, um ein hängendes Handle zu erhalten.pipe_buffer-Array
(msg_msg Feng Shui, um kmalloc-512 zu konditionieren, dann mit Splice gefüllte Pipes).pipe_buf_ops durch ein Treiber-Info-IOCTL: obj->size (Offset 216)
überschneidet sich mit pipe_buf[5].ops, liefert einen Kernel-Zeiger und die KASLR-Basis.obj->name (Offset 224) auf
pipe_buf[5].flags trifft und PIPE_BUF_FLAG_CAN_MERGE setzt (name = 16 = 0x10)./etc/passwd, Root wird passwortlos.Offsets werden über pahole verifiziert und sind spezifisch für das Layout von 6.18 bis
7.0. Überschreiben Sie die GEM_* / PIPEBUF_*-Definitionen für andere Kernel.
poc.c - der Exploit. Statisch bauen mit -lpthread.run_exploit.sh - baut den PoC und ein minimales Initramfs, startet es in QEMU.qemu-system-x86_64, gcc, busybox (statisch), fakeroot, cpio, gzip.
KVM (/dev/kvm) wird empfohlen; die Race-Condition ist damit wesentlich zuverlässiger.
Der Exploit benötigt ein verwundbares Ziel, das mit bestimmten Optionen gebaut wurde:
CONFIG_KASAN muss AUS sein. KASAN quarantänisiert freigegebene Slabs und blockiert die
Pipe-Spray-Rückgewinnung, daher funktioniert der Exploit nicht gegen einen KASAN-Build.virtio_gpu (im Demo verwendet) oder nouveau.CONFIG_DRM=y, CONFIG_DRM_VIRTIO_GPU=y, CONFIG_DEVTMPFS=y,
CONFIG_BLK_DEV_INITRD=y.Schritte:
CONFIG_KASAN nicht gesetzt ist.make -j"$(nproc)" bzImage, erzeugt arch/x86/boot/bzImage.Das Demo bootet mit nokaslr, sodass der geleakte Zeiger deterministisch ist. Der Leak
umgeht KASLR von selbst, also funktioniert auch KASLR-an, die Adresse variiert nur pro Boot.
Schauen Sie in drivers/gpu/drm/drm_gem.c, Funktion drm_gem_change_handle_ioctl().
Schnellprüfung:
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 bedeutet VERWUNDBAR (keine Refcount-Behandlung).Nach Struktur:
file_priv->prime.lock, ein einzelnes
idr_alloc(&file_priv->object_idr, obj, ...), dann idr_remove() auf dem alten
Handle. Kein drm_gem_object_handle_get.idr_alloc dann idr_replace(NULL), um das
alte Handle zu lösen), wobei das echte Objekt erst ausgetauscht wird, wenn die
Prime-Operationen erfolgreich sind../run_exploit.sh /path/to/bzImage
Es baut poc.c, wickelt es in ein Initramfs ein und startet QEMU mit
-device virtio-gpu-pci und nokaslr. Der PoC läuft als uid 1000 (unprivilegiert),
dann gibt das Skript /etc/passwd vorher und nachher aus und öffnet eine Shell.
Verlassen Sie die VM mit poweroff -f oder Strg-A X.
[!] Race gewonnen (Iter 977): handle=132049
[!] KASLR: pipe_buf_ops = 0xffffffff82428400
[!] EXPLOIT ERFOLGREICH
[!] FLINK: 16 = 0x10
[*] /etc/passwd:
root::0:0:pwned:/root:/bin/sh
[!] LPE BESTÄTIGT
[!] Root-Konto ist jetzt passwortlos
Eine schreibgeschützte (chmod 444) /etc/passwd, die root gehört, wird von einem
unprivilegierten Prozess überschrieben. Die root:-Zeile verliert ihr Passwortfeld.
Bauen Sie gegen einen gepatchten Baum (6.18.32, 7.0.9, 7.1-rc3 oder später, oder einen Baum, der die obige gepatchte Prüfung besteht) neu und führen Sie erneut aus:
./run_exploit.sh /path/to/patched-bzImage
Gegen den gepatchten Kernel sollte der Exploit niemals "EXPLOIT ERFOLGREICH" erreichen: Die Race-Condition gibt das Objekt nicht mehr unter dem neuen Handle frei, sodass der Leak niemals einen gültigen Kernel-Zeiger zurückgibt.
Veröffentlicht für Forschungs- und Abwehrzwecke, nachdem upstream-Korrekturen ausgeliefert wurden. Bereitgestellt wie besehen. Führen Sie nur gegen VMs aus, die Sie kontrollieren.