# DF-2447 — dm_dev_remove_ioctl / dm_dev_resume_ioctl use-after-free

Reproduction + fix for the dm device-mapper UAF race in
`sys/dev/disk/dm/dm_ioctl.c`.

## The bug (confirmed)

`dm_dev_remove_ioctl()` and `dm_dev_resume_ioctl()` drop the device's busy
reference (`dm_dev_unbusy`) and then keep dereferencing the `dmv` pointer
(`dm_dev_remove(dmv)` / `dm_table_destroy(&dmv->table_head,…)`). Two
concurrent `remove` ioctls on the same device race through this window: the
first remover frees `dmv` while the second is still between its `unbusy` and
its `dm_dev_remove` → use-after-free / double-remove → kernel panic.

## Reproduce (as root)

```sh
cc -O2 -o dm_race_uaf dm_race_uaf.c -lprop      # build.sh
kldload dm                                       # root: load the dm module
./dm_race_uaf 8 2000                            # run.sh ; 8 racers x 2000 iters
```

**Expected on the unpatched (#0) kernel:** a near-instant kernel panic
```
panic: Bad link elm 0xfffff80117ed3a40 prev->next != elm
... dm_dev_remove() at dm_dev_remove+0x25
... dm_dev_remove_ioctl() at dm_dev_remove_ioctl+0xb7
... dmioctl() ...
db>
```
Guest wedges in DDB. Serial console (`dfbsd-qemu/boot.log`) captures it.

**Expected on the patched dm.ko:** the racer completes all iterations cleanly
("exhausted N iterations without a panic"), guest stays up, `boot.log` empty.

## Privilege note (why this is panic/DoS, not uid0)

`/dev/mapper/control` is `0640 root:operator` and the `dm` module must be
`kldload`-ed by root. The whole dm ioctl surface is therefore root/operator-only.
`maxx` (uid 1001, not in `operator`/`wheel`) cannot open the control dev and
cannot `kldload`. 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. See `VERDICT.md` for the full privilege analysis.

## Fix

`fix.diff` — atomic `dm_dev_destroy_by_key()` helper (lookup+is_open+remove+
drain+destroy under `dm_dev_mutex`, so two removers are serialized and the
drop-ref-then-deref window is eliminated) + move `dm_dev_unbusy` after
`dm_table_destroy` in `dm_dev_resume_ioctl`. `git apply --check` clean; built
and validated as `dm.ko`.
