# 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**:

```c
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_unr` before both inserts → losing insert returns `-EINVAL`
  and `free_unr()`s the **live** key; object teardown `free_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_item` is 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 failing `EINVAL`
  ("failed to add to map hash" on console) and/or unr corruption panic.
  Also sweeps `drm_mode_create_dumb` size-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 with `dev->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: `race` counters; any `EINVAL != 0` row
  reproduces the collision; `sim_interleave` prints 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.
