Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-46215-POC — Exploit for CVE-2026-46215, a Linux kernel DRM GEM use-after-free local privilege escalation. Uses racing, slab spraying, and Dirty Pipe-style file overwrite to turn an unprivileged render-node user into passwordless root. | Kitploit
Tools/GitHubGitHub/0xcyberstan/cve-2026-46215-poc
Privilege EscalationMemory ForensicsVulnerability AnalysisExploitationCTFLearning & EducationBinary Exploitation
GitHub0xcyberstan/cve-2026-46215-poc

CVE-2026-46215-POC

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Exploit for CVE-2026-46215, a Linux kernel DRM GEM use-after-free local privilege escalation. Uses racing, slab spraying, and Dirty Pipe-style file overwrite to turn an unprivileged render-node user into passwordless root.

View Repository
11232 months agoNot yet reviewed

CVE-2026-46215: DRM GEM change_handle Use-After-Free (unprivileged LPE)

Local privilege escalation via a use-after-free in the DRM core ioctl DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, ioctl nr 0xD2). Reachable by any user with access to a render node (/dev/dri/renderD*, granted to the active session by systemd-logind on all major desktop distributions). The chain in this repo takes the UAF to passwordless root from an unprivileged user.

  • CVE: CVE-2026-46215 (HIGH, 7.8, AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
  • Introduced: v6.18-rc1, commit 53096728b891 ("drm: Add DRM prime interface to reassign GEM handle", David Francis / AMD), added for AMD's CRIU work
  • Fixed in: 6.18.32, 7.0.9, 7.1-rc3 (upstream 5e28b7b94408). The ioctl is also being disabled upstream in 7.1 because of this and related races.
  • Affected: v6.18-rc1 through the fixed releases above

Attribution

This bug was first reported by Puttimet Thammasaeng, who holds the upstream Reported-by credit on the fix. I found and reported it independently to [email protected] on 2026-04-12. My report was acknowledged and forwarded to the maintainers, but the earlier report is the one credited upstream. This repo contains my own analysis and exploit.

Writeup: https://cyberstan.co.uk

Disclosure status

The fix is in released stable kernels (6.18.32 and 7.0.9 onwards). This repo was published after fixes became broadly available.

The bug

drm_gem_change_handle_ioctl() moves a GEM object from one handle to another but never adjusts obj->handle_count. It also skips drm_vma_node_allow/revoke and the driver open/close callbacks. Because handle_count stays at 1, a concurrent GEM_CLOSE on the old handle drops it to 0 and frees the object while the new handle still references it in the IDR. That dangling handle is the use-after-free, later dereferenced in drm_gem_object_release_handle().

Exploit chain

  1. Race GEM_CHANGE_HANDLE against GEM_CLOSE to get a dangling handle.
  2. Reclaim the freed object's slab slot with a sprayed pipe_buffer array (msg_msg feng shui to condition kmalloc-512, then splice-filled pipes).
  3. Leak pipe_buf_ops through a driver info ioctl: obj->size (offset 216) overlaps pipe_buf[5].ops, giving a kernel pointer and the KASLR base.
  4. FLINK the dangling handle so obj->name (offset 224) lands on pipe_buf[5].flags and sets PIPE_BUF_FLAG_CAN_MERGE (name = 16 = 0x10).
  5. Write to the pipes to merge into the page cache and overwrite a read-only file (DirtyPipe-style). Target is /etc/passwd, root becomes passwordless.

Offsets are verified via pahole and are specific to the layout across 6.18 to 7.0. Override the GEM_* / PIPEBUF_* defines for other kernels.

Files

  • poc.c - the exploit. Build static with -lpthread.
  • run_exploit.sh - builds the PoC and a minimal initramfs, boots it in QEMU.

Host prerequisites

qemu-system-x86_64, gcc, busybox (static), fakeroot, cpio, gzip. KVM (/dev/kvm) is recommended; the race is far more reliable with it.

Building a test kernel

The exploit needs a vulnerable target built with specific options:

  • CONFIG_KASAN must be OFF. KASAN quarantines freed slabs and blocks the pipe-spray reclaim, so the exploit will not work against a KASAN build.
  • A DRM driver that exposes the size/name fields at the expected offsets: virtio_gpu (used by the demo) or nouveau.
  • CONFIG_DRM=y, CONFIG_DRM_VIRTIO_GPU=y, CONFIG_DEVTMPFS=y, CONFIG_BLK_DEV_INITRD=y.

Steps:

  1. Check out a 6.18 to 7.0 source tree (or any tree that fails the patched check below).
  2. Set the config options above and make sure CONFIG_KASAN is not set.
  3. make -j"$(nproc)" bzImage, producing arch/x86/boot/bzImage.

The demo boots with nokaslr so the leaked pointer is deterministic. The leak defeats KASLR on its own, so KASLR-on also works, the address just varies per boot.

Checking whether a tree is vulnerable or patched

Look at drivers/gpu/drm/drm_gem.c, function drm_gem_change_handle_ioctl().

Quick check:

root@kitploit:~
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
    drivers/gpu/drm/drm_gem.c | grep -c handle_count
  • 0 means VULNERABLE (no refcount handling).
  • non-zero means PATCHED.

By structure:

  • Vulnerable: takes file_priv->prime.lock, a single idr_alloc(&file_priv->object_idr, obj, ...), then idr_remove() on the old handle. No drm_gem_object_handle_get.
  • Patched: two-phase insert (idr_alloc then idr_replace(NULL) to detach the old handle), with the real object only swapped in once the prime operations succeed.

Running the exploit

root@kitploit:~
./run_exploit.sh /path/to/bzImage

It builds poc.c, wraps it in an initramfs, and boots QEMU with -device virtio-gpu-pci and nokaslr. The PoC runs as uid 1000 (unprivileged), then the script prints /etc/passwd before and after and drops you to a shell. Leave the VM with poweroff -f or Ctrl-A X.

Expected output

root@kitploit:~
[!] 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

A read-only (chmod 444) /etc/passwd owned by root is overwritten by an unprivileged process. The root: line loses its password field.

Reliability

  • Around 99% per boot in testing (99/100 over 100 fresh boots, plus 53/53 in earlier runs). The PoC retries internally: on a bad leak it re-races for a fresh dangling object rather than re-spraying a dead slot, up to 200 rounds, each round a few tens of milliseconds. Most boots win on the first race; the worst observed used 11 of the 200 rounds.
  • Rare (about 1%) failure mode: a losing race interleaving corrupts kernel state and wedges the VM (silent hang, no verdict printed). This is inherent to a kernel race UAF. If a run does not print a verdict within roughly 30 seconds, power-cycle and run again.

Confirming the fix

Rebuild against a patched tree (6.18.32, 7.0.9, 7.1-rc3 or later, or any tree that passes the patched check above) and re-run:

root@kitploit:~
./run_exploit.sh /path/to/patched-bzImage

Against the patched kernel the exploit should never reach "EXPLOIT SUCCESSFUL": the race no longer frees the object out from under the new handle, so the leak never returns a valid kernel pointer.

Disclaimer

Published for research and defensive purposes after upstream fixes shipped. Provided as-is. Run only against VMs you control.

Download Tool