Unprivileged root via a use-after-free in DRM GEM change_handle (CVE-2026-46215) – cyberstan
Technical analysis of CVE-2026-46215, a use-after-free in the Linux kernel DRM GEM core ioctl DRM_IOCTL_GEM_CHANGE_HANDLE. drm_gem_change_handle_ioctl() moves a GEM object between handles without adjusting handle_count, creating a window where two IDR entries exist while handle_count reads 1. A concurrent GEM_CLOSE on the old handle frees the object while the new handle still references it. The author describes an exploit chain: reclaim the kmalloc-512 slot with pipe_buffer spray, leak a kernel pointer via struct overlap to defeat KASLR, set PIPE_BUF_FLAG_CAN_MERGE via FLINK name assignment, and overwrite /etc/passwd through the page cache. Both ioctls are DRM_RENDER_ALLOW, reachable by any logged-in user on default desktop distributions. The fix uses a two-stage idr_replace to close the race window; the ioctl is being disabled in Linux 7.1. The bug was independently discovered by the author and first reported by Puttimet Thammasaeng, who holds the upstream credit.
Research notes
- Root causedrm_gem_change_handle_ioctl() open-codes handle manipulation without calling drm_gem_object_handle_get() or drm_gem_object_handle_put_unlocked(), leaving handle_count at 1 across the operation. Between idr_alloc and idr_remove, the object has two live IDR entries while handle_count reads 1, and a racing GEM_CLOSE can free the object.
- Exploit chainThe described exploit chain: (1) race GEM_CHANGE_HANDLE against GEM_CLOSE to free the object, (2) reclaim the kmalloc-512 slot with pipe_buffer spray, (3) read pipe_buf[5].ops via GEM size field to derive KASLR base, (4) FLINK the dangling handle to set PIPE_BUF_FLAG_CAN_MERGE via name=16=0x10, (5) write through spliced pipes to overwrite /etc/passwd page cache.
- MitigationThe shipped fix uses a two-stage idr_replace: allocate the new slot, replace with NULL, and only commit the real object after prime bookkeeping. If a concurrent close got in first, it backs out. The change_handle ioctl is being disabled outright in Linux 7.1.