# 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:

```c
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:

```c
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)

```c
tap = sc->qid2tap[qid];
if (tap == NULL)
        return;                 /* stale/unsolicited TX_DONE for agg queue */
```
