⬢ DragonFlyBSD Kernel Audit
DF-5029 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5029",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "Unprivileged local user can create unkillable D-state processes at will: pcfclock_open() (sys/dev/misc/pcfclock/pcfclock.c:152-153) requests the ppbus with bare PPB_WAIT, which equals PPB_WAIT|PPB_NOINTR (sys/bus/ppbus/ppbconf.h:125-128), so ppb_request_bus() (sys/bus/ppbus/ppbconf.c:474-476) tsleeps on ppbreq with no PCATCH and no timeout while any owner holds the bus. Because /dev/pcfclock0 is 0444 root:wheel (pcfclock.c:133-134), any user can hold the bus forever, and every other process that blocking-opens the device sleeps unkillably until the holder releases; SIGKILL stays pending. Reproduced 2/2 on a custom-kernel guest with a QEMU ISA parallel port: victim STAT D0, wchan ppbreq, survived kill -9 while held, reaped only after release; O_NONBLOCK control gave EWOULDBLOCK. Not memory corruption - DoS only, hardware-gated (parallel port + kernel with pcfclock), hence Low severity.",
  "exploit_chain": "unprivileged open(/dev/pcfclock0) [holder] -> takes permanent ppbus ownership -> victim blocking-open sleeps in non-PCATCH tsleep ppbreq -> kill -9 ineffective (pending only) -> repeat to accumulate unkillable D-state processes; also stalls lpt0/ppi0/vpo0 consumers for the whole time.",
  "evidence": "run.log and run.2.log: RESULT: victim pid 847 SURVIVED kill -9 (unkillable D-state sleep) + ps row 847 D0 ppbreq ./pcf_dos victim + victim_nb: open returned fd=-1 errno=35 + post-release: victim reaped once the ppbus was released. fix_run.log (kernel PCFTEST #1 with fix.diff): RESULT: victim died on kill -9. env.txt: pcfclock0 cr--r--r-- root:wheel attach lines.",
  "kernel_refs": [
    "sys/dev/misc/pcfclock/pcfclock.c:152",
    "sys/dev/misc/pcfclock/pcfclock.c:133",
    "sys/bus/ppbus/ppbconf.h:125",
    "sys/bus/ppbus/ppbconf.c:466",
    "sys/dev/misc/ppi/ppi.c:256"
  ],
  "poc_changes": "n/a - trigger written fresh for this finding (no seed). Guest environment had to be constructed: stock X86_64_GENERIC lacks pcfclock, so a custom kernel (PCFTEST = GENERIC + ppc0/ppbus/lpt/ppi/pcfclock) was built on the guest and QEMU was started with -device isa-parallel to attach a parallel port. First generated fix.diff had an unbalanced paren (caught by the guest kernel build, fixed and re-validated).",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Sat Sep 26 05:30:21 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/PCFTEST  x86_64 (baseline) / #1: Sat Sep 26 05:45:18 UTC 2026 (fixed)",
  "runtime_sec": 180,
  "guest_dirty": 0,
  "build_cmd": "cc -O -Wall -o pcf_dos pcf_dos.c   (kernel: make -j6 nativekernel KERNCONF=PCFTEST && make installkernel KERNCONF=PCFTEST)",
  "run_cmd": "./run_test.sh   (as unprivileged uid=1001 maxx)",
  "code_hash": "be18afc7af428af6592d07d201b90dfa0073d73a74573bd577e87889f41ba141",
  "notes": "DoS only; severity Low because stock X86_64_GENERIC does not compile pcfclock and the trigger needs a parallel port. Same bare-PPB_WAIT pattern exists in sys/dev/disk/vpo/vpoio.c:616 (different file, follow-up). The read paths DELAY busy-wait (~7.4 s worst case per read, uninterruptible) is documented as a secondary note in VERDICT.md.",
  "recommended_fix": "Pass PPB_WAIT | PPB_INTR (like ppi.c/pps.c/if_plip.c) instead of bare PPB_WAIT in pcfclock_open().",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Sep 26 05:45:18 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/PCFTEST x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (PPB_WAIT|PPB_INTR) applied to guest /usr/src, kernel PCFTEST rebuilt and installed; identical test as the same unprivileged user now shows kill -9 reaping the blocked victim immediately while the holder still owns the bus; EWOULDBLOCK non-blocking and EIO read semantics unchanged. Bad behavior eliminated.",
  "fix_evidence": "fix_run.log (RESULT: victim died on kill -9 (bug NOT reproduced) on kernel #1 05:45:18) vs run.log/run.2.log (SURVIVED kill -9 on kernel #0 05:30:21); fix.diff applies cleanly to pristine sys/ (patch --dry-run verified); fixed kernel install completed 05:48:46 UTC (fix_build.log)."
}