# DF-2443 — dm_dev_remove lifecycle: deadlock-forces-UAF

## Summary
`disable_dev()` (`sys/dev/disk/dm/dm_dev.c:65-77`) does
`while (dmv->ref_cnt != 0) cv_wait(...)`, so `dm_dev_remove()` (which calls it)
**cannot be called while the caller holds a busy reference** — it would
cv_wait forever for the caller's OWN reference → deadlock. This **forces**
`dm_dev_remove_ioctl()` (`sys/dev/disk/dm/dm_ioctl.c:330-362`) to
`dm_dev_unbusy(dmv)` (drop its busy ref) at line 356 BEFORE calling
`dm_dev_remove(dmv)` at line 362. The drop-ref-then-remove window between
lines 356 and 362 is a **use-after-free**: the caller dereferences `dmv` while
holding no reference, so a concurrent remover that stacked its lookup on top
can free the same `dm_dev_t` out from under it. Under INVARIANTS this
deterministically panics with `Bad link elm … prev->next != elm` inside
`dm_dev_remove` → list corruption. Sibling of DF-2447 (same root-cause
lifecycle bug; this finding frames the deadlock-forces-UAF design angle).

## Privilege gate (valid hard blocker for uid0)
The whole dm ioctl surface is **root/operator-only**:
- `dm` is a KLD module; `kldload dm` is root-only.
- `/dev/mapper/control` is `0640 root:operator` (`device-mapper.c:181`).
- `maxx` (uid 1001, not in `operator`/`wheel`) gets `Permission denied`.
Root→kernel is game-over by definition, so this is a **root/operator → kernel
memory-corruption / local-DoS / hardening gap**, not an unprivileged→root
escalation. Impact ceiling: deterministic kernel panic (DoS) by any
root/operator user, and — with slab grooming on a noinv kernel — potential heap
corruption toward code execution.

## Build
```
cc -O2 -o dm_deadlock_uaf dm_deadlock_uaf.c -lprop
```

## Run (as root)
```
kldload dm
./dm_deadlock_uaf 8 2000          # <racers> <iterations>
```

## Expected behavior
- **Bug present (unpatched dm):** kernel panic
  `Bad link elm <addr> prev->next != elm` with backtrace
  `dm_dev_remove_ioctl` → `dm_dev_remove` → list corruption, within ~500-1000
  iterations (~4000-8000 concurrent races). Guest wedges in DDB.
- **Bug fixed (patched dm.ko via `fix.diff`):** PoC completes all 2000
  iterations cleanly (`exhausted 2000 iterations without a panic`), guest stays
  up, no panic in boot.log.

## Fix
`fix.diff` adds `dm_dev_destroy_by_key(name, uuid, minor)` — an atomic
lookup + is_open check + removal + disable_dev drain + destroy under
`dm_dev_mutex` that never takes a caller-held long-lived busy reference. This
closes BOTH the deadlock (the helper holds no ref) AND the UAF (no
drop-ref-then-deref window). Same root-cause fix validated for sibling DF-2447.
`git apply --check` passes; built as `dm.ko`, installed, and confirmed to close
the bug.

## Files
- `dm_deadlock_uaf.c` — trigger PoC (concurrent remove ioctls via libprop + pipe barrier)
- `build.sh` / `run.sh` — exact build/run commands
- `VERDICT.md` — full analysis + fix validation
- `fix.diff` — git-apply-able fix
- `fix_build.log` / `fix_run.log` — patched-module build/run logs
- `run.log` / `panic.txt` — baseline reproduction + panic signature
- `manifest.json` — artifact catalog
