# DF-2446 — dm_message_ioctl uninitialized `msg` free/deref

## Summary
`dm_message_ioctl()` (`sys/dev/disk/dm/dm_ioctl.c`) declares `char *msg`
without an initializer and calls `prop_dictionary_get_cstring(...,&msg)`
without checking the return. When the `"message"` key is missing from the
ioctl dictionary, proplib leaves `*cpp` unwritten, so `msg` holds stack
residue. The function then calls `kfree(msg, M_TEMP)` on that residue →
kernel page-fault / panic (or, with stack grooming, an arbitrary-free/UAF
primitive).

## Privilege
Root/operator-only. `/dev/mapper/control` is `0640 root:operator`
(`device-mapper.c:181`), the `dm` module is demand-loaded via root-only
`kldload`, and there is no setuid helper or devfs relaxation. Verified:
unprivileged `maxx` gets `Permission denied`. This is a root→kernel
robustness/hardening gap (local DoS + potential heap corruption), **not**
an unprivileged→root escalation.

## Reproduce
```sh
./build.sh && ./run.sh      # run.sh does: kldload dm; ./dm_uninit_msg
```
- **Build:** `cc -o dm_uninit_msg dm_uninit_msg.c -lprop`
- **Run as root** (must be root or operator-group to open the control dev).
- **Expected on the BUGGY (unpatched) kernel:** kernel panic —
  `Fatal trap 12: page fault while in kernel mode`,
  `Stopped at _kfree+0x45: movl 0x54(%rax),%r13d`, guest wedged in DDB.
- **Expected on the FIXED kernel:** the message ioctl returns `EINVAL (22)`,
  the PoC prints `message ioctl returned rv=22`, exits 0, and the guest
  stays up.

## How the PoC works
1. Opens `/dev/mapper/control`.
2. Sends `NETBSD_DM_IOCTL` with `command="create"`, `name="df2446dev"` —
   creates a dm device so `dm_dev_lookup()` succeeds inside
   `dm_message_ioctl`.
3. Sends `NETBSD_DM_IOCTL` with `command="message"`, `name="df2446dev"`,
   and **omits** the `"message"` key. `prop_dictionary_get_cstring` returns
   false without writing `&msg`, so `msg` stays uninitialized; the
   unconditional `kfree(msg, M_TEMP)` frees stack garbage.

## Fix
See `fix.diff`: initialize `char *msg = NULL;`, check the
`prop_dictionary_get_cstring` return (return `EINVAL` on failure after
unbusying the device), and guard the cleanup `kfree` with `if (msg != NULL)`.
Validated by rebuilding the `dm` module and re-running the same PoC —
panic → clean `EINVAL`.
