DF-5029 / verdict.json
{ "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)." } |