# DF-4472 — dm_table_load_ioctl: uninitialized stack pointer `type` dereferenced as a C string

**REPRODUCED (panic / local DoS) + FIX VALIDATED.**

## The bug

`dm_table_load_ioctl()` (sys/dev/disk/dm/dm_ioctl.c:673) declares
`const char *name, *uuid, *type;` at :684 and initializes name/uuid/dmv/str —
but **never `type`** (:690-695). Inside the table-entry loop it does:

```c
730  while ((target_dict = prop_object_iterator_next(iter)) != NULL) {
731      prop_dictionary_get_cstring_nocopy(target_dict, DM_TABLE_TYPE, &type);
...
737      if (((target = dm_target_lookup(type)) == NULL) &&
738          ((target = dm_target_autoload(type)) == NULL)) {
```

`prop_dictionary_get_cstring_nocopy()` (sys/libprop/prop_dictionary_util.c:185)
returns false **without touching the out-param** when the key is absent, the
element is not a dictionary, or the value is not a string. On the first loop
iteration `type` therefore holds stale kernel-stack data, which is then
dereferenced as a NUL-terminated string by:

* `dm_target_lookup_name()` → `strcmp(dm_target_name, dmv->name)`
  (sys/dev/disk/dm/dm_target.c:127) — wild read;
* on lookup failure, `dm_target_autoload()` →
  `ksnprintf(mod_name, sizeof, "dm_target_%s", dm_target_name)`
  (dm_target.c:74) — second wild read of the same garbage.

## Reproduction (baseline, unpatched)

Three independent confirmations on the QEMU guest (DragonFly 6.5-DEVELOPMENT,
X86_64_GENERIC, dm.ko kldload-ed):

1. **Wild read executes on 100% of rounds (250/250).** Groom mode: reload #1
   (valid, `type:"zero"`) loads a 1-entry inactive table; reload #2 omits the
   `"type"` key entirely → returns ENOENT **and** the inactive table is now
   empty. dm_ioctl.c has exactly one ENOENT return after the
   `dm_table_destroy(INACTIVE)` at :722-723 — the lookup failure at :741 —
   so `dm_target_lookup(garbage)` demonstrably executed with the stale value
   (run.log: 50/50 and 200/200 rounds).
2. **Autoload prints the garbage.** Serial console during flood:
   `dm: could not autoload module for target <binary garbage>` — the
   ksnprintf at dm_target.c:74 reading uninitialized memory.
3. **PANIC.** Flood mode (8 threads × 800 malformed reloads) — the garbage
   landed on an unmapped value (fault VA 0x8) and the kernel took a fatal
   page fault in `strcmp+0xc` called from the lookup path; guest down
   (panic.txt: `Fatal trap 12 … Stopped at strcmp+0xc: movzbl (%rdi,%rax,1),%edx`).

## Why no uid=0

The primitive is a **wild read of a stack-derived pointer** used as a string.
There is no write primitive; the value at the stack slot cannot be aimed at
an attacker-chosen address by construction (it is kernel-stack residue from
the proplib internalize machinery of the same ioctl). Best case for an
attacker is the observed reliable local DoS (panic) — the same ceiling as the
known DF-2448 NULL-deref on this surface. Valid hard blocker for escalation.

## Privilege precondition

`/dev/mapper/control` is created 0640 root:operator
(sys/dev/disk/dm/device-mapper.c:181) and `dm` must be kldload-ed by root —
threat model is operator-group member (or root) → kernel, consistent with the
known DF-1639/1638xx family on this file.

## Fix validation

`fix.diff` (shared with DF-4473/DF-4474; this file's relevant hunk):
initialize `type = NULL` before the loop, re-initialize per iteration, and
check the `prop_dictionary_get_cstring_nocopy` return value → EINVAL.

* Baseline: groom 50/50 wild-read proofs; flood → fatal trap 12, guest down.
* Patched (dm.ko rebuilt with fix.diff, -Werror clean): groom returns
  **EINVAL (22)** on every round (missing type rejected before any lookup);
  flood 8×800 completes with no panic, guest healthy (run.log §fix).

## Files

* `dm_uninit_type.c` — PoC (groom + flood modes)
* `run.log` — baseline groom (250/250), baseline panic, patched groom + flood
* `panic.txt` — full serial-console panic excerpt (strcmp+0xc, trap 12)
* `fix.diff` — combined dm_ioctl.c fix (validated)
* `env.txt`, `build.sh`, `run.sh`

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
