
Exploit pour CVE-2026-46215, une élévation de privilèges locale par use-after-free dans le DRM GEM du noyau Linux. Utilise des courses, du slab spraying et une réécriture de fichier de type Dirty Pipe pour transformer un utilisateur non privilégié du nœud de rendu en root sans mot de passe.
change_handle GEM DRM (élévation de privilèges locale non privilégiée)Élévation de privilèges locale via une utilisation après libération dans l'ioctl noyau DRM DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, numéro d'ioctl 0xD2). Accessible par tout utilisateur ayant accès à un nœud de rendu (/dev/dri/renderD*, accordé à la session active par systemd-logind sur toutes les distributions de bureau majeures). La chaîne dans ce dépôt transforme l'UAF en root sans mot de passe à partir d'un utilisateur non privilégié.
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), ajouté pour le travail CRIU d'AMD5e28b7b94408). L'ioctl est également désactivé en amont dans 7.1 à cause de ce problème et de conditions de course apparentées.Ce bug a été signalé pour la première fois par Puttimet Thammasaeng, qui détient le crédit amont Reported-by sur le correctif. Je l'ai découvert et signalé indépendamment à [email protected] le 2026-04-12. Mon rapport a été reconnu et transmis aux mainteneurs, mais le rapport antérieur est celui crédité en amont. Ce dépôt contient ma propre analyse et mon exploit.
Writeup : https://cyberstan.co.uk
Le correctif se trouve dans les noyaux stables publiés (6.18.32 et 7.0.9 et ultérieurs). Ce dépôt a été publié après que les correctifs ont été largement disponibles.
drm_gem_change_handle_ioctl() déplace un objet GEM d'un handle à un autre mais n'ajuste jamais obj->handle_count. Il saute également drm_vma_node_allow/revoke et les callbacks d'ouverture/fermeture du pilote. Comme handle_count reste à 1, un GEM_CLOSE concurrent sur l'ancien handle le fait passer à 0 et libère l'objet alors que le nouveau handle le référence toujours dans l'IDR. Ce handle pendant est l'utilisation après libération, déréférencée plus tard dans drm_gem_object_release_handle().
GEM_CHANGE_HANDLE contre GEM_CLOSE pour obtenir un handle pendant.pipe_buffer pulvérisé (feng shui msg_msg pour conditionner kmalloc-512, puis tuyaux remplis par splice).pipe_buf_ops via un ioctl d'info du pilote : obj->size (décalage 216) chevauche pipe_buf[5].ops, donnant un pointeur noyau et la base KASLR.obj->name (décalage 224) atterrisse sur pipe_buf[5].flags et mette PIPE_BUF_FLAG_CAN_MERGE (name = 16 = 0x10)./etc/passwd, root devient sans mot de passe.Les décalages sont vérifiés via pahole et sont spécifiques à la disposition entre 6.18 et 7.0. Surcharger les définitions GEM_* / PIPEBUF_* pour d'autres noyaux.
poc.c – l'exploit. Compiler statiquement avec -lpthread.run_exploit.sh – construit le PoC et un initramfs minimal, le lance dans QEMU.qemu-system-x86_64, gcc, busybox (statique), fakeroot, cpio, gzip. KVM (/dev/kvm) est recommandé ; la condition de course est bien plus fiable avec.
L'exploit nécessite une cible vulnérable construite avec des options spécifiques :
CONFIG_KASAN doit être DÉSACTIVÉ. KASAN met en quarantaine les slabs libérés et bloque la récupération par pulvérisation de tuyaux, donc l'exploit ne fonctionnera pas contre une construction KASAN.virtio_gpu (utilisé dans la démo) ou nouveau.CONFIG_DRM=y, CONFIG_DRM_VIRTIO_GPU=y, CONFIG_DEVTMPFS=y, CONFIG_BLK_DEV_INITRD=y.Étapes :
CONFIG_KASAN n'est pas défini.make -j"$(nproc)" bzImage, produisant arch/x86/boot/bzImage.La démo démarre avec nokaslr pour que le pointeur fuité soit déterministe. La fuite vainc KASLR par elle-même, donc KASLR activé fonctionne aussi, l'adresse varie simplement à chaque démarrage.
Regarder dans drivers/gpu/drm/drm_gem.c, fonction drm_gem_change_handle_ioctl().
Vérification rapide :
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 signifie VULNÉRABLE (pas de gestion de refcount).Par structure :
file_priv->prime.lock, un seul idr_alloc(&file_priv->object_idr, obj, ...), puis idr_remove() sur l'ancien handle. Pas de drm_gem_object_handle_get.idr_alloc puis idr_replace(NULL) pour détacher l'ancien handle), l'objet réel n'étant échangé qu'une fois les opérations prime réussies../run_exploit.sh /chemin/vers/bzImage
Il construit poc.c, l'emballe dans un initramfs, et démarre QEMU avec -device virtio-gpu-pci et nokaslr. Le PoC s'exécute en tant qu'uid 1000 (non privilégié), puis le script affiche /etc/passwd avant et après et vous laisse un shell. Quittez la VM avec poweroff -f ou 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 fichier /etc/passwd en lecture seule (chmod 444) appartenant à root est écrasé par un processus non privilégié. La ligne root: perd son champ mot de passe.
Reconstruire contre un arbre corrigé (6.18.32, 7.0.9, 7.1-rc3 ou ultérieur, ou tout arbre passant la vérification de correctif ci-dessus) et réexécuter :
./run_exploit.sh /chemin/vers/bzImage-corrigé
Contre le noyau corrigé, l'exploit ne devrait jamais atteindre « EXPLOIT SUCCESSFUL » : la course ne libère plus l'objet sous le nouveau handle, donc la fuite ne renvoie jamais un pointeur noyau valide.
Publié à des fins de recherche et de défense après le déploiement des correctifs amont. Fourni en l'état. Exécutez-le uniquement sur des VM que vous contrôlez.