ppbus_setup_intr() accepts overlapping interrupt registrations and overwrites its cookie bookkeeping: forced unregistration on bus release orphans live handlers
Summary
Only bus ownership is checked before unconditionally overwriting intr_cookie/intr_resource - the exact bookkeeping release uses to force-unregister: a child registering twice (reachable via lpt's signal-interrupted-write re-register path) orphans the first handler, which stays live and untracked, accumulating per cycle. Rig: setup#2 err=0 cookie overwritten, after release still-live=1 while ivars NULL (4/4); EBUSY-on-over-registration fix -> 0 still-live, lpt degrades safely to polled. Hardware-gated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5512 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| Makefile | β | 479 B | β download | |
| README.md | β | 1010 B | β raw | |
| VERDICT.md | β | 1.7 KB | β raw | |
| build.sh | β | 74 B | view raw | |
| env.txt | β | 456 B | view raw | |
| fix.diff | β | 2.2 KB | view raw | |
| fix_build.log | β | 8.8 KB | view raw | |
| fix_run.log | β | 578 B | view raw | |
| harness.c | β | 21.5 KB | view raw | |
| run.log | β | 714 B | view raw | |
| run.sh | β | 98 B | view raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-5512 -- ppbus_setup_intr() overlapping registrations corrupt forced-teardown bookkeeping
What
ppbconf.c:426-427 unconditionally overwrite ppbdev->intr_cookie/intr_resource; a second registration (reachable via lpt.c write-error paths that keep the bus) orphans the first handler -- ppb_release_bus():514-521 can then only tear down the last one.
Build / run / expected (inside the DragonFly guest)
build (guest): sh build.sh run (guest): sh run.sh # deterministic F3 section expected pristine: "setup#2 err=0 ck2=0x1001 ivars.cookie=0x1001" then "after ppb_release_bus: parent setups=2 teardowns=1 still-live=1 ... REPRODUCED" expected patched : "setup#2 err=16 (EBUSY) ... still-live=0"
Rig
ppbharness2.ko compiles the VERBATIM production sources
/usr/src/sys/bus/ppbus/{ppbconf.c,ppb_1284.c,ppb_base.c}; a fake "ppc"
controller on nexus provides a fully scripted (malicious) IEEE1284 peer.
Details in VERDICT.md; shared rig sources in the DF-5510 pack (identical
copies here).
DF-5512 VERDICT
ppbus_setup_intr() overlapping registrations corrupt forced-teardown bookkeeping
REPRODUCED (rig) 4/4 fresh-boot runs (deterministic test)
The rig registers the same child handler twice while it owns the bus -- the exact sequence lpt.c produces after an interrupted lptwrite (lpt.c:745 takes the bus and registers lpt_intr; error returns at :778/:784/:799/:810 keep the bus and the handler; the next lptwrite re-registers because HAVEBUS short-circuits the request). ppc.c's setup_intr layer chains handlers (shared-IRQ support), so the duplicate succeeds.
Observed: ppbus ivars hold only cookie #2 after the overwrite; on ppb_release_bus() the forced teardown removes exactly one registration; ONE handler remains live at the parent bus while ppbus bookkeeping says none is registered -- the bus layer's force-unregistration guarantee is void and each interrupted-write cycle adds another permanent handler (every port ACK then runs lpt_intr repeatedly -> strobed byte-stream corruption; orphans persist until driver detach).
FIX VALIDATED: setup #2 returns EBUSY(16) with the original cookie kept; release tears the single registration down (still-live=0). lpt degrades safely to polled mode on EBUSY (lpt.c:754-757).
-- status=reproduced reproduced=True impact=none confidence=certain fix_status=fixed (baseline reproduced, patched behavior gone)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedfix.diff applied to the guest's /usr/src/sys/bus/ppbus/ppbconf.c, harness rebuilt from the same verbatim source (module-level rebuild; running kernel unchanged), full rig re-run on a fresh boot: the previously observed bad behavior is GONE (setup#2 returns EBUSY and exactly one registration remains, which release tears down (still-live=0), verified on two patched boots). Behavior of the sibling known findings (DF-1093/2327/2328/2329) was not targeted by this diff.
fix_run.log / fixrun2.dmesg (patched) vs run.log / run4.dmesg (pristine) in this pack; fix.diff is the exact applied diff.
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log ('DF-5512 setup#2 err=0 ... after ppb_release_bus: parent setups=2 teardowns=1 still-live=1 ... REPRODUCED'); fix_run.log ('setup#2 err=16 ... still-live=0'); harness.c hx_f3_intr_test; fix.diff hunk 2.
PoC changes
No seed PoC existed (pass-2 finding); rig written from scratch following the DF-5418/5419 ppbharness class: verbatim-source KLD (ppbconf.c+ppb_1284.c+ppb_base.c), scripted IEEE1284 nibble-mode peer, fake ppc controller on nexus, real ppbus driver attach. Bring-up fixes: comment typo; the stack pre-dirtier initially collapsed to a single frame via -O2 tail-call optimization (made noinline with a post-call touch).
Verified recommended fix
Refuse over-registration: if (ppbdev->intr_cookie != NULL) return (EBUSY) in ppbus_setup_intr (fix.diff hunk 2).
Verdict
REPRODUCED 4/4 pristine runs (deterministic rig test). ppbus_setup_intr() only checks bus ownership (ppbconf.c:416) and then unconditionally overwrites ppbdev->intr_cookie/intr_resource (:426-427) -- the very bookkeeping ppb_release_bus() relies on to force-unregister the child's handler at :514-521. A child that registers twice therefore orphans its first handler: the rig performs two BUS_SETUP_INTR calls on an owning child -- exactly what lpt.c's write-error paths produce (lpt.c:745-815 returns from lptwrite holding the bus with the handler registered; the next write re-registers) -- and the fake parent records 2 registrations while ppbus ivars hold only cookie #2; after ppb_release_bus() exactly one teardown happens and ONE handler remains live at the parent bus while ppbus believes none is registered (ivars cookie/resource NULL). Ceiling: handler count grows once per interrupted-write/print cycle (each parallel-port ACK then runs lpt_intr multiple times -> strobed byte-stream corruption) and the orphans persist until driver detach; requires an IRQ-mode parallel port, so hardware-gated. Fix validated: setup #2 now returns EBUSY(16), ppb_release_bus tears the single registration down (still-live=0), and lpt degrades safely to polled mode on EBUSY (lpt.c:754-757).
No comments yet.