# DF-5065 — `dm_target_uninit()` tears the dm target registry down unconditionally: frees still-registered / still-referenced `dm_target_t` objects and destroys the registry lock

**File:** `sys/dev/disk/dm/dm_target.c:277-295` (with the entry path in
`sys/dev/disk/dm/device-mapper.c:187-197`)
**Severity:** Medium · **Confidence:** certain · **Reproduced:** yes —
deterministic `Fatal trap 12`, no race, no timing (1/1 attempts).

## Build (guest, as root)

```sh
# host: ship sources + prop headers, then in guest:
cc -O2 -Wall -I/tmp/propinc -o /tmp/dmt_registry_uaf dmt_registry_uaf.c -lprop
```
(`build.sh` automates both sides — the guest has libprop.so but no headers.)

## Run (as root; /dev/mapper/control is 0640 root:operator)

```sh
sh /tmp/run.sh
```

## Expected (baseline)

Seven sequential steps — **no race, no timing**:

| # | command | effect |
|---|---------|--------|
| 0 | `kldload dm` | dm.ko linker `refs=1` (base), `userrefs=1`; registry holds builtin `error`/`zero` |
| 1 | `dmt_registry_uaf create df5065dev` | `dm_dev_counter` 0→1 (dm_dev.c:193) |
| 2 | `kldunload dm` | `dm_modcmd` EBUSY (device-mapper.c:161) → MOD_UNLOAD **veto** → `linker_file_unload` veto path executes `file->refs--` **with refs==1** (kern_linker.c:514-519) → dm refs 1→0: a *failed* unload destroyed the base reference |
| 3 | `dmt_registry_uaf remove df5065dev` | counter 1→0 (dm_dev.c:71) |
| 4 | `kldload dm_target_linear` | MDT_DEPEND does `dm refs++` → 0→1; `refs > 1` deferral (kern_linker.c:494) can never trigger again → **MODULE_DEPEND pin defeated**; linear registered in dm's registry |
| 5 | `kldunload dm` | refs==1 → full unload attempt → counter==0 → `dmdestroy()` → **`dm_target_uninit()` (dm_target.c:284-292) frees linear's registered `dm_target_t` and `lockuninit`s `dm_target_mutex` — then dm.ko text/data are freed** |
| 6 | `kldunload dm_target_linear` | `dmtl_mod_handler(MOD_UNLOAD)` → `dm_target_remove("linear")` → **call into freed dm.ko text** |

Observed (panic.txt): console prints `Device Mapper unloaded` with linear
still loaded, then

```
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0xffffffff82604950
fault code = supervisor read instruction, page not present
instruction pointer = 0x8:0xffffffff82604950      <- freed dm.ko text region
current process = 870                             <- the kldunload syscall
```

1/1 deterministic; guest down at `db>`.

## Fix validation (same guest, `vm.sh reset with-src`, fix.diff applied to
/usr/src, dm.ko rebuilt -Werror-clean, identical PoC)

- step 5 `kldunload dm` → **"can't unload file: Device busy"** — the patched
  `dm_target_uninit` returns EBUSY (registry non-empty), `dmdestroy`
  propagates it, dm.ko **stays loaded** (`kldstat`: Refs=0 but present).
- step 6 `kldunload dm_target_linear` → rc=0, clean unload through *live*
  dm text; the dependency cascade then unloads dm.ko correctly; `STILL_UP`.

**fix_status: fixed** (baseline panics, patched does not).

## Relationship to known findings (not dupes)

- **DF-4751** (device-mapper.c, Low, reproduced): the *unlocked counter
  check* + *unpinned dev dispatch* race. This finding is the **second,
  independent defect at a different site**: `dm_target_uninit` performs no
  ref_cnt/registry validation at all (contrast `dm_target_remove`,
  dm_target.c:181). It (a) survives a DF-4751 fix — an ioctl holding a
  looked-up target ref (dm_ioctl.c:737) is left with a freed heap object —
  and (b) yields a **deterministic, race-free** chain via the kern_linker.c
  veto-path refs underflow. The registry check that this file is missing is
  precisely the *second veto* that would have stopped both.
- **DF-2437** (dm_target_delay.c): delay's handler ignoring `dm_target_remove`
  EBUSY. Here the EBUSY machinery itself is missing.
- Cross-file note for the orchestrator: `sys/kern/kern_linker.c:518`
  (`file->refs--` on a MOD_UNLOAD veto, executed even at refs==1) is the
  enabler of the deterministic variant and is itself an accounting bug worth
  a pass-2 audit of kern_linker.c.
