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

Duplicate firmware TX-done re-completes a freed TX slot: NULL-deref/KASSERT panic and TX-ring accounting underflow

Summary

bwn_dma_handle_txeof/bwn_pio_handle_txeof map a firmware TX-done cookie to a slot/txpkt with only a range check - no completion-state validation. A duplicate TX-done (behavior the driver's own comment documents occurring in the field) re-enters completion for an already-freed descriptor pair: KASSERT(meta->mt_m!=NULL) panics INVARIANTS kernels (stock X86_64_GENERIC); non-INVARIANTS builds NULL-deref at meta->mt_ni->ni_vap or, in PIO mode, at tp->tp_m->m_pkthdr.len. dr_usedslot is also decremented twice, corrupting free-slot accounting. Any BCM43xx-equipped DragonFly system running stock kernel: a firmware duplicate TX-done instantly panics the kernel; a malicious mini-PCIe card can synthesize cookies at will. Local DoS; no uid0 chain - the corrupted value stays a range-checked slot index. Hardware-absent guest: source-certain, inconclusive at runtime.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3593 Β· 7 files
FileTypeDescriptionSize
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 4.3 KB ↓ raw
probe.sh β€” 838 B view raw
run.log β€” 581 B view raw
fix.diff β€” 1.5 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.4 KB view raw

DF-3593 β€” Duplicate firmware TX-done re-completes a freed TX slot (NULL-deref / KASSERT panic + ring accounting underflow)

Build / Run / Expected

No runtime trigger exists on the audit guest (virtio-only, no Broadcom card):

# on the DF guest, as root:
sh probe.sh

Expected output (what we got): driver resident (hw.bwn.* sysctls live, compiled into X86_64_GENERIC), INVARIANTS enabled, NO Broadcom wireless PCI device β€” the TX-completion interrupt path cannot execute. Verification is therefore source-level certain; runtime classification is inconclusive (hardware absent).

On a machine with a BCM43xx card whose firmware emits a duplicate TX-done event (the driver's own comment, if_bwn.c:7088-7091, states this happens in the field), the expected observation is an immediate kernel panic:

  • INVARIANTS kernels (stock DF X86_64_GENERIC, sys/config/X86_64_GENERIC:56): panic: kassert "/sys/dev/netif/bwn/bwn/if_bwn.c:5996: fail" (DMA mode) or if_bwn.c:6021: fail.
  • non-INVARIANTS kernels: NULL-pointer dereference fatal page fault in bwn_dma_handle_txeof (meta->mt_ni->ni_vap, if_bwn.c:6012) or bwn_pio_handle_txeof (tp->tp_m->m_pkthdr.len, if_bwn.c:6052).

Trigger precondition

  1. bwn(4) attach of a BCM43xx device (DMA or PIO mode β€” PIO via PCMCIA or hw.bwn.usedma=0).
  2. Firmware raises BWN_INTR_TX_OK a second time for a cookie whose slot pair was already completed β€” i.e. two bwn_intr_txeof iterations delivering the same cookie. The in-tree comment at if_bwn.c:7088-7091 documents exactly this hardware behavior ("sometimes H/W returns TX DONE events duplicately"). A malicious/compromised mini-PCIe card can synthesize it at will.
VERDICT.md
↓ download raw

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored against read-only sys/; cannot baseline-validate without BCM43xx hardware on the guest

fix.diff in this pack
↓ fix.diffper-fix-DF-3593

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md full path:line trace; run.log = guest probe (driver resident, hw absent); fix.diff guards duplicate completion state in both DMA and PIO engines

PoC changes

No runnable PoC possible without hardware; probe.sh substitutes, proving residency+absence

Verified recommended fix

Peek the BODY descriptor (mt_m == NULL) / PIO tp_m == NULL before completing; drop duplicate TX-done statuses

Verdict

Source-certain NULL-deref/KASSERT panic on duplicate firmware TX-done; the driver's own comment (if_bwn.c:7088-7091) admits duplicates occur in the field. Runtime trigger impossible on the audit guest: virtio-only QEMU box has no Broadcom 802.11 PCI device (see run.log), and the driver is compiled into the stock kernel (X86_64_GENERIC:267-268) with INVARIANTS (:56), so any BCM43xx-equipped stock DF system panics at KASSERT if_bwn.c:5996; non-INVARIANTS builds NULL-deref at if_bwn.c:6012 (meta->mt_ni->ni_vap) or if_bwn.c:6052 (tp->tp_m->m_pkthdr.len). Secondary effect: dr_usedslot double-decrement (if_bwn.c:6024) corrupts TX-ring free-slot accounting (bwn_dma_freeslot at :5425).