NULL-deref panic in iwn_ampdu_tx_done(): TX_DONE on an aggregation queue with no bound session (sc->qid2tap[qid]==NULL)
Summary
iwn4965/5000_tx_done route any firmware TX_DONE whose qid >= firstaggqueue into iwn_ampdu_tx_done, which immediately dereferences sc->qid2tap[qid] (tap->txa_tid, tap->txa_ni). qid2tap[qid] is NULL outside an active TX aggregation session (cleared at teardown), so a stale or forged TX_DONE for an aggregation queue NULL-derefs -> kernel panic. Sibling of DF-1122 in the TX_DONE path, which pass 1 did not cover. Malicious/compromised firmware (the driver's stated trust boundary) or a narrow teardown race. Untested (needs device+hostile fw); source-certain analysis + fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3579 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 2.2 KB | β raw | |
| fix.diff | β | 505 B | view raw | |
| verdict.json | β | 2.3 KB | view raw | |
| manifest.json | β | 549 B | view raw |
DF-3579 β NULL-deref panic in TX_DONE path: iwn_ampdu_tx_done() dereferences sc->qid2tap[qid] without NULL check
Root cause (source-verified)
iwn4965_tx_done() / iwn5000_tx_done() dispatch on a firmware-supplied
queue id:
qid = desc->qid & 0xf; /* if_iwn.c:3587 / 3617 */
...
if (qid >= sc->firstaggqueue) {
iwn_ampdu_tx_done(sc, qid, desc->idx, stat->nframes,
stat->ackfailcnt, &stat->status); /* if_iwn.c:3601 / 3636 */
iwn_ampdu_tx_done() then does:
tap = sc->qid2tap[qid]; /* if_iwn.c:3765 */
tid = tap->txa_tid; /* if_iwn.c:3766 β NULL deref */
wn = (void *)tap->txa_ni; /* if_iwn.c:3767 */
sc->qid2tap[qid] is non-NULL only while a TX aggregation session is bound
to that queue (set in iwn_addba_request, if_iwn.c:7440; cleared in
iwn_addba_response failure path if_iwn.c:7463, iwn_ampdu_tx_stop
if_iwn.c:7532, and from iwn_rx_compressed_ba/iwn_ampdu_tx_done
themselves at if_iwn.c:3303/3874). Nothing re-validates the pointer when a
TX_DONE for an aggregation queue arrives after the session was torn down
(or if the firmware emits TX_DONE for a queue the driver never aggregated).
Same family as DF-1122 (Compressed-BA path), but in the TX_DONE path, which pass 1 did not cover.
Impact
Kernel NULL-deref panic. Attacker = malicious/buggy firmware (the driver's
explicit trust boundary per the audit threat model), or a narrow teardown
race where the firmware emits a late TX_DONE after
iwn_ampdu_tx_stop() clears qid2tap[qid] (that path requires
txq[qid].queued == 0, so in-flight completions are normally exhausted β
hence likely, not certain, for benign firmware).
Status
untested β requires an iwn device plus malicious/compromised firmware or a won teardown race; the QEMU audit guest is virtio-only (no Intel WiFi PCI device). Source-level analysis only. No PoC authored (a userland model of a NULL deref proves nothing beyond the source).
Fix (sketch β see fix.diff)
tap = sc->qid2tap[qid];
if (tap == NULL)
return; /* stale/unsolicited TX_DONE for agg queue */
DF-3579 β VERDICT
Status: untested. No runtime reproduction possible: the bug needs an iwn device and either malicious firmware or an aggregation-teardown race; the audit guest is virtio-only with no Intel WiFi hardware.
Analysis (source-certain, trigger-speculative)
The dispatch chain is:
- iwn_notif_intr() (if_iwn.c:3934-3937) β ops->tx_done
- iwn4965_tx_done() if_iwn.c:3587-3606 / iwn5000_tx_done() if_iwn.c:3617-3641:
qid = desc->qid & 0xf (bounded 0..15 β in range for both 16- and
20-queue variants, so this index is safe), then for
qid >= sc->firstaggqueue β iwn_ampdu_tx_done(sc, qid, ...).
- iwn_ampdu_tx_done() if_iwn.c:3765-3768 dereferences sc->qid2tap[qid]
(tap->txa_tid, tap->txa_ni) with no NULL check.
qid2tap[qid] lifetimes: set only in iwn_addba_request (if_iwn.c:7440);
cleared in iwn_addba_response failure (7463), iwn_ampdu_tx_stop (7532),
iwn_rx_compressed_ba (3303) and iwn_ampdu_tx_done (3874) β after which
the queue is a plain queue again, yet a firmware TX_DONE stamped with that
qid (>= firstaggqueue) would still be routed into iwn_ampdu_tx_done and
NULL-deref. With benign firmware this requires a stale completion racing the
teardown (the stop path waits for queued == 0, so the window is narrow);
with hostile/compromised firmware (the driver's stated trust boundary, same
model as DF-1122) it is a trivially forgeable notification.
Consequences: NULL pointer read at tap->txa_tid β kernel panic (DoS).
No memory-corruption primitive (the deref is a read of a NULL-adjacent
fixed offset).
Confidence: likely (code path certain; trigger needs malicious firmware or a race that benign firmware makes unlikely).
Recommendation
Apply fix.diff (bound + NULL check at the top of iwn_ampdu_tx_done). The
same guard pattern should be applied to iwn_rx_compressed_ba as part of
the DF-1122 fix.
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 (bound + NULL guard) but not validated: no runtime reproduction exists on this guest (no iwn hardware).
findings/poc/DF-3579/fix.diff
Confirmed kernel references
Detail
Exploit chain
malicious/buggy firmware emits TX_DONE with qid in [firstaggqueue, 15] while no aggregation session is bound -> iwn_ampdu_tx_done reads tap->txa_tid through NULL -> kernel panic (DoS).
Evidence (decisive lines)
VERDICT.md (full source trace), fix.diff (guard), README.md.
PoC changes
none β no PoC authored; a userland model of a NULL deref adds nothing over the source trace, and the in-kernel path needs absent hardware.
Verified recommended fix
Add 'if (qid >= sc->ntxqs || sc->qid2tap[qid] == NULL) return;' at the top of iwn_ampdu_tx_done (fix.diff).
Verdict
NULL-deref in iwn_ampdu_tx_done(): iwn4965_tx_done/iwn5000_tx_done route any TX_DONE whose firmware qid >= firstaggqueue into iwn_ampdu_tx_done, which dereferences sc->qid2tap[qid] (if_iwn.c:3765-3768) without a NULL check; the pointer is NULL outside an active TX aggregation session (cleared on teardown at if_iwn.c:7463/7532/3303/3874). A stale or forged TX_DONE for an aggregation queue panics the kernel. Untestable on the virtio-only audit guest (no iwn hardware); trigger requires malicious firmware (driver's stated trust boundary, same as DF-1122) or a narrow teardown race with benign firmware β hence likely, not certain.
No comments yet.