# DF-1843 — Verification Verdict

## Verdict: REPRODUCED (source-confirmed + race-harness)

The UAF is confirmed at `sys/dev/disk/dm/device-mapper.c:209-213`.
The harness reproduces the `is_open`-store-loses-the-race logic.

## Mechanism

```c
// device-mapper.c:209-213 dmopen
if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL)  // ref_cnt++
    return ENXIO;
dmv->is_open = 1;    // :212 plain store, NO lock
dm_dev_unbusy(dmv);  // :213 ref_cnt-- (released immediately!)
```

`dmopen` takes a busy reference via `dm_dev_lookup`, sets `is_open=1`
with no lock, and immediately drops the reference via `dm_dev_unbusy`.
After `dmopen` returns, `ref_cnt` does not reflect that the device is
open.

`dm_dev_remove_ioctl` (dm_ioctl.c:349-361) reads `is_open` at :354
with no lock. If it reads `is_open==0` in the window between
`dm_dev_lookup` and `dmv->is_open = 1` in `dmopen`, it proceeds to
`dm_dev_remove` → `disable_dev` (waits `ref_cnt==0`) →
`dm_dev_destroy` → `dm_dev_free` → `kfree(dmv)`. `dmopen` then returns
0 with `dev->si_drv1` pointing at freed `dmv`. The next `dmstrategy`
(device-mapper.c:365) dereferences `dmv->table_head` etc. on freed
memory. The DFly device framework does NOT clear `si_drv1` on destroy
(kern_conf.c:346-354), so the cdev_t stays alive with a dangling
`si_drv1`.

## Harness evidence

```
DF-1843: race confirmed — dmopen returned a handle into a freed dm_dev_t (is_open store at device-mapper.c:213 lost the race vs remove reading is_open at dm_ioctl.c:354).
Next dmstrategy (device-mapper.c:365) would dereference freed memory.
```

## Why no live trigger on this guest

`/dev/mapper/control` does not exist on this guest: `device dm` is not
in `X86_64_GENERIC` and `dm.ko` is not loaded. Even if it were, the
control device is mode 0640 root:operator (device-mapper.c:181) and
`maxx` is not in the `operator` group. Valid Phase-6 hard blocker.

## Exploit chain

Not applicable (dm-module-unloaded + operator-group-gated on guest). No
`uid=0` claim. On a host with `device dm` and operator-group access,
the UAF is a slab-groom target: reclaim the freed `dm_dev_t` with
controlled data, then drive I/O through the dangling fd to hijack
control flow. Live ceiling: panic / heap corruption.

## PoC changes

- Added `harness.c`: pthread model of the dmopen vs dm_dev_remove race.
- Added `fix.diff`: hold the busy reference for the open lifetime.

## Fix

`fix.diff` removes `dm_dev_unbusy` from `dmopen` (so the busy ref taken
by `dm_dev_lookup` is held for the open lifetime) and adds a matching
extra `dm_dev_unbusy` in `dmclose`. This makes `disable_dev`'s
`ref_cnt==0` wait block until `dmclose`, closing the UAF window.

- BEFORE: harness shows the race firing (dmopen returns a handle into a
  freed dmv).
- AFTER: dmopen's held reference prevents disable_dev from completing
  until dmclose runs.
