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

Non-terminating RX loop in iwn_notif_intr(): 12-bit firmware closed_count vs 64-slot ring

Summary

iwn_notif_intr() reads hw = closed_count & 0xfff (range 0..4095) and loops 'while (sc->rxq.cur != hw)' incrementing cur modulo IWN_RX_RING_COUNT=64. Any firmware value in [64,4095] can never equal cur, so the loop spins forever under the driver lock - a hard hang (DoS) with all in-loop indexing staying in bounds (liveness defect, not corruption). Termination analysis decidable from source ({cur mod 64} intersect [64,4095] = empty). Malicious/buggy firmware trust boundary. Untested (virtio-only guest); fix masks to ring size.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3581 Β· 5 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 1.8 KB ↓ raw
fix.diff β€” 586 B view raw
verdict.json β€” 2.1 KB view raw
manifest.json β€” 534 B view raw

DF-3581 β€” Unmasked firmware closed_count can wedge iwn_notif_intr() in an infinite loop

Root cause (source-verified)

sys/dev/netif/iwn/if_iwn.c:3900-4057 (iwn_notif_intr()):

hw = le16toh(sc->rxq.stat->closed_count) & 0xfff;    /* if_iwn.c:3900 */
while (sc->rxq.cur != hw) {                           /* if_iwn.c:3901 */
        ... process notification at sc->rxq.data[sc->rxq.cur] ...
        sc->rxq.cur = (sc->rxq.cur + 1) % IWN_RX_RING_COUNT;   /* 64 */
}

closed_count is a firmware-written DMA value masked to 12 bits (0..4095), but the RX ring has only IWN_RX_RING_COUNT == 64 (1<<6) descriptors (if_iwnreg.h:29-30) and sc->rxq.cur cycles modulo 64. Any firmware value in [64, 4095] can never equal cur, so the loop never terminates: the driver spins forever processing garbage "notifications" from the same 64 buffers, holding sc->sc_lk in the interrupt handler β€” the machine wedges (hard hang / soft lockup, no panic, no watchdog output because the thread never sleeps).

Impact

Firmware-triggered hard hang (DoS). The iwn firmware is the driver's stated trust boundary (same model as DF-1122); a firmware bug (register write truncation, DMA corruption of sc->rxq.stat) or hostile firmware value triggers it. No memory corruption: sc->rxq.cur stays in [0,63] and all array indexing inside the loop uses bounded indices β€” the defect is purely the non-terminating loop.

Status

untested β€” requires an iwn device whose firmware delivers an out-of-range closed_count; the QEMU audit guest is virtio-only. Source analysis only (the loop-termination arithmetic is decidable on paper: {cur mod 64} ∩ [64,4095] = βˆ…).

Fix

Mask the firmware value to the ring size and/or bound the iteration count:

hw = le16toh(sc->rxq.stat->closed_count) & (IWN_RX_RING_COUNT - 1);

(or bail out with a device reset when hw >= IWN_RX_RING_COUNT).

VERDICT.md
↓ download raw

DF-3581 β€” VERDICT

Status: untested. The trigger is a firmware-written closed_count outside [0,63]; the audit guest has no iwn hardware, so no runtime reproduction. The loop-termination analysis is decidable from source.

Analysis

  • sc->rxq.stat->closed_count is DMA-filled by the firmware; iwn_notif_intr() reads it as hw = le16toh(...) & 0xfff (if_iwn.c:3900) β€” range [0, 4095].
  • The RX ring holds IWN_RX_RING_COUNT = 64 buffers (if_iwnreg.h:29-30); sc->rxq.cur is maintained modulo 64 (if_iwn.c:4056).
  • Termination condition sc->rxq.cur != hw (if_iwn.c:3901): for hw in [64, 4095], cur (always < 64) can never equal hw β†’ the while-loop at 3901-4057 never exits.
  • The loop body dispatches desc->type handlers (iwn_rx_done etc.) with the lock held; none of them advance hw. The interrupt thread spins indefinitely β†’ system wedge (DoS), not a panic.

Inside the loop, indexing is bounded (data[sc->rxq.cur] with cur < 64), so this is a pure liveness defect β€” no OOB. Severity Low (firmware-triggered DoS), confidence likely (arithmetic certain; whether real firmware can emit such a value is unproven β€” 12-bit register semantics suggest hardware truncation is expected to keep it within the ring, which the driver should not rely on).

Related hardening: the same function trusts desc->len/desc->type from the buffers each iteration β€” covered by DF-1123/1124 family.

Fix

hw &= (IWN_RX_RING_COUNT - 1) or an explicit range check + device restart.

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 (ring-size mask); not validated β€” no runtime reproduction possible on this guest.

findings/poc/DF-3581/fix.diff
↓ fix.diffper-fix-DF-3581

Confirmed kernel references

Detail

Exploit chain

malicious/buggy firmware writes closed_count >= 64 into the RX status DMA block -> iwn_notif_intr spins forever with sc_lk held -> hard hang.

Evidence (decisive lines)

VERDICT.md (termination analysis), fix.diff (mask to ring size), README.md.

PoC changes

none β€” no PoC authored; trigger needs iwn hardware absent from the guest.

Verified recommended fix

Mask closed_count to (IWN_RX_RING_COUNT-1) at if_iwn.c:3900, or bail out and reset the device on out-of-range values.

Verdict

Non-terminating RX processing loop in iwn_notif_intr(): the firmware-written closed_count is masked to 12 bits (0..4095, if_iwn.c:3900) but rxq.cur cycles modulo IWN_RX_RING_COUNT=64 (if_iwn.c:4056); any firmware value in [64,4095] can never equal cur, so the while loop at if_iwn.c:3901-4057 spins forever under the driver lock, wedging the machine (DoS, no panic). All indexing inside the loop stays in range, so this is a liveness defect, not memory corruption. Untestable on the virtio-only guest (needs an iwn device whose firmware emits an out-of-range closed_count); termination analysis is decidable from source, hence likely.