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

Four unbounded MMUCR_BUSY busy-waits (two in hard interrupt context) plus no-exit device-driven loops: unrecoverable system hang on SMC91xx glitch or PCMCIA ejection

Summary

The driver spins on the on-chip MMU engine with ZERO timeout at four sites - two of them in hard interrupt context, one executed for every received frame. On a glitching card or after physical ejection (floating port reads keep BUSY asserted), the spins never terminate while holding the ifnet serializer in interrupt context - total system hang, power-cycle required; pccard detach cannot recover (needs the same serializer). The driver demonstrably knows the bounded idiom (its allocation wait is capped). Bound-all-waits fix using the driver's own MEMORY_WAIT_TIME.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4355 Β· 6 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 4.7 KB ↓ raw
fix.diff β€” 2.1 KB view raw
env.txt β€” 818 B view raw
verdict.json β€” 3.3 KB view raw
manifest.json β€” 1.2 KB view raw

DF-4355 β€” Unbounded MMU busy-waits and device-fault infinite loops: unrecoverable system hang on SMC91xx fault/removal

File: sys/dev/netif/sn/if_sn.c Verdict status: untested β€” hardware-gated (SMC91xx ISA/PCMCIA NIC absent from the QEMU guest). Source-level certainty: certain. Class: CWE-835 (loop with unreachable exit condition, kernel context) β†’ local DoS (hard hang requiring power cycle).

Where (all sites)

Unbounded while (inw(BASE + MMU_CMD_REG_W) & MMUCR_BUSY); spins β€” no timeout, no bail-out:

Site Context Locks held while spinning
if_sn.c:275-276 sninit() MMU reset wait ifnet serializer (ioctl/watchdog)
if_sn.c:641-642 snresume() wrong-size alloc release ifnet serializer (hard IRQ)
if_sn.c:871-872 sn_intr() TX-error MMUCR_FREEPKT ifnet serializer (hard IRQ)
if_sn.c:1056-1057 snread() packet release, per packet ifnet serializer (hard IRQ)

Related no-exit device-driven loops in the same paths:

  • if_sn.c:354+446-450: snstart() ARR_FAILED β†’ ifq_prepend β†’ goto startagain β€” infinite re-dequeue/re-alloc retry at serializer context with a card whose allocation always fails.
  • if_sn.c:1063-1067: snread() read_another walk β€” loops as long as the card keeps FIFO_PORTS non-empty (FIFO_REMPTY never set); a wedged card (or one whose I/O floats after PCMCIA ejection) pins the RX interrupt forever.
  • if_sn.c:415-419: bounded (MEMORY_WAIT_TIME 1000) β€” OK by contrast; shows the author knew how to bound waits but did not apply it to the above.

Trigger

  • Physical ejection of the PCMCIA card while the interface is up (mid-TX or mid-RX): on pccard, port reads after removal float (typically 0xFFFF) β†’ MMUCR_BUSY (0x0001) reads as set forever β†’ the spin at 871/1056 (hard-interrupt context, serializer held) never exits. Machine is dead; only a hard reset recovers.
  • A glitching/failing SMC91xx that never clears MMUCR_BUSY or never empties the RX FIFO produces the same hang from a transmit error interrupt.
  • sn_pccard_detach() cannot save you: it must acquire the same ifnet serializer the spinning interrupt holds (cf. DF-2186 in if_sn_pccard.c) β€” the detach path also deadlocks.

Impact

Permanent kernel hang (all-contexts DoS) on machines with the hardware. Requires physical presence (card yank) or faulty hardware β€” hence Low severity, but certain at source level, and a well-known bug class the project treats as fileable for hardware-gated drivers.

Fix (sketch β€” bounded wait helper)

--- a/sys/dev/netif/sn/if_sn.c
+++ b/sys/dev/netif/sn/if_sn.c
@@ -272,8 +272,13 @@ sninit(void *xsc)
    SMC_SELECT_BANK(2);
    outw(BASE + MMU_CMD_REG_W, MMUCR_RESET);
-   while (inw(BASE + MMU_CMD_REG_W) & MMUCR_BUSY)  /* NOTHING */
-       ;
+   {
+       int t = MEMORY_WAIT_TIME * 1000;
+       while ((inw(BASE + MMU_CMD_REG_W) & MMUCR_BUSY) && --t)
+           DELAY(1);
+       if (t == 0) {
+           device_printf(sc->dev, "MMU busy timeout\n");
+           return; /* leave interface down; watchdog retries */
+       }
+   }

(the same bounded-wait pattern must replace the spins at if_sn.c:641-642, 871-872 and 1056-1057, and the read_another/startagain loops need iteration caps).

Reproduce

Not runnable on the audit guest (no PCMCIA/SMC91xx hardware; driver is optional sn module, pccard front-end only β€” see env.txt). Source-level proof in VERDICT.md.

VERDICT.md
↓ download raw

DF-4355 β€” VERDICT

Status: untested (hardware-gated) Β· Source-level confidence: certain Β· Expected on real hardware: unrecoverable system hang (interrupt-context / serializer-held infinite spin).

Root cause (path:line)

