⬢ DragonFlyBSD Kernel Audit
DF-5094 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5094",
  "status": "not_reproduced",
  "reproduced": false,
  "impact": "none",
  "confidence": "certain",
  "verdict": "The multi-consumer heap double-free hypothesis (two concurrent vinum daemons double-dequeueing one daemonq element and Free()ing it twice at vinumdaemon.c:177) is REFUTED on current DragonFly: vinum_ops is not D_MPSAFE (vinum.c:58-70), so dev_doperate_ops() wraps every vinum ioctl in get_mplock() (kern_device.c:570-577); the daemon lives inside its VINUM_DAEMON ioctl and every daemonq producer (complete_rqe at vinuminterrupt.c:75, config ioctls, free_vinum) holds the same MP token, so dequeue/append/Free are token-serialized despite the crit-only guard. Two adversarial runs (3 daemon threads + 4 producer threads, 6-CPU guest) pushed 27.8M + 18.7M elements with zero corruption and a healthy guest; a live wchan capture shows two daemons asleep on vinum and the third blocked on mp_token. What IS real and demonstrated: VINUM_DAEMON spawns daemons unboundedly (vinumioctl.c:296), abdication is pid-based and lazy (vinumdaemon.c:83 \u2014 same-process threads are never ousted, a busy daemon never re-checks), and the parked daemon threads make the host process survive kill -9 in D state (captured), aggravating the DF-4760 stop-wedge. If vinum ever adopts D_MPSAFE or the daemon moves out of ioctl context without adding a real queue lock, this exact PoC becomes the double-free trigger.",
  "exploit_chain": "",
  "evidence": "run.log / run.2.log (46.5M raced elements, RC=0, guest up both runs); threadstate.txt (wchan capture: 2x vinum sleepers + 1x mp_token blocker inside the same process; kill -9 leaves PID 1316 in D1E; kldstat shows vinum.ko non-removable); VERDICT.md (full MP-token reasoning with path:line); dmesg.txt (no allocator complaints, only benign DIOCGPART rejections)",
  "kernel_refs": [
    "sys/dev/raid/vinum/vinumdaemon.c:83",
    "sys/dev/raid/vinum/vinumdaemon.c:89-94",
    "sys/dev/raid/vinum/vinumdaemon.c:177",
    "sys/dev/raid/vinum/vinumdaemon.c:246",
    "sys/dev/raid/vinum/vinumioctl.c:296-301",
    "sys/dev/raid/vinum/vinum.c:58-70",
    "sys/dev/raid/vinum/vinum.c:459",
    "sys/kern/kern_device.c:570-577",
    "sys/dev/raid/vinum/vinuminterrupt.c:75"
  ],
  "poc_changes": "authored from scratch (no runnable seed): correct superdev node /dev/vinum/control (production name; /dev/vinum/Control is the VINUMDEBUG twin), ioctl letter L=F (vinumio.h:39), VINUM_DAEMON=_IOC(0,F,92,0) no-arg ioctl, VINUM_SAVECONFIG=_IOW(F,72,int) as the fast queue producer, VINUM_SETDAEMON noupdate to keep elements no-ops, and deliberately same-process daemon threads so the pid-based abdication check can never oust any of them",
  "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": 240,
  "guest_dirty": 1,
  "build_cmd": "kldload vinum; cc -O2 -pthread -o /tmp/df5094 /tmp/df5094_multidaemon.c",
  "run_cmd": "/tmp/df5094 90   (repeat: /tmp/df5094 60)",
  "code_hash": "0b6823bc68f00ba15db8e30cc9cf9d25436185b18bb53681c4f3687602e77754",
  "notes": "Pass-2 value: (1) closes the obvious DF-2103 no mem corruption extension with live negative evidence and documents the MP-token invariant for future vinum audits (it also tempers DF-2103s cross-CPU element-loss claim for token-holding producers, though the lost-wakeup liveness half of DF-2103 stands); (2) documents the real residual: unbounded daemon instantiation, pid-blind abdication, unkillable P_SYSTEM processes, non-unloadable module. Root-gated throughout (superdev requires SYSCAP_RESTRICTEDROOT, vinum.c:459). Guest reset (vm.sh reset with-src) performed after evidence capture.",
  "recommended_fix": "Refuse VINUM_DAEMON/VINUM_FINDDAEMON when a daemon is already alive: track the daemon by its thread pointer (not pid) cleared on every exit path, reject re-entry with EBUSY, and make the daemon tsleep PCATCH-aware or wake it on process exit so its host process is killable; if D_MPSAFE is ever adopted, replace the crit sections around daemonq/dqend/intqp with a real spinlock.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "No fix validation performed: the memory-corruption claim did not reproduce on the baseline (MP token serializes all daemonq access), so there is no observable bad behavior to flip; the residual unbounded-daemon/unkillable-process defect is robustness-level and root-gated. fix.diff intentionally omitted per the false-positive convention.",
  "fix_evidence": "VERDICT.md What is refuted section; run.log/run.2.log show no baseline corruption to compare a patched kernel against"
}