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)
PoC verification
Evidence pack
findings/poc/DF-3581 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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_countis DMA-filled by the firmware;iwn_notif_intr()reads it ashw = le16toh(...) & 0xfff(if_iwn.c:3900) β range [0, 4095].- The RX ring holds
IWN_RX_RING_COUNT = 64buffers (if_iwnreg.h:29-30);sc->rxq.curis 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 equalhwβ the while-loop at 3901-4057 never exits. - The loop body dispatches
desc->typehandlers (iwn_rx_done etc.) with the lock held; none of them advancehw. 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_testablefix.diff authored (ring-size mask); not validated β no runtime reproduction possible on this guest.
findings/poc/DF-3581/fix.diff
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.
No comments yet.