# DF-2206 — iounmap() walks global iomap_list without lock (High)

## Claim
`iounmap()` in `sys/dev/drm/linux_iomapping.c` performs an unlocked
`SLIST_FOREACH_MUTABLE` lookup over the process-global `iomap_list`, then
calls `pmap_unmapdev()` and only later (still without holding the lock during
the unmap) acquires `iomap_lock` purely for the `SLIST_REMOVE` / `kfree` tail.
Because the list is shared across every DRM device (not per-device), two
concurrent `iounmap()` callers can race and either dereference a `kfree()`-ed
saved-next pointer (UAF read), double-call `pmap_unmapdev` / `SLIST_REMOVE`
on the same VA (double free), or walk past end-of-list (NULL deref panic).

## Verification approach
**HW-gated / source-only.** `iounmap()` is exercised only by DRM drivers
(radeon, amdgpu, i915) during MMIO teardown, which on this audit guest has no
backing GPU.  Per the task brief, source-only confirmation is acceptable for
HW/module-gated findings.  The race is fully visible at the source level and
the structural fix is mechanical, so the verification is:

1. Source-trace the cited path line-by-line in `sys/dev/drm/linux_iomapping.c`
   and confirm the lock-acquire / release pattern matches the claim.
2. Confirm the list is process-global (single `SLIST_HEAD` at file scope).
3. Author `fix.diff` (hold `iomap_lock` for the entire op).
4. **Phase 8** — apply all 5 batched fixes and rebuild `drm.ko` with `-Werror`
   to confirm the patched source compiles cleanly.

## Source trace (confirmed)
* `sys/dev/drm/linux_iomapping.c:39` — `SLIST_HEAD(iomap_list_head, iomap) iomap_list` is a single global list, file-scope.
* `sys/dev/drm/linux_iomapping.c:37` — `iomap_lock` exists but is only taken selectively.
* `sys/dev/drm/linux_iomapping.c:68` — `SLIST_FOREACH_MUTABLE(imp, &iomap_list, ...)` runs **without** `iomap_lock` held.
* `sys/dev/drm/linux_iomapping.c:80` — `paddr_end` is computed from `imp` (still unlocked).
* `sys/dev/drm/linux_iomapping.c:90` — `pmap_change_attr(imp->paddr, ...)` (still unlocked).
* `sys/dev/drm/linux_iomapping.c:96` — `pmap_unmapdev((vm_offset_t)imp->pmap_addr, ...)` is called **before** the entry is removed (still unlocked).
* `sys/dev/drm/linux_iomapping.c:98-100` — `iomap_lock` is finally acquired here, only for `SLIST_REMOVE`.
* `sys/dev/drm/linux_iomapping.c:102` — `kfree(imp)` runs **after** the lock is released.

### Race window
Between the unlocked lookup (line 68) and the locked remove (line 99) the
entry pointed at by `imp` (and the `tmp_imp = SLIST_NEXT(imp)` saved by
`SLIST_FOREACH_MUTABLE`) can be removed and `kfree()`-ed by a concurrent
`iounmap()` on the same or any other DRM device, because the list is global.
Worst observable outcomes:
* UAF read of `tmp_imp` next iteration (kernel heap info leak / wild walk).
* Same-handle double `iounmap`: both pass `found`, both call
  `pmap_unmapdev` on the same VA (double `kmem_free` of `kernel_map`),
  second `SLIST_REMOVE` walks `SLIST_NEXT(NULL)` → panic.

## Files
* `VERDICT.md` — full narrative.
* `fix.diff` — git-apply-able fix: hold `iomap_lock` from function entry
  through `kfree`. `iomap_lock` is `LK_CANRECURSE` and the called helpers
  (`pmap_unmapdev`, `pmap_change_attr`, `kfree`) do not take it, so holding
  it across teardown is safe.
* `build_baseline.log` — full unpatched `drm.ko` build, rc=0, `-Werror`.
* `build_patched.log` — full patched `drm.ko` rebuild (only
  `linux_iomapping.o` recompiled), rc=0, `-Werror`.
* `env.txt` — guest environment.

## Reproduce
```
./build.sh    # applies fix.diff into in-guest /usr/src and rebuilds drm.ko
./run.sh      # re-runs (here: only a source-level repro is possible)
```
