change_handle 释放后使用(非特权LPE)通过DRM核心ioctl DRM_IOCTL_GEM_CHANGE_HANDLE(drm_gem_change_handle_ioctl,ioctl编号0xD2)中的释放后使用实现本地权限提升。任何有权访问渲染节点(/dev/dri/renderD*,主流桌面发行版中由systemd-logind授予活动会话)的用户均可触发。本仓库中的利用链从非特权用户出发,实现无密码root权限提升。
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),为AMD的CRIU工作添加5e28b7b94408)。该ioctl也将在7.1上游中被禁用,原因是此漏洞及相关竞态问题。该漏洞首先由 Puttimet Thammasaeng 报告,上游修复的 Reported-by 署名归属于他。我独立发现并於2026-04-12向[email protected]报告。我的报告得到了确认并转发给了维护者,但上游署名的是更早的报告。本仓库包含我自己的分析和漏洞利用代码。
Writeup:https://cyberstan.co.uk
修复已包含在已发布的内核稳定版(6.18.32及7.0.9之后)。本仓库于修复广泛可用后发布。
drm_gem_change_handle_ioctl() 将GEM对象从一个句柄移动到另一个句柄,但从未调整 obj->handle_count。它还跳过了 drm_vma_node_allow/revoke 以及驱动程序的打开/关闭回调。由于 handle_count 保持为1,对旧句柄的并发 GEM_CLOSE 会将其降至0并释放对象,而新句柄仍在IDR中引用该对象。这个悬垂句柄就是释放后使用的根源,随后在 drm_gem_object_release_handle() 中被解引用。
GEM_CHANGE_HANDLE 和 GEM_CLOSE 进行竞态竞争,获得一个悬垂句柄。pipe_buffer 数组(通过 msg_msg 风水和 splice 填充管道)重新回收已释放对象的slab槽位(kmalloc-512)。pipe_buf_ops:obj->size(偏移216)与 pipe_buf[5].ops 重叠,提供内核指针及KASLR基址。obj->name(偏移224)落在 pipe_buf[5].flags 上并设置 PIPE_BUF_FLAG_CAN_MERGE(name = 16 = 0x10)。/etc/passwd,root变为无密码。偏移量通过 pahole 验证,特定于6.18至7.0的布局。对于其他内核版本,请覆盖 GEM_* / PIPEBUF_* 定义。
poc.c - 漏洞利用代码。静态编译需加 -lpthread。run_exploit.sh - 构建PoC和极简initramfs,并在QEMU中启动。qemu-system-x86_64、gcc、busybox(静态)、fakeroot、cpio、gzip。推荐启用KVM(/dev/kvm);竞态竞争在有KVM时可靠得多。
漏洞利用需要一个具有特定配置的脆弱目标内核:
CONFIG_KASAN 必须关闭。KASAN会隔离已释放的slab并阻止管道喷洒回收,因此利用无法在KASAN构建上工作。virtio_gpu(演示所用)或 nouveau。CONFIG_DRM=y、CONFIG_DRM_VIRTIO_GPU=y、CONFIG_DEVTMPFS=y、CONFIG_BLK_DEV_INITRD=y。步骤:
CONFIG_KASAN。make -j"$(nproc)" bzImage,生成 arch/x86/boot/bzImage。演示使用 nokaslr 启动,因此泄漏的指针是确定的。漏洞利用本身可以击败KASLR,因此KASLR开启也可工作,只是地址每次启动不同。
查看 drivers/gpu/drm/drm_gem.c 中的 drm_gem_change_handle_ioctl() 函数。
快速检查:
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 表示脆弱的(无引用计数处理)。按结构判断:
file_priv->prime.lock,单个 idr_alloc(&file_priv->object_idr, obj, ...),然后对旧句柄执行 idr_remove()。无 drm_gem_object_handle_get。idr_alloc 然后 idr_replace(NULL) 分离旧句柄),只有在prime操作成功后才会交换真实对象。./run_exploit.sh /path/to/bzImage
它会构建 poc.c,打包进initramfs,并在QEMU中使用 -device virtio-gpu-pci 和 nokaslr 启动。PoC以uid 1000(非特权)运行,然后脚本会打印 /etc/passwd 前后内容,并将你放入一个shell。使用 poweroff -f 或 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
一个只读(chmod 444)且属主为root的 /etc/passwd 被非特权进程覆盖。root: 行丢失了密码字段。
针对已修补的内核树(6.18.32、7.0.9、7.1-rc3或更高版本,或任何通过上述补丁检查的树)重新构建并重新运行:
./run_exploit.sh /path/to/patched-bzImage
在已修补的内核上,漏洞利用不应达到"EXPLOIT SUCCESSFUL":竞争不再导致新句柄下的对象被释放,因此泄漏永远无法返回有效内核指针。
在上游修复发布后,出于研究和防御目的发布。按原样提供。仅在您控制的虚拟机中运行。