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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5512 Β· 12 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.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.
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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