# 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)

1. **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).
2. **Handle → object**: `drm_gem_object_lookup` takes a ref under
   `filp->table_lock` (drm_gem.c:621-636) — safe, but the ref is the *only*
   thing held when the driver callback runs.
3. **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 *default* `drm_gem_dumb_map_offset` (drm_gem.c:381-406) reaches the
   same function for any GEM driver that does not override.
4. **Unsynchronized publication**: drm_gem.c:573-583 — `on_map` test-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 mirror `drm_gem_free_mmap_offset` (drm_gem.c:529-546) is equally
   lockless around `drm_ht_remove_item`/`free_unr`.
5. **Consumer under a *different* lock**: `drm_gem_mmap_single` takes
   `DRM_LOCK(dev)` for the hash lookup + object reference (drm_gem.c:1089-1098)
   — which does not serialize against the unlocked publishers in (4).
6. **Corruption consequences** — proven deterministically by `sim_interleave.c`
   (1:1 transliteration; run on the guest, sim_output.txt):
   - interleaving A: loser `free_unr()`s the live key; teardown frees it again
     → `alloc_unr` hands the same index to a second live object while the
     first object's hash entry still claims it;
   - interleaving B: the same `drm_hash_item` is 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.
