DRM_UNLOCKED dumb-buffer MAP_DUMB races the unsynchronized legacy GEM offset-hash publication: live-key free_unr double-free, same-node dual-bucket hlist link, and wrong-object resolution in the mmap path (cross-object / UAF-class mapping ceiling)
Summary
MAP_DUMB is dispatched lockless into drm_gem_create_mmap_offset_size whose publication has zero sync - two barrier-synced calls free the live unr key, dual-link one hash node, and the corrupted hash (the mmap resolver under DRM_LOCK) maps the WRONG object or a freed one. 1:1 kernel transliteration proves both interleavings deterministically (live-key free + double free + aliasing; dual-bucket + wrong-object resolution); PoC is hardware-ready but the guest has no DRM device. Serialize-with-object_name_lock fix (drm_gem.c).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5186 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.1 KB | β raw | |
| VERDICT.md | β | 5.7 KB | β raw | |
| mapdumb_race.c | β | 8.0 KB | view raw | |
| sim_interleave.c | β | 7.7 KB | view raw | |
| build.sh | β | 158 B | view raw | |
| run.sh | β | 121 B | view raw | |
| build.log | β | 150 B | view raw | |
| run.log | β | 65 B | view raw | |
| sim_output.txt | β | 1.2 KB | view raw | |
| env.txt | β | 292 B | view raw | |
| fix.diff | β | 2.0 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-5186 β unsynchronized GEM offset-hash publication raced via DRM_UNLOCKED MAP_DUMB
What
drm_mode_mmap_dumb_ioctl (sys/dev/drm/drm_dumb_buffers.c:115-130) is dispatched
DRM_UNLOCKED|DRM_RENDER_ALLOW (sys/dev/drm/drm_ioctl.c:676) β the handler runs
with no lock held. The default path drm_gem_dumb_map_offset β
drm_gem_create_mmap_offset and the i915 override
(.dumb_map_offset = i915_gem_mmap_gtt, i915_drv.c:3301 β i915_gem.c:2600-2602)
both reach drm_gem_create_mmap_offset_size (sys/dev/drm/drm_gem.c:567-606),
which publishes into the legacy per-device offset_hash with zero
synchronization:
if (obj->on_map) return 0; /* 573 */
obj->map_list.key = alloc_unr(...); /* 576 */
ret = drm_ht_insert_item(...); /* 577 β lockless hlist insert */
if (ret != 0) { free_unr(...); return ret; }
obj->on_map = true; /* 583 */
Two concurrent MAP_DUMB on the same handle corrupt it (see sim_interleave.c):
- A both
alloc_unrbefore both inserts β losing insert returns-EINVALandfree_unr()s the live key; object teardownfree_unr()s it again (drm_gem.c:541-542) β unr index aliasing across two live objects; - B key write lands after the winner's insert β the same
drm_hash_itemis linked into two hash buckets (cross-bucket hlist merge);drm_ht_find_item()then resolves the wrong item.
The corrupted hash is the authoritative mmap resolver:
drm_gem_object_from_offset() (drm_gem.c:1063-1080) β drm_gem_mmap_single()
(drm_gem.c:1082-1109) β container_of(hash, struct drm_gem_object, map_list).
Wrong/dangling entries hand the device pager the wrong (or freed) GEM object β
cross-object mapping (other process's GPU buffer) or a UAF-class mapping.
Files
mapdumb_race.cβ guest PoC: churn create/map/destroy with a pair of barrier-synchronized MAP_DUMB racers; smoking gun = MAP_DUMB failingEINVAL("failed to add to map hash" on console) and/or unr corruption panic. Also sweepsdrm_mode_create_dumbsize-math rows (all in-file checks verified correct in source).sim_interleave.cβ deterministic 1:1 transliteration of the kernel critical sections; proves both corruption interleavings.fix.diffβ serialize publish/teardown withdev->object_name_lock.
Build / run (on any DragonFly box, needs no root if /dev/dri/card0 is 0660 video)
cc -O2 -Wall -pthread -o mapdumb_race mapdumb_race.c ./mapdumb_race 20000 # iterations cc -O2 -Wall -o sim_interleave sim_interleave.c && ./sim_interleave
Expected
- On this audit guest (QEMU std-VGA, no DRM driver): exit code 2,
[-] no /dev/dri node availableβ see run.log / env.txt. - On i915/radeon/amdgpu hardware:
racecounters; anyEINVAL != 0row reproduces the collision;sim_interleaveprints the deterministic corruption proof (see sim_output.txt).
Honest status
inconclusive on this guest β the bug is code-certain (full trace in
VERDICT.md) but the guest has no DRM device, so the kernel path cannot be
exercised here. The PoC is ready for any hardware-bearing DragonFly machine.
DF-5186 β VERDICT
status: inconclusive (target device absent on this guest) Β· reproduced: 0 Β· code-trace confidence: high (the corruption follows from the source with no assumptions beyond two concurrent ioctls on one fd)
Guest
DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026, X86_64_GENERIC,
cc 8.3. QEMU std-VGA: no /dev/dri, no drm kld, zero drm lines in dmesg
(env.txt). sys/dev/drm/drm/ contains only a Makefile β there is no
virtual/software DRM driver in this tree (vgem absent), and QEMU cannot
present Intel/AMD hardware. => the kernel race path cannot be entered on this
guest by any means. Not a false positive: the path is live on every machine
with i915/amdgpu/radeon KMS (see reachability below).
Code trace (why the bug is real)
- Entry, no lock:
DRM_IOCTL_DEF(DRM_IOCTL_MODE_MAP_DUMB, drm_mode_mmap_dumb_ioctl, DRM_UNLOCKED|DRM_RENDER_ALLOW)(sys/dev/drm/drm_ioctl.c:676); DRM_UNLOCKED handlers are invoked directly,func(dev, kdata, file_priv)(drm_ioctl.c:777) β no per-fd serialization, no DRM_LOCK, no DRM_AUTH required (drm_ioctl_permit, drm_ioctl.c:535-557: only ROOT_ONLY/AUTH/MASTER/RENDER flags are checked; MAP_DUMB needs none beyond a non-render client being allowed). - Handle β object:
drm_gem_object_lookuptakes a ref underfilp->table_lock(drm_gem.c:621-636) β safe, but the ref is the only thing held when the driver callback runs. - Driver override (i915, the default install):
.dumb_map_offset = i915_gem_mmap_gtt(i915_drv.c:3301) βi915_gem_object_create_mmap_offsetβdrm_gem_create_mmap_offset(&obj->base)(i915_gem.c:2600-2602), called with nothing held. radeon/amdgpu go through their ttm mmap-offset paths; the defaultdrm_gem_dumb_map_offset(drm_gem.c:381-406) reaches the same function for any GEM driver that does not override. - Unsynchronized publication: drm_gem.c:573-583 β
on_maptest-and-set,map_list.key = alloc_unr(),drm_ht_insert_item()(drm_hashtab.c:109-133, plain hlist insert, no internal lock),free_unr()on the -EINVAL path. Teardown mirrordrm_gem_free_mmap_offset(drm_gem.c:529-546) is equally lockless arounddrm_ht_remove_item/free_unr. - Consumer under a different lock:
drm_gem_mmap_singletakesDRM_LOCK(dev)for the hash lookup + object reference (drm_gem.c:1089-1098) β which does not serialize against the unlocked publishers in (4). - Corruption consequences β proven deterministically by
sim_interleave.c(1:1 transliteration; run on the guest, sim_output.txt): - interleaving A: loserfree_unr()s the live key; teardown frees it again βalloc_unrhands the same index to a second live object while the first object's hash entry still claims it; - interleaving B: the samedrm_hash_itemis linked into two buckets;drm_ht_find_item(1)returns objA's node although key 1 belongs to objB β wrong-object resolution. In the kernel,container_of(hash, struct drm_gem_object, map_list)(drm_gem.c:1078) then maps the wrong object's pages into the caller (cross-object mapping = another process's GPU buffer readable/writable), and an entry left dangling by the delete race maps a freed object (UAF-class).
Exploit chain (hardware-bearing machine, unprivileged video-group user)
open /dev/dri/card0 (root:video 0660, drm_drv.c:1178-1179, drmP.h:108-110) β
DRM_IOCTL_MODE_CREATE_DUMB (no auth) β pair of threads issuing barrier-
synchronized DRM_IOCTL_MODE_MAP_DUMB on the fresh handle β repeat with
create/destroy churn to widen windows and recycle idxunr entries β corrupted
offset_hash β craft mmap(card0_fd, DRM_GEM_MAPPING_KEY|DRM_GEM_MAPPING_OFF(idx))
offsets that resolve aliased/dangling entries β read/write another process's
GEM backing memory (info leak / cross-process corruption) or a freed object's
pages (UAF). Detectable symptom during racing: MAP_DUMB EINVAL +
[drm:drm_gem_create_mmap_offset_size] *ERROR* failed to add to map hash.
Why not reproduced here
No DRM device exists on the QEMU guest (std VGA; no in-tree driver binds it; no vgem). The PoC binary builds clean on the guest (build.log) and runs (run.log: exit 2, "no /dev/dri node available"), i.e. the trigger is correct but the kernel path is unreachable on this machine. The deterministic sim proves the interleaving logic that the kernel executes.
Fix validation
fix_status: not_applicable β the baseline itself cannot be exercised on this
guest, so a patched-kernel A/B comparison is impossible here. fix.diff
(serialize both critical sections with dev->object_name_lock, which is
LK_CANRECURSE and sleepable) is line-accurate against
sys/dev/drm/drm_gem.c:529-588 and should be validated on hardware: with the
patch, concurrent MAP_DUMB never produces EINVAL from the map-hash insert and
the unr pool never double-frees.
kernel_refs
- sys/dev/drm/drm_dumb_buffers.c:115-130 (audited-file entry point)
- sys/dev/drm/drm_ioctl.c:676, 777 (DRM_UNLOCKED dispatch, no serialization)
- sys/dev/drm/drm_gem.c:567-606 (unsynchronized publication)
- sys/dev/drm/drm_gem.c:529-546 (unsynchronized teardown)
- sys/dev/drm/drm_gem.c:1063-1109 (hash is the mmap resolver, under DRM_LOCK)
- sys/dev/drm/drm_hashtab.c:109-133 (lockless insert)
- sys/dev/drm/i915/i915_drv.c:3301 + i915_gem.c:2600-2648 (live override path)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableBaseline cannot be exercised on this guest (no DRM device), so a patched-kernel A/B run is impossible here; fix.diff is line-accurate against drm_gem.c:529-588 and must be validated on DRM hardware (with the patch, concurrent MAP_DUMB can never lose the insert race, so no live-key free_unr / dual-link).
fix.diff
Confirmed kernel references
Detail
Exploit chain
video-group user opens /dev/dri/card0 (0660 root:video, drm_drv.c:1178) -> CREATE_DUMB (no auth) -> two threads barrier-synchronized MAP_DUMB on the fresh handle, with create/destroy churn recycling idxunr -> corrupted offset_hash (double free_unr / dual-linked node) -> mmap(card0_fd, KEY|OFF(idx)) resolving an aliased or dangling entry -> wrong-or-freed GEM object mapped: cross-process GPU-buffer disclosure or UAF-class RW mapping. Ceiling not demonstrated: needs DRM hardware this guest lacks.
Evidence (decisive lines)
run.log (PoC exit 2 - no /dev/dri on guest), sim_output.txt (deterministic interleaving A: live-key free + double free_unr + index aliasing; interleaving B: same node in two buckets + drm_ht_find_item(1) returns objA -> wrong-object resolution), env.txt (guest uname/kldstat/no-dri), build.log, VERDICT.md full path trace
PoC changes
n/a (seed written from scratch in this run; self-contained uapi defs because the guest ships no drm headers; pair-barrier churn design so the race hits the first-MAP_DUMB window of every fresh object)
Verified recommended fix
Serialize drm_gem_create_mmap_offset_size/drm_gem_free_mmap_offset legacy offset-hash publication with dev->object_name_lock (sleepable, LK_CANRECURSE) - see fix.diff
Verdict
Code-certain race, target-device-absent on this guest. drm_mode_mmap_dumb_ioctl is dispatched DRM_UNLOCKED|DRM_RENDER_ALLOW (drm_ioctl.c:676, invoked locklessly at drm_ioctl.c:777); the i915 override (.dumb_map_offset=i915_gem_mmap_gtt, i915_drv.c:3301 -> i915_gem.c:2600-2602) and the GEM default both reach drm_gem_create_mmap_offset_size (drm_gem.c:567-606), whose on_map/map_list.key/drm_ht_insert_item/idxunr publication has no synchronization, while the same hash is the authoritative mmap resolver under DRM_LOCK (drm_gem.c:1063-1109). Two barrier-synced MAP_DUMB calls on one fresh handle deterministically corrupt state (proved by the 1:1 kernel transliteration in sim_interleave.c: live-key free_unr + teardown double-free => index aliasing; same drm_hash_item linked into two buckets => drm_ht_find_item returns the wrong object => cross-object/UAF mapping via drm_gem_mmap_single). The QEMU audit guest has no DRM device (std-VGA, no /dev/dri, no drm kld, no vgem in-tree), so the kernel path cannot be entered: PoC builds clean (cc 8.3) and runs, exiting 2 with 'no /dev/dri node available' (run.log). Ready to run on any i915/amdgpu/radeon DragonFly box; smoking gun = MAP_DUMB EINVAL + '[drm:drm_gem_create_mmap_offset_size] ERROR failed to add to map hash'.
No comments yet.