Raw-xmit path lacks TXWI pool-empty guard β NULL-pointer write panic under Tx saturation
Summary
rt2860_tx and rt2860_tx_raw take SLIST_FIRST(&sc->data_pool) with only a comment asserting non-emptiness; the sole guard is rt2860_start's loop. The pool holds 128 entries while ring admission allows ~63 frames on each of 6 rings (~378), and raw/management frames (probe responses via ieee80211_raw_output -> rt2860_raw_xmit) bypass the guard: at exhaustion data==NULL -> txwi = data->txwi NULL write -> panic. DoS of any busy ral(4) interface (hostap probe-request bursts from any client + downlink congestion or power-save peer keep completions lagging); deterministic Fatal trap 12, no corruption beyond the NULL write.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3750 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| fix.diff | β | 655 B | view raw | |
| verdict.json | β | 2.9 KB | view raw | |
| env.txt | β | 275 B | view raw | |
| manifest.json | β | 996 B | view raw |
DF-3750 β rt2860 raw-xmit path: TXWI pool exhaustion β NULL deref panic
Severity / class
Medium (DoS; remotely assisted). CWE-476 / CWE-667 (unchecked invariant "the data pool contains at least one element").
Root cause
rt2860_tx (rt2860.c:1536) and rt2860_tx_raw (rt2860.c:1824) take
data = SLIST_FIRST(&sc->data_pool); with only a comment asserting the pool is
non-empty. The only empty-guard in the driver is the dequeue loop in
rt2860_start (rt2860.c:2058, while (!SLIST_EMPTY(&sc->data_pool) && β¦)).
rt2860_raw_xmit (rt2860.c:1788/1794) calls straight into rt2860_tx/rt2860_tx_raw
with no such guard (net80211 ieee80211_raw_output doesn't check either).
Why the invariant does not hold
- Pool size
RT2860_TX_POOL_COUNT = RT2860_TX_RING_COUNT * 2 = 128(rt2860var.h:21-23). One pool entry is consumed per queued frame (rt2860.c:1744/2002) and returned only by TX-complete processing (rt2860.c:1191) or ring reset. - Per-ring admission is
ring->queued + ntxds >= RT2860_TX_RING_COUNT(rt2860.c:1671, 1704) β up to 63 single-segment frames per ring, across 6 rings (4 EDCA + HCCA + mgmt; rt2860.c:291) β up to ~378 frames in flight vs 128 pool entries.qfullmskonly marks full rings (rt2860.c:1758-1759, checked at 2058) β it does not protect the pool across rings. - Raw (management) frames land on
mgtqidor an EDCA ring viaqid = params->ibp_pri & 3(rt2860.c:1840) β a probe response / auth frame transmitted while ~128 data frames are still unacknowledged in the rings takes the pool below empty βSLIST_FIRSTreturns NULL βtxwi = data->txwi(rt2860.c:1582/1848) β write to NULL+offset β kernel panic.
Trigger recipe (hardware required)
Hostap ral(4) AP under downlink saturation (e.g. iperf to a station, filling β₯2 AC
rings with ~63 frames each while TX completions lag), combined with probe-request
bursts from any 802.11 client (probe responses go through ieee80211_send_mgmt β
ic_raw_xmit = rt2860_raw_xmit). Local variant: any local user pumping frames out
a busy STA/hostap interface while a scan (probe requests) is in flight.
Expected observation on hardware: Fatal trap 12: page fault during read/write at
0x⦠in rt2860_tx/rt2860_tx_raw shortly after the interface goes quiet-but-full.
Status on this audit's guest
No Ralink NIC (env.txt) β not reproducible here; counting argument above is exact.
DF-3750 β VERDICT
status: not_reproduced (hardware absent) β arithmetic of the invariant breach is exact; consequence is an unambiguous NULL write
- Guard exists only in one of three call paths: rt2860_start:2058 checks
!SLIST_EMPTY(&sc->data_pool); rt2860_raw_xmit (rt2860.c:1788 β rt2860_tx at :1536, rt2860_tx_raw at :1824) has no check, and net80211's ieee80211_raw_output (wlan/ieee80211_output.c:635) just callsic->ic_raw_xmit. - Capacity math (rt2860var.h:21-23): pool 128; admission per ring allows ~63 frames;
6 rings; pool entries are per-frame while
queuedcounts TXDs (1-2 per frame) β in-flight frames can exceed 128 while every per-ring admission test passes. - Failure mode:
data = NULLβtxwi = data->txwi(offset 0x0 ofstruct rt2860_tx_data) βtxwi->flags = 0(rt2860.c:1583/1849) writes to 0x8-ish address β unrecoverable panic. (No KASSERT guards this; note the same file shows the author knew the pattern β rt2860_tx_raw validates ridx at :1834 while rt2860_tx doesn't.) - Reachability: needs (a) β₯128 frames in flight, i.e. downlink congestion / power-save peer / TX stall before completions, plus (b) a management frame transmitted via the raw path at that moment β on a hostap interface, attacker probe requests continuously generate (b) for free; (a) is producible by ordinary traffic bursts (a station in 802.11 power-save also delays completions).
- Severity Medium: deterministic panic, but needs a load coincidence; no memory corruption beyond the NULL write (first dereference faults immediately).
- Not reproduced: no Ralink NIC on the audit guest (env.txt).
Fix (fix.diff): guard rt2860_raw_xmit exactly like rt2860_start β drop the frame
with ENOBUFS when the pool is empty. (Guarding inside rt2860_tx/rt2860_tx_raw instead
would be equivalent; the raw_xmit site keeps the invariant with the existing
m_freem/ENETDOWN error shape used at rt2860.c:1778-1782.)
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_testableMinimal guard in the unguarded call path; compile-consistent; runtime validation needs hardware.
fix.diff (git apply --check passes against pristine sys/)
Confirmed kernel references
Detail
Exploit chain
saturate >=2 Tx rings with unacknowledged frames (iperf / PS peer) -> pool (128) empties while rings still admit -> attacker's probe request triggers ieee80211_send_mgmt -> ieee80211_raw_output -> rt2860_raw_xmit -> rt2860_tx with empty pool -> NULL->txwi deref -> Fatal trap 12 panic (DoS).
Evidence (decisive lines)
README.md (exact capacity math + recipe), VERDICT.md, fix.diff (raw_xmit guard, git apply --check passes)
PoC changes
No PoC source: needs the NIC + radio load. Recipe documented.
Verified recommended fix
Guard rt2860_raw_xmit with SLIST_EMPTY(&sc->data_pool) (drop frame, ENOBUFS), matching rt2860_start:2058.
Verdict
rt2860_tx (rt2860.c:1536) and rt2860_tx_raw (rt2860.c:1824) take SLIST_FIRST(&sc->data_pool) with only a comment claiming the pool is non-empty; the sole empty-guard is rt2860_start's dequeue loop (rt2860.c:2058). The raw-xmit path (rt2860_raw_xmit rt2860.c:1788/1794, driven by net80211 management frames incl. hostap probe responses) bypasses it. Pool capacity is 128 (rt2860var.h:23) while ring admission allows ~63 single-segment frames on each of 6 rings (~378 > 128) with qfullmsk tracking only per-ring fullness -- under downlink congestion or a power-save peer, a management frame at the wrong moment makes SLIST_FIRST return NULL and txwi = data->txwi (rt2860.c:1582/1848) NULL-write-panics the kernel. Deterministic panic (DoS), remotely assistable (probe requests keep the raw path busy), no corruption beyond the NULL write. Not reproduced: guest has no Ralink NIC (env.txt); the capacity arithmetic is exact.
No comments yet.