# DF-4599 verdict — REPRODUCED (panic / kernel-heap UAF read; root-gated)

## Root cause (code, certain)
* `vinumioctl()` dispatches `VINUM_RESETCONFIG` at `sys/dev/raid/vinum/vinumioctl.c:214-226`:
  it checks `vinum_inactive(0)` (which itself takes and *releases* `lock_config`, `vinum.c:179-187`)
  and then calls `free_vinum(1)` **without holding `lock_config`**.
* `free_vinum()` (`sys/dev/raid/vinum/vinum.c:199-259`) frees **all four** global object arrays —
  `Free(DRIVE)` :213, `Free(SD)` :228, `Free(PLEX)` :244, `Free(VOL)` :255 — `bzero(&vinum_conf, …)`
  and reallocates initial-size arrays via `vinum_initconf()` :258. It never takes the config lock.
* Meanwhile `VINUM_CREATE` (`vinumioctl.c:102-116`) *does* hold `lock_config()` while
  `parse_user_config` → `find_volume()`/`get_empty_vol()`/`EXPAND` walks and grows those same arrays
  (`EXPAND` → `expand_table()` `Free()`s the old array, `vinummemory.c:65-81`).
  Every unlocked reader (`VINUM_*CONFIG` bcopy handlers, `vinumioctl.c:118-178`) is equally exposed.

So the lock that `VINUM_CREATE` relies on is simply ignored by the biggest destructor in the driver.
Windows: (a) reader/writer walking an array freed by `free_vinum` (UAF read), (b) `free_vinum`'s
`Free(VOL)` racing `EXPAND`'s `Free(old)` on the same chunk (double free), (c) `parse_user_config`
writing into an array freed by `free_vinum` (UAF write).

## How it was reproduced (stock kernel, `kldload vinum`, root)
3 threads hammering `/dev/vinum/control`:
* creator: `VINUM_CREATE` ("volume vN", N monotonic → constant `EXPAND(VOL)` churn under the lock)
* resetter: `VINUM_RESETCONFIG` loop (`free_vinum` every iteration)
* reader: `VINUM_SDCONFIG` loop (unlocked bcopy)

Run 2 of 3 (240 s) killed the guest: serial console shows
`Fatal trap 12: page fault while in kernel mode … supervisor read data, page not present …
current process = 958 (resetconfig_race) … Stopped at strcmp+0xc` (see `panic.txt`).
`strcmp` on this path is `find_volume()`'s `strcmp(VOL[i].name, name)` walking the `VOL[]` array that
the racing `free_vinum` had just freed — a freed/unmapped kernel-heap page fault (UAF read) in the
process that *held* the config lock. Runs 1 (100 s) and 3 (240 s) survived: the race is probabilistic
(the decisive free/realloc window is narrow relative to the `destroy_dev` churn inside `free_vinum`).

## Impact
Root-gated (`/dev/vinum/control` 0600 + SYSCAP_RESTRICTEDROOT), so this is a post-root robustness /
kernel-integrity bug: demonstrated kernel heap UAF read (panic); the same interleaving gives UAF-write
(creator writing into freed arrays) and double-free windows. No unprivileged vector — the device cannot
be opened by non-root. Realistic severity: Medium (matches DF-1739/DF-4478 precedent).

## Fix validation (fix.diff)
`fix.diff` wraps `free_vinum(1)` in `lock_config()`/`unlock_config()` with a `setjmp(command_fail)`
(free_vinum → remove_drive → save_config can `throw_rude_remark` under the lock), and adds the missing
`break` that previously fell through into `VINUM_SETSTATE` (that fallthrough is DF-1741; leaving it
while holding no lock would trade one bug for another, so the break is required by this fix too).
Note `vinum_inactive(0)` must stay *outside* the lock (it takes the lock itself; nesting would deadlock).

* Patched build: `make nativekernel KERNCONF=X86_64_GENERIC` rc=0, `make installkernel` rc=0
  (kernel #1 Tue Sep 22 00:51:18 UTC 2026, `vinum.ko` rebuilt with the fix).
* Patched runs: 2 × 240 s at ≥ baseline load (1.51M+1.60M resetconfigs, 9.23M+3.61M sdconfigs,
  ~333K volume creations): guest survived both, no panic (`fix_runs.log`).
* Baseline vs patched: 1 kill / 3 runs → 0 kills / 2 runs at higher op counts. **Fixed.**

## Negative results worth recording
* `bus_dmamap_unload`/`bus_dmamem_free` NULL-guard their maps (`sys/sys/bus_dma.h:312`,
  `busdma_machdep.c:553`) — mpt's partial-init free path is safe.
* `expand_table` bzeroes new slots, so `VINUM_GETFREELIST` on a `drive_referenced` slot
  (`freelist_entries==0`) is bounded — no new OOB there beyond known DF-1740.
* `VINUM_SDCONFIG/PLEXCONFIG/VOLCONFIG/DRIVECONFIG` use unsigned compares on the index —
  negative indices are rejected (unlike the `valid*`/`resetstats` paths of DF-1739).

## 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.
