# DF-2435 — dm_target_crypt_init status_str heap overflow via %ju of negative strtouq

## Summary
`dm_target_crypt_init()` (`sys/dev/disk/dm/crypt/dm_target_crypt.c`) sizes the
`status_str` buffer from the sum of input argv string lengths (line 462-466),
then formats `iv_offset` and `block_offset` with `%ju` (line 573) after parsing
via `strtouq` (lines 475, 477). `strtouq("-1")` returns `UQUAD_MAX` =
`18446744073709551615` (20 digits), but the buffer was sized from the original
`"-1"` string (2 chars). `ksprintf()` (sprintf, no bounds check) writes 36
bytes past the `kmalloc'd` buffer → kernel heap overflow (CWE-787).

## 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 `dm_target_crypt` auto-loads from there. Verified: unprivileged
`maxx` gets `Permission denied`. This is a root→kernel memory-corruption /
local-DoS / hardening gap, **not** an unprivileged→root escalation.

## Reproduce
```sh
./build.sh && ./run.sh overflow      # single-shot: readback proof of overflow
./build.sh && ./run.sh loop          # loop (200 iters): baseline panics ~100
```
- **Build:** `cc -O2 -o dm_crypt_overflow dm_crypt_overflow.c -lprop`
- **Run as root** (must be root or operator-group to open the control dev).
- **Expected on the BUGGY (unpatched) kernel:**
  - `overflow` mode: prints `OVERFLOW CONFIRMED: status_str is 129 bytes but
    buffer was only kmalloc(94)`.
  - `loop` mode: panics after ~100-125 iterations with
    `panic: BADFREE2` at `_kfree` ← `dm_table_load_ioctl`, or
    `chunk_mark_allocated` assertion failure at `_kmalloc` ←
    `dm_target_crypt_init`.
- **Expected on the FIXED kernel:** both modes complete cleanly (RUN_EXIT=0),
  guest stays up, no panic.

## How the PoCs work
1. `dm_crypt_overflow` opens `/dev/mapper/control`, creates a dm device, reloads
   a `crypt` table with params `"aes-xts-plain <key> -1 /dev/md0 -1"`. The
   negative offsets trigger `strtouq("-1")` → `UQUAD_MAX`, and `ksprintf("%ju")`
   overflows the undersized `status_str`. Then reads back `status_str` via
   `command="table"` (using `prop_dictionary_sendrecv_ioctl`) to show 129 bytes
   from a 94-byte allocation — definitive proof of the overflow.
2. `dm_crypt_loop` repeats create+overflow-reload+remove 200× to accumulate
   slab-zone corruption until the INVARIANTS slab bitmap/redzone checks catch
   it as a panic.

## Fix
See `fix.diff`: replace `kmalloc(len, ...)` with `kmalloc(DM_MAX_PARAMS_SIZE,
...)` (1024 bytes, same size used by `dm_target_crypt_table` when copying
`status_str`). Validated by rebuilding the `dm_target_crypt` module and
re-running both PoCs — overflow confirmed → clean completion, panic → no panic.
