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

Lost-wakeup race in ppb_request_bus() can permanently hang a process opening a parallel port device

Summary

ppb_request_bus() checks ppb_owner inside crit section then exits crit section BEFORE calling tsleep(). On SMP another CPU can call ppb_release_bus() (sets ppb_owner=0 calls wakeup(ppb)) in window between crit_exit() and tsleep(). Wakeup delivered to thread not yet slept so lost. Requesting thread calls tsleep with timo=0 (infinite) hangs forever. With PPB_WAIT|PPB_NOINTR uninterruptible permanent hang open() never returns. PPB_WAIT|PPB_NOINTR path not interruptible by signals kill -9 cannot terminate. [VERIFIED pass2: LIVE CAUGHT at trial 16070 - waiter asleep with ppb_owner NULL, wakeup lost in the crit-exit tsleep window, PPB_WAIT unkillable; tsleep_interlock PINTERLOCKED fix-validated 0 lost in 50000 trials]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2329 Β· 6 files
FileTypeDescriptionSize
ppbharness3.c β€” 13.1 KB view raw
Makefile β€” 484 B ↓ download
run.log β€” 1.6 KB view raw
fix.diff β€” 900 B view raw
verdict.json β€” 2.8 KB view raw
README.md β€” 1.4 KB ↓ raw

DF-2329 β€” lost-wakeup race in ppb_request_bus() permanently hangs a requester

What (sys/bus/ppbus/ppbconf.c:464-497 vs :508-540)

The requester checks ppb_owner inside a crit section, then EXITS the crit section BEFORE tsleep(). ppb_release_bus clears the owner and calls wakeup(ppb) (:538) with no interlock: a release landing between the requester's crit_exit() (:467) and tsleep() (:470/:474) is lost β€” the requester then sleeps on timo=0 with the PPB_WAIT|PPB_NOINTR variant being NON-INTERRUPTIBLE (no PCATCH: kill -9 cannot terminate).

Rig / Reproduce

Same ppbharness3 rig, test=3: an owner thread acquires (DONTWAIT), releases after a swept DELAY (0..399 us) while a waiter thread pinned to a second CPU (lwkt_migratecpu(1)) executes the real ppb_request_bus(PPB_WAIT|PPB_NOINTR); caught when the waiter stays asleep with ppb_owner == NULL.

Result (run.log)

hw.ppbx3.racetrials: 16071
hw.ppbx3.lostwake: 1
PPBX3: DF-2329 REPRODUCED at trial 16070 (delay 70 us): waiter
  STILL ASLEEP with PPB owner==NULL -- wakeup was lost;
  PPB_WAIT|PPB_NOINTR sleep is uninterruptible (kill-proof)

(The rig revived the waiter artificially; production has no reviver.)

Fix

fix.diff interlocks: tsleep_interlock(ppb, PCATCH) INSIDE the crit section + tsleep(... | PINTERLOCKED) after it. Validated on the patched rig (VERDICT.md): zero lost wakeups in 50k trials.

Fix verification

fixed
↓ fix.diffper-fix-DF-2329

Confirmed kernel references

Detail

Exploit chain

two threads on one ppbus -> owner releases between requester crit_exit (:467) and tsleep (:474) -> wakeup(ppb) delivered before the sleep is armed -> requester sleeps forever on a FREE bus -> PPB_WAIT|PPB_NOINTR: unkillable permanent hang

Evidence (decisive lines)

run.log: hw.ppbx3.lostwake = 1 at trial 16070/16071 with owner==NULL and the REPRODUCED console line; control attach + DF-2327/2328 stock panics on the same rig prove the production code is what executes

PoC changes

Authored the race test in ppbharness3; initial 0/44894 catch rate was a rig flaw (both kthreads on cpu 0: DELAY starved the waiter) -- fixed with lwkt_migratecpu(1) in the waiter; fast-poll drain added so 50k trials run in ~30 s.

Verified recommended fix

tsleep_interlock(ppb, PCATCH) inside the crit section + tsleep(...|PINTERLOCKED) (fix.diff, the sgread pattern).

Verdict

REPRODUCED live: the ppbharness3 race test runs the REAL ppb_request_bus(PPB_WAIT|PPB_NOINTR) on a waiter thread pinned to a second CPU while an owner thread releases inside the crit_exit-to-tsleep window (delay sweep 0..399 us): caught at trial 16070 -- the waiter was STILL ASLEEP with ppb_owner == NULL (the wakeup was lost; timo=0; the NOINTR variant has no PCATCH so the sleep is unkillable). The rig revived the waiter with an artificial wakeup; production has none, so the hang is permanent. The window is exactly the missing-interlock ordering at ppbconf.c:464-497 vs ppb_release_bus :538. Confidence likely rather than certain only because the catch is a scheduling race (1 in ~16 k trials here) while the code-ordering proof is certain.