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

Unbounded device-register polling under BWN_LOCK in bwn_intr_pmq and bwn_core_start: permanent unkillable wlan-subsystem wedge

Summary

bwn_intr_pmq and bwn_core_start poll device registers in while(1) with no retry bound while holding sc_lk exclusive. A stalled/failing card (or malicious one freezing PS_STATUS/XMITSTAT) spins the bwn taskq thread forever with the lock held: every later bwn ioctl/start/stop/detach blocks unkillably in lockmgr and the watchdog callout (which shares sc_lk) dies. Every other polling loop in the driver is bounded - these two are the outliers. Permanent soft-hang DoS of the wlan subsystem on stalled hardware; not remote; requires device misbehavior. Source-certain; guest hardware-absent.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3596 Β· 7 files
FileTypeDescriptionSize
README.md β€” 774 B ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
probe.sh β€” 838 B view raw
run.log β€” 581 B view raw
fix.diff β€” 973 B view raw
manifest.json β€” 929 B view raw
verdict.json β€” 1.9 KB view raw

DF-3596 β€” Unbounded device-register polling loops under BWN_LOCK (bwn_intr_pmq, bwn_core_start): permanent lock-held wedge on stalled device

Build / Run / Expected

# on the DF guest, as root:
sh probe.sh

Guest result: driver resident, no Broadcom hardware β†’ runtime inconclusive. On a BCM43xx machine with a wedged/failing card (or malicious card that simply stops updating the PS_STATUS / XMITSTAT registers): the next PMQ interrupt or core-start spins forever reading the frozen register while holding sc_lk exclusive β€” the bwn taskq thread pegs a CPU, every subsequent ioctl/start/stop on the interface blocks forever in lockmgr (unkillable), and the device can no longer be reset or detached. System-wide soft hang of the wlan subsystem.

VERDICT.md
↓ download raw

VERDICT β€” DF-3596

Status: inconclusive at runtime (no bwn hardware on the audit guest); defect source-certain. Impact: DoS β€” unkillable lock-held kernel spin (wlan subsystem wedge) on stalled device.

Root cause (path:line)

Two while (1) loops poll device registers with no iteration bound and no timeout, both executing with BWN_LOCK held (sc_lk, lockmgr exclusive β€” if_bwnvar.h:1030-1035):

  1. bwn_intr_pmq() β€” sys/dev/netif/bwn/bwn/if_bwn.c:5168-5178: c while (1) { tmp = BWN_READ_4(mac, BWN_PS_STATUS); if (!(tmp & 0x00000008)) break; } Called from bwn_intrtask (:4995-4996) on every BWN_INTR_PMQ (part of BWN_INTR_MASKTEMPLATE, if_bwnreg.h:326), i.e. straight from the device's power-management-queue interrupt, under the lock taken at :4954.
  2. bwn_core_start() β€” if_bwn.c:2234-2239: c while (1) { tmp = BWN_READ_4(mac, BWN_XMITSTAT_0); if (!(tmp & 0x00000001)) break; tmp = BWN_READ_4(mac, BWN_XMITSTAT_1); } Reached from bwn_init/bwn_hwreset/bwn_switch_band with the lock held.

Every other polling loop in this driver is bounded (bwn_dma_tx_reset 10 iterations :3307, bwn_mac_suspend 35+40 :3836-3847, bwn_psctl 100 :3898, PIO rxeof 10 :5567, ucode-timeout loop 21 :4263) β€” these two are the outliers, and the upstream Linux b43 equivalent (b43_pio_tx / ssb-wait helpers) uses bounded waits.

Impact when a card stalls (failing hardware is the classic trigger; a malicious mini-PCIe card can freeze the register deliberately): the loop never exits; the bwn taskq thread spins at 100% holding sc_lk forever. Every later ifconfig bwn0 …, TX wakeup (bwn_start), detach, or suspend blocks unkillably on the lock; the watchdog callout (sc_watchdog_ch shares sc_lk, if_bwn.c:844) also blocks, so even the device-timeout printer dies. Not a panic β€” a permanent, unkillable subsystem wedge.

Why runtime reproduction was not possible

Same as the sibling findings: the audit guest has no Broadcom 802.11 device (run.log); the loop bodies are unreachable. A qemu-specific device model would be required to emulate a stalling bwn card.

Fix

Bound both loops with a retry budget and bail out with a device_printf on timeout β€” see fix.diff.

Artifacts

  • probe.sh, run.log β€” guest probe (driver residency + hardware absence)
  • fix.diff β€” proposed patch vs read-only sys/ tree (never applied)

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

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored against read-only sys/; cannot baseline-validate without BCM43xx hardware

fix.diff in this pack
↓ fix.diffper-fix-DF-3596

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md loop-by-loop analysis with bounded-loop contrast; run.log guest probe; fix.diff adds retry budgets + timeout messages

PoC changes

No runnable PoC possible without hardware; probe.sh substitutes

Verified recommended fix

Bound both register-drain loops with retry budgets and timeout diagnostics

Verdict

Source-certain unbounded polling: bwn_intr_pmq (if_bwn.c:5172-5176) and bwn_core_start (if_bwn.c:2234-2239) spin on device registers with no retry bound while holding sc_lk (lockmgr exclusive); a stalled/failing card (or malicious mini-PCIe device) freezes the thread with the lock held, wedging every later bwn ioctl/start/stop/detach and the watchdog callout permanently and unkillably. All other polling loops in the driver are bounded (e.g. :3307, :3836, :3898, :5567) β€” these two are the outliers. Guest has no Broadcom device (run.log), so the loop bodies are unreachable at runtime.