# DF-4474 — dm ioctl error paths leak proplib objects (iterator + retained array tree; deps/status cmd_array)

**REPRODUCED (permanent kernel heap leak) + FIX VALIDATED.**

## The bug

Three error paths in sys/dev/disk/dm/dm_ioctl.c drop proplib objects without
releasing them:

1. `dm_table_load_ioctl()` — `iter = prop_array_iterator(cmd_array)` at :708
   **retains the whole inbound cmd_data array** (sys/libprop/prop_array.c:525
   `prop_object_retain(pa)`), yet every early return leaks it:
   * `:713` ENOENT (device lookup failed)
   * `:739-741` ENOENT (unknown target type)
   * `:745-748` ENOMEM (dead code — M_WAITOK)
   * `:783-789` (dm_table_init failure)
   Only the success path releases at :793.
2. `dm_table_deps_ioctl()` — `cmd_array = prop_array_create()` at :602,
   ENOENT return at :606 without release.
3. `dm_table_status_ioctl()` — `cmd_array = prop_array_create()` at :895,
   ENOENT return at :899 without release.

## Reproduction (baseline, unpatched)

* **Permanent linear leak, +1 M_TEMP object (~40B) per failed reload**:
  InUse 470 → 570 → 670 → 740 tracking 100 + 100 + 60 failed reloads
  exactly, never dropping (run.log). The object is the leaked iterator.
* **With fat cmd_data payloads the retained tree costs ~MBs per call**:
  60 failed reloads carrying 4000-entry (~1.4MB XML) cmd_data trees
  consumed ~294MB of kernel memory (v_free_count 821767 → 746491 pages),
  of which ~208MB was still allocated after unrelated churn — consistent
  with the iterator pinning the array tree (prop_array.c:525). M_TEMP
  zone accounting under-reports the retained tree, so the page counter is
  the honest signal.

An operator-group user can loop failed reloads with large cmd_data payloads
and exhaust kernel memory (OOM / allocation-failure panics) within minutes.

## Fix validation

`fix.diff` (shared with DF-4472/DF-4473) releases the iterator on every
early return of `dm_table_load_ioctl` and releases `cmd_array` on the
deps/status ENOENT paths.

* Baseline: +1 object per failed reload (470 → 740 over the series), ~MBs
  retained per fat failed reload.
* Patched (`make` -Werror clean, kldload): **200 failed reloads + 60000
  deps/status ENOENT calls leave InUse unchanged at 470**; free-page count
  healthy (826089). (run.log)

## Files

* `dm_errleak.c` — PoC (fat failed reloads + deps/status ENOENT loops,
  vmstat/page accounting)
* `run.log` — baseline leak series + patched zero-leak run
* `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.
