# DF-2442 VERDICT — dm_dev_insert KKASSERT panic via concurrent create race

## Verdict: REPRODUCED (panic) — root→kernel DoS; fix VALIDATED (KKASSERT fixed)

## Summary

Racing two or more concurrent `create` ioctls for the SAME dm device name
triggers a KKASSERT panic in `dm_dev_insert` (sys/dev/disk/dm/dm_dev.c:195).

## Mechanism (trigger → primitive → effect)

1. **Attacker races N threads**, each calling `create` ioctl with the same name.
2. `dm_dev_create_ioctl` (dm_ioctl.c:207) first calls `dm_dev_lookup(name, uuid, -1)`.
   All threads pass this check (device doesn't exist yet). This is the TOCTOU
   window — the mutex is released between this lookup and the insert.
3. Each thread proceeds to `dm_dev_create` (dm_dev.c:225) → `dm_dev_insert`
   (dm_dev.c:173).
4. The first thread to acquire `dm_dev_mutex` in `dm_dev_insert` inserts
   successfully.
5. The second thread acquires the mutex and hits the bug at **dm_dev.c:190-196**:
   ```c
   if (memcmp(dev->uuid, dummy_uuid, DM_UUID_LEN))
       dmv = dm_dev_lookup_uuid(dev->uuid);     // SKIPPED: uuid is zero-filled

   if ((dmv == NULL) &&                           // dmv == NULL (uuid was zero)
       (_dm_dev_lookup(dev->name, NULL, dev->minor) == NULL)) {  // name EXISTS now
       ...
   } else {
       KKASSERT(dmv != NULL);   // PANIC: dmv is NULL
       r = EEXIST;
   }
   ```
   Since `dev->uuid` is zero-filled (the normal case for create without uuid),
   the uuid lookup at line 187-188 is skipped, so `dmv` stays NULL. But
   `_dm_dev_lookup` at line 191 finds the name (inserted by the winning thread),
   so we enter the else-branch with `dmv == NULL`. The `KKASSERT(dmv != NULL)`
   at line 195 fires → **kernel panic**.

## Panic signature

```
panic: assertion "dmv != NULL" failed in dm_dev_insert at /usr/src/sys/dev/disk/dm/dm_dev.c:195
Trace:
dm_dev_insert() at dm_dev_insert+0xe1
dm_dev_create() at dm_dev_create+0x223
dm_dev_create_ioctl() at dm_dev_create_ioctl+0xa0
dmioctl() at dmioctl+0x2eb
dev_dioctl() at dev_dioctl+0x65
```

## Exploit chain

N/A — **valid hard blocker (root-only reachability)**. `/dev/mapper/control`
is 0640 root:operator; maxx (uid 1001) is not in operator/wheel. The bug
requires root to trigger. KKASSERT panic is not a write primitive, so no
escalation chain.

## Fix

Remove the incorrect `KKASSERT(dmv != NULL)` at dm_dev.c:195. The assertion is
wrong: `dmv` can legitimately be NULL here (when uuid is zero-filled and name
lookup matched). The `r = EEXIST` assignment doesn't use `dmv`, so removing
the assertion is safe. See `fix.diff`.

## Fix validation

- **Unpatched (#0 baseline):** PoC panics on round 1 with
  `panic: assertion "dmv != NULL" failed in dm_dev_insert`.
- **Patched (fixed dm.ko):** 5 consecutive rounds complete cleanly
  (`created=1 existed=7` each round, RC=0). The KKASSERT no longer fires.

### Note on secondary devstat race

Under sustained concurrent racing (50+ rounds with 12 children), a **separate**
panic can occur: `Stopped at devstat_remove_entry+0x25: movq (%rdx),%rax` (NULL
deref). This is a race in the error cleanup path of `dm_dev_destroy` when the
losing thread destroys its partially-registered device (disk_destroy +
devstat_remove_entry race under concurrent access). This is a **different bug**
from DF-2442 — it's in the device teardown path, not the KKASSERT path. It was
previously masked by the KKASSERT firing first. The KKASSERT fix is correct and
sufficient for DF-2442; the devstat race should be filed as a separate finding.