The SMC91xx driver waits for the on-chip MMU engine and the RX FIFO with completely unbounded busy-loops:

  1. sys/dev/netif/sn/if_sn.c:275-276 β€” sninit(): while (inw(BASE + MMU_CMD_REG_W) & MMUCR_BUSY); after MMUCR_RESET. Runs under the ifnet serializer (SIOCSIFFLAGS β†’ sninit, if_sn.c:1090; watchdog β†’ sn_intr β†’ EPH path β†’ sninit, if_sn.c:924-927).
  2. sys/dev/netif/sn/if_sn.c:641-642 β€” snresume() wrong-size-allocation release path.
  3. sys/dev/netif/sn/if_sn.c:871-872 β€” sn_intr() TX-error path before MMUCR_FREEPKT. Hard interrupt context.
  4. sys/dev/netif/sn/if_sn.c:1056-1057 β€” snread() per-packet release before MMUCR_RELEASE. Hard interrupt context, inside the read_another loop, so it executes for every received frame.

MMUCR_BUSY is 0x0001 (sys/dev/netif/sn/if_snreg.h:238). On a card that is ejected mid-operation, pccard bus reads float (conventionally 0xFFFF), so inw() & 0x0001 is persistently true: all four sites spin forever. Sites 3-4 spin in a hard interrupt handler holding the ifnet serializer β€” nothing else on the system can make progress; the serial-console softclock path that would normally print a hang watchdog is starved of the interrupt β†’ total hang, power-cycle required. The sn_pccard_detach() path cannot quiesce the driver because it needs the same serializer (see known DF-2186, sys/dev/netif/sn/if_sn_pccard.c:110-121) β€” detach itself deadlocks against the spinner.

Additional device-driven no-exit loops in the same fault model:

  • sys/dev/netif/sn/if_sn.c:446-450 β€” ARR_FAILED β†’ ifq_prepend() β†’ goto startagain (354): with a card whose MMU allocation fails immediately and forever, snstart() re-dequeues the same mbuf and re-issues the allocation in a tight loop at serializer context.
  • sys/dev/netif/sn/if_sn.c:1063-1067 β€” read_another: no cap on the number of packets drained per interrupt; a card that keeps FIFO_REMPTY (0x8000) clear pins the RX interrupt handler indefinitely.

Contrast: the allocation wait at if_sn.c:415-419 is bounded (MEMORY_WAIT_TIME = 1000, if_snreg.h:60) β€” the unbounded sites are an oversight, not a style choice.

Threat model

Local attacker with physical access to a machine using an SMC91xx PCMCIA/ISA NIC (or a failing card): eject the card while the interface is up and transmitting/receiving β†’ hang. There is no unprivileged-software-only trigger: the loops only spin when the hardware misbehaves or vanishes. Severity Low (hardware-gated DoS), confidence certain at source level.

Why untested on this guest

Same hardware gate as DF-4354: the QEMU guest has no PCMCIA slot/SMC91xx chip; if_sn is an optional sn module with a pccard-only front-end (sys/conf/files:1247, sys/dev/netif/sn/if_sn_pccard.c:126-133). Guest inventory captured in env.txt (only vtnet0/lo0). No guest-side reproduction is possible without emulating the card; classification is untested-with-source-certainty rather than not-reproduced (nothing was executed and failed β€” the path simply cannot exist on this machine).

Fix validation

fix_status: not_testable (same hardware gate). The fix.diff pattern (bounded MEMORY_WAIT_TIME*1000 spin with DELAY(1) and bail-out leaving the interface for the watchdog) is the same approach the driver already uses at if_sn.c:415-419; applying it at the four MMU sites plus iteration caps on the FIFO/ARR loops removes every unbounded spin. By inspection the patched loops always terminate within ~1s per wait on both healthy and vanished hardware.

Kernel references

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

not_testable: fix validation requires SMC91xx hardware (guest has none). fix.diff applies bounded waits at all four MMU sites using the driver's own MEMORY_WAIT_TIME idiom; by inspection every patched loop terminates within ~1s per wait on both healthy and vanished hardware, converting the hang into a logged timeout.

findings/poc/DF-4355/fix.diff (4 hunks); env.txt (hardware absent).
↓ fix.diffper-fix-DF-4355

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (site-by-site table with lock context), README.md (trigger model + bounded-wait fix sketch), env.txt (guest lacks hardware), fix.diff (bounded waits at all four MMU sites). Decisive lines: if_sn.c:275, 641, 871, 1056 (unbounded spins), if_sn.c:415-419 (bounded counter-example), if_snreg.h:238/60.

PoC changes

No executable PoC exists: path requires SMC91xx PCMCIA hardware (card ejection mid-transfer) absent from the guest. Evidence pack is source-level (VERDICT.md) plus guest hardware-inventory proof (env.txt).

Verified recommended fix

Bound all four MMUCR_BUSY waits with a MEMORY_WAIT_TIME-scaled counter + DELAY(1) and bail out leaving the interface for the watchdog, mirroring the existing bounded wait at if_sn.c:415-419; cap the read_another/ARR_FAILED loops.

Verdict

The SMC91xx driver waits on the on-chip MMU engine with four completely unbounded busy-loops - if_sn.c:275-276 (sninit), 641-642 (snresume), 871-872 (sn_intr TX-error, hard IRQ context), 1056-1057 (snread, hard IRQ, per packet) - plus two device-driven no-exit loops (ARR_FAILED retry at 446-450->354; read_another FIFO walk at 1063-1067). On a glitching card or after physical PCMCIA ejection (floating port reads keep MMUCR_BUSY=0x0001 asserted), the spins never terminate while holding the ifnet serializer in interrupt context: the machine hangs and only a power cycle recovers. Source-level certainty is high (the same driver bounds its allocation wait at 415-419 with MEMORY_WAIT_TIME=1000, showing the omission is an oversight), but reproduction requires SMC91xx hardware which the QEMU guest lacks -> untested. Local DoS only, physical-presence/failing-hardware trigger, severity Low.