{
  "finding_id": "DF-2815",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "The global `struct dumperinfo dumper` in sys/kern/kern_shutdown.c is completely unsynchronized: set_dumper() (:948-961) does an unlocked EBUSY check-then-write / bzero, and dumpsys() (:980-983) hands &dumper itself (not a snapshot) to md_dumpsys(), whose write loops re-read di->priv on every block (minidump_machdep.c:95/143/348/427). A DIOCGKERNELDUMP clear (subr_disk.c:1186-1188, unlocked and unprivileged per DF-2743) landing after dumpsys()'s check NULLs di->priv mid-dump; the next dev_ddump(NULL,...) faults reading NULL->si_ops in dev_needmplock (kern_device.c:119). Demonstrated on the stock INVARIANTS guest: with a 4-thread ioctl churner on the dump device and a single-threaded reboot(2) (RB_DUMP|RB_NOSYNC) caller, the kernel page-faults mid-dump at dev_ddump+0xc (fault VA 0xa8, cpu0, CRIT priority, trace dev_ddump <- blk_write; panic.txt). Ceiling rigorously assessed: the dumper function-pointer field can only be diskdump or NULL and priv only a referenced cdev, so no function-pointer forgery exists - the primitive tops out at kernel-panic DoS (proven) plus code-proven destructive dump misdirection (torn set-vs-set copy mixing priv/geometry of two devices -> wrong-device/wrong-offset dump writes). Fix (spinlock + stack snapshot in dumpsys) validated on a rebuilt guest kernel: identical churn recipe yields 'Dump complete' with the churner provably alive (run.patched.log).",
  "exploit_chain": "root/operator fd on a disk device -> 4 threads spam ioctl(DIOCGKERNELDUMP) set/clear (set_dumper NULL/bcopy race on the global) -> separate single-threaded process calls reboot(2) with RB_DUMP|RB_NOSYNC -> kernel enters dumpsys() during a 'set' phase -> churn clear bzeros dumper mid-dump -> dev_ddump(di->priv==NULL) -> dev_needmplock NULL->si_ops deref -> page fault while dumping -> panic in the reboot path (double panic if triggered from the panic path). uid0 NOT reachable: no attacker-controlled function pointer or forged object; impact ceiling = panic DoS + destructive wrong-device dump writes (torn registration variant).",
  "evidence": [
    "panic.txt - decisive serial excerpt: 'Dumping 666 MB:' then Fatal trap 12, fault VA 0xa8, dev_ddump()+0xc <- blk_write(), cpu0, pri 31 (CRIT)",
    "serial.race9.log - full serial console of the reproducing run (RACE-MARK-9, stock kernel #0)",
    "run.baseline.log - clean baseline dump without churn (Dump complete)",
    "run.patched.log - fix validation: churn markers (T/c) live through shutdown, 'Dumping 624 MB ... Dump complete', no fault (patched kernel #1)",
    "churn.c / rebootdirect.c - final PoC sources; run.sh - exact recipes",
    "fix.diff - git-apply-checked diff (spinlock dumper_lock + dumpsys snapshot)"
  ],
  "kernel_refs": [
    "sys/kern/kern_shutdown.c:137",
    "sys/kern/kern_shutdown.c:948-961",
    "sys/kern/kern_shutdown.c:963-984",
    "sys/kern/subr_disk.c:1186-1188",
    "sys/kern/subr_disk.c:915-939",
    "sys/kern/kern_device.c:119-122",
    "sys/platform/pc64/x86_64/minidump_machdep.c:95",
    "sys/platform/pc64/x86_64/minidump_machdep.c:143",
    "sys/platform/pc64/x86_64/minidump_machdep.c:348",
    "sys/platform/pc64/x86_64/dump_machdep.c:313-318"
  ],
  "poc_changes": "Seed concept (dumpctl churn vs reboot -d) did not reproduce as-is; iterated to a working recipe: (1) churner must be a separate process - threads of the reboot(2) caller are torn down by shutdown_cleanup_proc first (that is DF-2816, which panicked via a different path); (2) removed all in-loop file/console I/O from the churner - the root fs is force-unmounted before the dump and blocked writes freeze the churn threads; (3) replaced reboot(8) with a direct single-threaded reboot(2) helper using RB_NOSYNC|RB_DUMP so dumpsys() is reached while the churner is provably alive; (4) mode-3 churn cadence (clear every 64th ioctl) maximizes NULL duty. Added diagnostic marker variant to prove churner liveness through the shutdown phases.",
  "attempts": 5,
  "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": 5400,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -pthread -o /root/churn /root/churn.c && cc -O2 -Wall -o /root/rebootdirect /root/rebootdirect.c",
  "run_cmd": "sysctl debug.debugger_on_panic=0 debug.trace_on_panic=1; nohup /root/churn /dev/vbd0s1b 900 3 4 >/dev/null 2>&1 & sleep 2; /root/rebootdirect   # then: dfbsd-qemu/vm.sh log",
  "code_hash": "churn.c=0f46e686da6bc35c5165fdbc82254cb03f5449d4c2e28b0c4111560ece2a6f2f rebootdirect.c=fb226a3046de5e729657e904c08eda4749d684c45e6a23bea0307be469340d5a",
  "notes": "Sysctl path kern.dumpdev is properly privilege-gated (SYSCAP_NOSYSCTL_WR, kern_sysctl.c:1446) and oid-lock serialized; the unserialized entry is the DIOCGKERNELDUMP ioctl (DF-2743 side). The torn set-vs-set wrong-device dump-write variant is code-proven but deliberately not executed (would corrupt the guest root disk); documented in VERDICT.md. Panic-path dumps are protected by stop_cpus (kern_shutdown.c:887); the exposed windows are reboot(2)-initiated dumps (root 'reboot -d'/'shutdown -d') and DDB-initiated dumps without stopped CPUs. Note: RB_NOSYNC reboots skip sync/unmount, so guest-side rebuilds made just before such a reboot are lost to the dirty restart - rebuild binaries on a normally-synced system first.",
  "recommended_fix": "Serialize set_dumper()/clear with a spinlock and make dumpsys() take a stack snapshot of the dumper under that lock before calling md_dumpsys() (see fix.diff).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep  1 17:21:28 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to the guest /usr/src copy, kernel rebuilt in-guest (make nativekernel && make installkernel) and booted (#1 Sep 1 17:21:28). Re-running the exact reproducing recipe (4-thread mode-3 churn + rebootdirect RB_DUMP|RB_NOSYNC): the dump now runs to completion ('Dumping 624 MB: ... Dump complete', run.patched.log) with the churner provably alive (console markers at boot() entry and through the kproc waits); no page fault, no panic. In an additional run the churn's clear happened to be in effect at dumpsys() entry and the dump was cleanly skipped - also correct, crash-free behavior. Baseline (stock kernel #0) signature 'Fatal trap 12 ... dev_ddump()+0xc while dumping' is gone.",
  "fix_evidence": [
    "run.patched.log - patched-kernel run: churn markers live, full dump progress, Dump complete, no Fatal/panic",
    "fix.diff - the validated diff (also carries the DF-2816 hunk validated in that same build)"
  ]
}
