β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5029

Unprivileged unkillable D-state sleep: pcfclock_open() requests the ppbus with bare PPB_WAIT (= PPB_WAIT|PPB_NOINTR) on world-readable /dev/pcfclock0

Summary

PPB_NOINTR==0 so bare PPB_WAIT selects the non-PCATCH infinite tsleep on ppbreq - any user can open the 0444 device and hold the ppbus, wedging every blocking opener unkillably (even root cannot reap) plus a ppbus outage for lpt/ppi/vpo. Every other ppbus consumer passes PPB_WAIT|PPB_INTR. FULLY VERIFIED on guest: custom PCFTEST kernel + isa-parallel, reproduced 2/2 as uid 1001 (D0 ppbreq survives kill -9), one-line fix validated on rebuilt kernel (immediate reap). vpoio.c:616 same bare-PPB_WAIT (routed).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5029 Β· 11 files
FileTypeDescriptionSize
pcf_dos.c β€” 2.4 KB view raw
run_test.sh β€” 1.5 KB view raw
build.sh β€” 222 B view raw
build.log β€” 122 B view raw
run.log β€” 1.1 KB view raw
run.2.log β€” 1.0 KB view raw
fix_run.log β€” 967 B view raw
fix_build.log β€” 660 B view raw
env.txt β€” 1.0 KB view raw
fix.diff β€” 342 B view raw
verdict.json β€” 4.2 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

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_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).
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Sep 26 05:45:18 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/PCFTEST x86_64

Confirmed kernel references

Detail

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 (decisive lines)

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.

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).

Verified recommended fix

Pass PPB_WAIT | PPB_INTR (like ppi.c/pps.c/if_plip.c) instead of bare PPB_WAIT in pcfclock_open().

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.