{
  "finding_id": "DF-4599",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "VINUM_RESETCONFIG (vinumioctl.c:214-226) calls free_vinum(1) (vinum.c:199-259) which frees ALL vinum object arrays (DRIVE/SD/PLEX/VOL) without ever taking lock_config(), so it races even the lock-holding VINUM_CREATE writer and every unlocked *CONFIG reader. Reproduced on the stock guest kernel: 3-thread race (volume-creator via VINUM_CREATE with constant EXPAND(VOL) churn under the lock, VINUM_RESETCONFIG hammer, VINUM_SDCONFIG reader) killed the guest in run 2 of 3 with Fatal trap 12 ... supervisor read data, page not present ... Stopped at strcmp+0xc, current process = resetconfig_race - find_volume()s strcmp walking the VOL[] array freed out from under it by free_vinum. UAF-read proven; UAF-write (parse writing into freed arrays) and double-free (free_vinum Free vs EXPAND Free of same chunk) windows exist on the same interleaving. Root-gated: /dev/vinum/control is 0600 root:wheel plus caps_priv_check(SYSCAP_RESTRICTEDROOT) at open, so no unprivileged vector; severity is post-root kernel-integrity/robustness (Medium). Fix (lock_config + setjmp around free_vinum, plus the missing break so it no longer falls into VINUM_SETSTATE) built via nativekernel+installkernel and validated: patched kernel survived 2x240s at >= baseline load (1.5M+1.6M resetconfigs, 9.2M+3.6M sdconfigs) with no panic.",
  "exploit_chain": "root opens /dev/vinum/control (0600 root:wheel, SYSCAP_RESTRICTEDROOT); thread A: ioctl(VINUM_CREATE, volume vN) loop - parse_user_config/find_volume walks and EXPAND-grows VOL[] while holding lock_config; thread B: ioctl(VINUM_RESETCONFIG) loop - free_vinum Free(VOL)+bzero(vinum_conf)+vinum_initconf realloc, ignoring the lock; thread C: ioctl(VINUM_SDCONFIG) unlocked bcopy reader. Interleaving B-free vs A-walk => kernel heap UAF read (observed: page-fault panic in strcmp); B-free vs A-EXPAND-free => double free; B-free vs A-write => UAF write. Ceiling: kernel heap corruption from a root-only device - denial of service / kernel memory corruption behind root, not an unprivileged escalation.",
  "evidence": "panic.txt (Fatal trap 12, strcmp+0xc, process 958 = PoC, fault VA 0xfffff80119a18000 page-not-present); run.log (baseline: kill on run 2/3, survive runs 1+3); fix_runs.log (patched: 2x240s survived, 9.23M/3.61M reads + 1.51M/1.60M resets); fix_build.log (nativekernel rc=0); VERDICT.md for the full narrative",
  "kernel_refs": [
    "sys/dev/raid/vinum/vinumioctl.c:214",
    "sys/dev/raid/vinum/vinum.c:199",
    "sys/dev/raid/vinum/vinum.c:213",
    "sys/dev/raid/vinum/vinum.c:255",
    "sys/dev/raid/vinum/vinummemory.c:65",
    "sys/dev/raid/vinum/vinumlock.c:227"
  ],
  "poc_changes": "v1 (single volume rv0 + RESETCONFIG + SDCONFIG) survived 90s: tokenize() stops at newline so only the first config line is parsed per ioctl and the arrays never grew, leaving only a tiny realloc window. v2 creates volume vN with monotonic N so every 4th ioctl triggers EXPAND(VOL) (Malloc/bcopy/Free of the old array) under lock_config, and adds a plex xN creator thread for PLEX churn; v2 killed the guest in 1 of 3 runs.",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 1140,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -o /root/resetconfig_race /root/resetconfig_race.c -lpthread",
  "run_cmd": "kldload vinum; /root/resetconfig_race 240",
  "code_hash": "8ff2f104b02a7e9b5f1bbdfffb48fec198600c281bbc3da0013902d391e1f315",
  "notes": "Race is probabilistic (decisive window is a few instructions wide against destroy_dev-dominated free_vinum): 1 kill / 3 baseline runs at 100-240s each. Distinct from DF-4478: the panic occurred in the thread HOLDING lock_config - DF-4478s fix (locking SETSTATE/ATTACH/DETACH) does not cover free_vinum. The fixs added break also resolves the DF-1741 RESETCONFIG->SETSTATE fallthrough (required: falling into setstate while releasing the lock would be a new unlocked path). vinum_inactive(0) stays outside the lock to avoid self-deadlock (it takes lock_config internally).",
  "recommended_fix": "Wrap free_vinum(1) in lock_config()/unlock_config() with setjmp(command_fail) throw-safety in the VINUM_RESETCONFIG case and add the missing break before VINUM_SETSTATE (see fix.diff)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (lock_config + setjmp around free_vinum, missing break added) applied to guest /usr/src, nativekernel + installkernel rc=0, rebooted into kernel #1 Sep 22 00:51:18; same 3-thread PoC survived 2x240s runs at >= baseline op counts with guest up and no panic.",
  "fix_evidence": "fix_runs.log (run1 survived 240s without a panic explicit; run2 all worker-exit counters printed, process exited, guest stayed up); fix_build.log (kernel build completed rc=0); env.txt (patched-kernel uname #1)"
}