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

ppb_release_bus() saves the device context after clearing ppb_owner and outside crit: cross-device chipset-mode context corruption race

Summary

The ctx snapshot happens after the ownership handoff and outside crit - a second child acquiring and set_mode'ing in that window has ITS mode written into the releasing device's context, later restored wrong for the first. Rig: 50M-iteration 2-thread race on 6 vCPUs, wrong-captures 2/1/1/6 across 4 boots; save-under-crit-before-clear fix -> 0/50M. Hardware-gated privileged; wrong-protocol transfers/corruption, no memory safety.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5511 Β· 12 files
FileTypeDescriptionSize
Makefile β€” 479 B ↓ download
README.md β€” 1012 B ↓ raw
VERDICT.md β€” 1.8 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 β€” 367 B view raw
harness.c β€” 21.5 KB view raw
run.log β€” 460 B view raw
run.sh β€” 98 B view raw
verdict.json β€” 3.8 KB view raw

DF-5511 -- ppb_release_bus() ctx-save race: context snapshotted after owner clear

What

ppbconf.c:528 clears ppb_owner, then :532 reads ppb_get_mode() into ppbdev->ctx.mode outside any crit section; a competing child acquiring the bus in the window gets its mode saved into the wrong device's context, which ppb_request_bus():491-492 later restores.

Build / run / expected (inside the DragonFly guest)

build (guest): sh build.sh
run   (guest): sh run.sh   # F2 phase runs 10 s
expected pristine: "DF-5511 releaser iterations=50000000 ... WRONG-mode-captured(B's EPP in A's ctx)=N -> REPRODUCED" (N = 2/1/1/6 across runs)
expected patched : "... WRONG-mode-captured ... =0 -> race not hit this run"

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-5511 VERDICT

ppb_release_bus() ctx-save race: context snapshotted after owner clear

REPRODUCED (rig) 4/4 fresh-boot runs

Deterministic attribution: only device A's own ppb_release_bus() writes A's ppbdev->ctx.mode, and the releaser thread checks it immediately after its release call returns; a capture of B's mode (PPB_EPP) can only have been read from ppb->mode AFTER B interposed between A's owner-clear (:528) and A's get_mode (:532).

Hits: 2, 1, 1, 6 wrong captures per 50M release iterations on the 6-vCPU guest (window is a handful of instructions).

Impact ceiling: the corrupted context is restored at A's next ppb_request_bus (:491-492), so device A afterwards operates the shared port in device B's IEEE1284 chipset mode (e.g. an lpt printer driven in EPP/ECP framing, or pcfclock's bit-banged protocol running in the wrong mode) -> data corruption / hung transfers on the parallel port. No memory-safety consequence. All ppbus children (lpt, ppi, pcfclock) share one bus, but their device nodes are 0600 root:wheel by default, so this needs privileged (or physically local) access plus real hardware.

FIX VALIDATED: saving ctx.{mode,valid} inside the crit section before clearing the owner yields 0 wrong captures in 50M iterations on two patched boots (fixrun1/fixrun2), vs 1-6 per run on pristine.

-- 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 (0 wrong captures in 50M iterations, 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-5511 releaser ... WRONG-mode-captured(B's EPP in A's ctx)=6 -> REPRODUCED' on the pristine decisive run); fix_run.log ('=0' on patched); harness.c hx_f2_releaser/hx_f2_stealer; fix.diff hunk 4.

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

Save ppbdev->ctx.{mode,valid} inside the crit section BEFORE clearing ppb_owner (fix.diff hunk 4).

Verdict

REPRODUCED 4/4 pristine runs. ppb_release_bus() clears ppb->ppb_owner at ppbconf.c:528 and only then reads ppb_get_mode() into ppbdev->ctx.mode at :532 -- outside any critical section -- so a second ppbus child (lpt/ppi/pcfclock all attach to the same bus) that acquires the bus and calls ppb_set_mode() inside that window has ITS mode snapshotted into the releasing device's context; ppb_request_bus() later restores that wrong mode for the first device (:491-492). Rig: releaser thread (device A, mode PS2) and stealer thread (device B, mode EPP) race request/set_mode/release for 50M iterations on the 6-vCPU guest: 2/1/1/6 wrong-mode captures across the four runs (A's ctx.mode == B's EPP), every capture provably written by A's own release call (no other writer of A's ctx). Impact ceiling: cross-device chipset-mode corruption on the shared port (wrong IEEE1284 protocol restored for lpt/pcfclock/ppi transfers -> printer data corruption / hung transfers), no memory-safety consequence; device nodes are 0600 root:wheel by default and the port is hardware-gated. Fix validated: with ctx saved under crit while still owner, 0 wrong captures in 50M iterations on two patched runs.