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

Firmware ring indices used unmasked: iwi_tx_intr/iwi_rx_intr termination depends on a raw CSR-read index; an out-of-range value β†’ non-terminating interrupt loop holding IWI_LOCK + wlan serializer (hard hang) and txq->queued int underflow

Summary

hw = CSR_READ_4(sc, txq->csr_ridx) and CSR_READ_4(IWI_CSR_RX_RIDX) are never masked into the ring domain, while the counters advance modulo IWI_TX/RX_RING_COUNT. If the firmware (or a wedged card - the driver itself expects fw desyncs per the watchdog) publishes hw outside [0,COUNT), the loops never terminate inside the interrupt handler with the lock held -> hard system hang; the tx loop also re-completes drained slots and underflows txq->queued. Hostile/buggy firmware or bus-error wedge on any iwi machine -> local hard hang. iwn(4) masks these; iwi does not. Mask-at-read fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3842 Β· 11 files
FileTypeDescriptionSize
README.md file 1.2 KB ↓ raw
VERDICT.md file 2.3 KB ↓ raw
build.log file 85 B view raw
build.sh file 82 B view raw
env.txt file 286 B view raw
fix.diff file 525 B view raw
manifest.json file 562 B view raw
run.log file 959 B view raw
run.sh file 90 B view raw
trigger.c file 1.6 KB view raw
verdict.json file 2.8 KB view raw
README.md file
↓ download raw

DF-3842 β€” iwi(4) trusts firmware ring indices without range validation: iwi_tx_intr() (if_iwi.c:1681-1694) and iwi_rx_intr() (if_iwi.c:1639-1668) compare a raw CSR-read index against a ring counter that is always taken modulo the ring size. An out-of-range index (hostile/buggy firmware or a wedged card) makes the loops non-terminating while IWI_LOCK and the wlan serializer are held -> hard system hang; the tx loop additionally re-completes already-completed slots (ni/m NULL, txq->queued int underflow).

WHAT TO LOOK AT trigger.c documentation harness with both loops quoted. fix.diff mask hw with % IWI_{TX,RX}_RING_COUNT at read time.

BUILD cc -O2 -o trigger trigger.c (compiles clean on the guest) RUN ./trigger (stub: hardware-gated path) EXPECT On hardware: wedge the card (e.g. rfkill toggle storm until a command timeout desynchronises the fw ring) or run hostile fw: the interrupt handler never returns; console dead; ping dies.

CALIBRATION Low / likely β€” needs fw/bus misbehaviour as the trigger, but the missing mask is unambiguous in source and every other driver in the tree masks or bounds-checks these reads.

VERDICT.md file
↓ download raw

DF-3842 VERDICT β€” status: inconclusive (hardware-gated), source-level: LIKELY

Source trace (if_iwi.c): iwi_tx_intr :1681 hw = CSR_READ_4(sc, txq->csr_ridx); / fw value / :1683 while (txq->next != hw) { :1684 data = &txq->data[txq->next]; / in-bounds / :1689 ieee80211_tx_complete(data->ni, data->m, 0); ieee80211_tx_complete() tolerates ni == NULL (sys/netproto/802_11/wlan/ieee80211_output.c:3680) and m_freem(NULL) is a no-op, so the second lap over the ring is "safe" per iteration β€” :1692 txq->queued--; / int underflow / :1693 txq->next = (txq->next + 1) % IWI_TX_RING_COUNT; (64) If hw >= 64 (or any value not in [0,64)), next cycles 0..63 forever and never equals hw: the interrupt handler spins holding IWI_LOCK and the wlan serializer -> hard hang (no timer, no second intr can make progress). iwi_rx_intr :1639-1667 has the identical pattern with IWI_RX_RING_COUNT (32) and additionally re-runs iwi_frame_intr over swapped mbufs each lap.

Preconditions: the firmware publishes an out-of-range read index β€” hostile fw, or a desynchronised/wedged card (the driver itself acknowledges fw desyncs: iwi_watchdog :2093 "device timeout" -> restart task). Reads of 0xffffffff from a vanished device are the classic source; note iwi_intr() guards only the INTR register against 0xffffffff (if_iwi.c:1733), not the ring index registers.

Why not reproduced: no iwi NIC on the audit guest (run.log). The hang is conditional on device misbehaviour, hence confidence "likely" rather than "certain" β€” the missing mask itself is unambiguous.

Severity: Low (DoS of the local system, preconditioned on fw/bus fault).

Fix: fix.diff β€” mask both index reads into the ring domain at read time. Fix validation: not possible on this guest β€” fix_status not_applicable.

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 validation impossible on this guest: the vulnerable device (AMD radeon CS / Intel 2200BG) does not exist, so neither baseline nor patched kernel can exercise the path. fix.diff authored against the read-only sys/ tree, never applied.

fix.diff in this pack; structurally mirrors the bounds checks the same driver family already enforces elsewhere (radeon_cs_packet_parse fit check; iwn ring-index masking).
↓ fix.diffper-fix-DF-3842

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (what to look at), VERDICT.md (line-by-line trace), trigger.c/mkbadfw.c, build.log (clean guest compile, gcc 8.3), run.log (honest ENOENT / generator RUN_OK), env.txt (guest has no DRI, no iwi NIC), fix.diff

PoC changes

PoC authored fresh for this finding (cross-file family seed): raw DRM ioctl ABI (cmd low byte 0x66 dispatches RADEON_CS on DF drm_ioctl.c:809), chunk structs and packet encodings verified against sys/dev/drm/include/uapi/drm/radeon_drm.h; iwi mkbadfw.c emits the v3 combined-image header per if_iwireg.h:161-166.

Verified recommended fix

Mask both ring index reads with % IWI_TX_RING_COUNT / % IWI_RX_RING_COUNT at read time.

Verdict

Likely (preconditioned on firmware/bus misbehaviour), runtime inconclusive (no iwi NIC on guest). hw = CSR_READ_4(csr_ridx) is never masked into [0,IWI_TX_RING_COUNT); the mod-64 counter can never equal an out-of-range hw, so the while-loop spins forever inside the interrupt handler with the lock held. ieee80211_tx_complete tolerates the NULL ni/m of repeat laps (ieee80211_output.c:3680) but txq->queued underflows and the system hangs. iwi_intr only guards the INTR register against 0xffffffff (:1733), not the ring registers. iwn(4) masks these reads; iwi does not.