# VERDICT — DF-3593

**Status: inconclusive at runtime (no bwn hardware on the audit guest); the defect itself is source-certain.**
**Impact: kernel panic (local DoS) — KASSERT panic on INVARIANTS kernels, NULL-deref fatal trap on non-INVARIANTS builds; plus TX-ring accounting corruption (`dr_usedslot` underflow).**

## Root cause (path:line)

TX completion maps a firmware-supplied 16-bit cookie back to the originating
slot/txpkt, but never validates that the slot is still *outstanding*:

1. `bwn_intr_txeof()` (`sys/dev/netif/bwn/bwn/if_bwn.c:5296-5336`) pops every
   `BWN_XMITSTAT` entry; `stat.cookie = stat0 >> 16` (`:5308`) is raw firmware
   data.
2. `bwn_dma_parse_cookie()` (`:7055-7102`) decodes queue (`cookie & 0xf000`)
   and slot (`cookie & 0x0fff`). The only rejection is the *range* check
   `:7087` (`*slot >= dr->dr_numslots`, 256 slots — pass-1 DF-1132 covers the
   separate `dr == NULL` deref when the queue nibble is invalid). A duplicate
   cookie for a valid, already-completed slot passes all checks.
3. First completion (`bwn_dma_handle_txeof`, `:5984-6028`) walks HEADER→BODY
   slots, and at the BODY slot does:
   `ieee80211_ratectl_tx_complete(meta->mt_ni->ni_vap, …)` (`:6012`),
   `ieee80211_tx_complete(meta->mt_ni, meta->mt_m, 0)` (`:6017`), then clears
   `meta->mt_ni = NULL; meta->mt_m = NULL;` (`:6018-6019`) and decrements
   `dr->dr_usedslot` (`:6024`).
4. A second TX-done with the same cookie re-enters: HEADER slot unloads its
   (already unloaded) map, BODY slot has `mt_islast == 1`:
   * `KASSERT(meta->mt_m != NULL)` (`:5996`) — **panics every INVARIANTS
     kernel** (stock `X86_64_GENERIC` builds with `options INVARIANTS`,
     `sys/config/X86_64_GENERIC:56`);
   * non-INVARIANTS: `meta->mt_ni->ni_vap` at `:6012` with `mt_ni == NULL` →
     NULL-deref fatal page fault in kernel mode; even before that,
     `ieee80211_tx_complete(NULL, NULL, 0)` at `:6017` would trap.
5. `dr->dr_usedslot--` executes a second time (`:6024`) → underflow below
   zero → `bwn_dma_freeslot()` (`:5421-5426`, `numslots - usedslot`) now
   reports more free slots than the ring holds; TX path
   (`bwn_dma_tx_start :1151` `bwn_dma_getslot`) then advances `dr_curslot`
   onto descriptors whose meta state is inconsistent.
6. PIO mode is worse: `bwn_pio_parse_cookie()` (`:6726-6760`) only validates
   the index against `N(tq->tq_pkts)`; `bwn_pio_handle_txeof()` immediately
   does `roundup(tp->tp_m->m_pkthdr.len + BWN_HDRSIZE(mac), 4)`
   (`:6052`) — `tp->tp_m` is NULL for any already-completed (free-listed)
   packet → **NULL deref on the first instruction of the completion**.

The driver itself documents that the trigger occurs in the field —
`if_bwn.c:7088-7091`:

> XXX FIXME: sometimes H/W returns TX DONE events duplicately that it occurs
> events which have same H/W sequence numbers.

so this is not merely a malicious-peripheral path; it is a known device
behavior with no completion-state guard. (The seq guard `KASSERT(status->seq
== dma->lastseq)` at `:7093` lives *inside the out-of-range branch only* and
duplicates carry the *same* seq, so it never fires.)

## Why runtime reproduction was not possible

The audit guest is QEMU/KVM with virtio devices only. `probe.sh` / `run.log`
show: `hw.bwn.*` sysctls live (driver compiled into the kernel,
`sys/config/X86_64_GENERIC:267-268`), and `pciconf -l` shows **no Broadcom
802.11 device** (no `class=028000`, no `chip=…14e4`). The interrupt/TX-done
path is unreachable without the card. No `uid=0` chain is applicable — this is
a device-driven crash, not a memory-corruption primitive under attacker-chosen
control (the corrupted quantity is a slot index that stays range-checked).

## Fix

Guard completion state before use (both engines), mirroring what the range
check already does for slot indices — see `fix.diff` in this directory.

## Artifacts

* `probe.sh` — guest probe (driver residency + hardware absence)
* `run.log` — probe output (untrimmed)
* `fix.diff` — proposed patch vs read-only `sys/` tree (never applied)

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