rssadapt_rate returns/stores rix=-1 (fresh node RSSI=0 or converged thresholds): rs_rates[-1] read, iwm_setrates KASSERT panic on INVARIANTS kernels, and u_int-indexed ~+8GiB wild read+write in rssadapt_lower_rate β the negative-side residual of DF-0730
Summary
The rate-pick loop exits with rix=-1 whenever no threshold is STRICTLY below rssi<<8 - deterministically for any fresh node (RSSI 0 vs M_ZERO'd thresholds) and after threshold convergence under signal decay. rix=-1 then reads rs_rates[-1] (in-struct), is stored in ra_rix and returned raw to ratectl callers - iwm_setrates KASSERTs (INVARIANTS panic); and on the next failed TX rssadapt_lower_rate - which declares 'u_int rix' - turns -1 into 0xffffffff so (*thrs)[rix] reads AND writes at chunk+~8GiB: wild 2-byte access (page-fault panic or genuine 2-byte wild heap write where the direct map covers it). LATENT: wlan_rssadapt optional, not in GENERIC, no in-tree driver selects it (default AMRR); guest has no radio. Proven via a code-level harness replicating the exact layout (with DF-0730's resize applied so only the negative index is under test): rix=-1, rs_rates[-1] read, KASSERT condition VIOLATED, wild access at chunk+0x200000032 caught by a SIGSEGV trapper ('RESULT: BUG REPRODUCED'); fixed variant clean. Fix: clamp rix to 0 at loop exhaustion + refuse to adapt when ra_rix<0 in lower_rate (the u_int hazard).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3459 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 5.8 KB | β raw | |
| df3459_harness.c | β | 10.8 KB | view raw | |
| build.sh | β | 93 B | view raw | |
| run.sh | β | 89 B | view raw | |
| run.log | β | 945 B | view raw | |
| run.fixed.log | β | 734 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| fix_module_build.log | β | 7.4 KB | view raw | |
| env.txt | β | 377 B | view raw | |
| verdict.json | β | 5.7 KB | view raw |
DF-3459 β rssadapt rix = -1: negative rate index β ~+8GiB wild read+write (u_int) in rssadapt_lower_rate
File: sys/netproto/802_11/wlan/ieee80211_rssadapt.c
Verdict: REPRODUCED (code-level harness; module latent on this guest β no WiFi radio,
no in-tree driver selects RSSADAPT, identical reachability profile as DF-0730/DF-0731).
Severity filed: Medium (latent) β would be High if a driver wired RSSADAPT up.
What the bug is
rssadapt_rate() (ieee80211_rssadapt.c:253-255) picks the rate index with
for (rix = rs->rs_nrates-1; rix >= 0; rix--)
if ((*thrs)[rix] < (rssi << 8))
break;
The comparison is strict. The loop falls out with rix == -1 whenever no rate
row has a threshold strictly below rssi<<8:
rssi == 0β the normal state of every freshly created node:node_getrssi()(ieee80211_node.c:1146-1155) returns 0 whileni_avgrssiis stillIEEE80211_RSSI_DUMMY_MARKER, and clamps negatives to 0. All thresholds are 0 (M_ZERO allocation) β0 < 0is false for every row β deterministic rix=-1 on the first rate query of any new node.- threshold convergence β
rssadapt_lower_rate()raises thresholds towardrssi<<8with integer interpolation that can converge to exactlyrssi<<8(e.g. a station's signal decays below every previously-raised threshold).
With rix == -1:
:258ni->ni_rates.rs_rates[-1]β in-struct OOB read (returns thers_nratesbyte) β bogusni_txrate;:259ra->ra_rix = -1is stored;-1is returned to every caller ofieee80211_ratectl_rate()β e.g.if_iwm.c:3540/3574feed it straight intoiwm_setrates(), whose first line isKASSERT(rix >= 0 && rix < nrates)(if_iwm.c:4366) β guaranteed panic on INVARIANTS kernels;-
a subsequent failed TX completion (
rssadapt_tx_completeβrssadapt_lower_rate) hits the killer::279declaresu_int rix;, sorix = ra->ra_rixmakes0xffffffff, and(*thrs)[rix]at:284/:285addresseschunk + 2 * 0xffffffff = chunk + 0x1fffffffe (~ +8 GiB)
β a WILD 2-byte read followed by a WILD 2-byte write at a fixed ~+8GiB offset
from the per-node heap chunk. Unmapped there (the usual case) β kernel page
fault β panic. On boxes whose direct map covers chunk+8GiB β genuine 2-byte
wild heap corruption with a semi-attacker-influenced value
(interpolate(last_thr, rssi<<8)).
This is the negative-side residual of DF-0730 (which fixed the high-side
[8] vs rs_nrates<=15 bound; its fix.diff leaves rix<0 untouched).
Reproduce
./build.sh # buggy (as shipped) ./run.sh # β RESULT: BUG REPRODUCED β¦ ./build.sh fixed # applies this pack's fix.diff logic ./run.sh # β RESULT: no negative-index effect observed (fix active)
Build/run on the audit guest (DragonFly 6.5-DEVELOPMENT, stock INVARIANTS #0
kernel): run.log (buggy) vs run.fixed.log.
Fix validation
fix.diffβgit apply-clean against the sys/ tree; the patchedwlan_rssadaptmodule builds clean with full kernel-WerrorCFLAGS (fix_module_build.log, rc=0).- Harness
fixedmode shows all three effects gone (no rix<0, no KASSERT violation, no wild access).
DF-3459 β VERDICT
Status: REPRODUCED (code-level harness, latent module on this guest) Impact: panic (INVARIANTS via iwm KASSERT; production via ~+8GiB wild access in rssadapt_lower_rate) / 2-byte wild heap write where chunk+8GiB is mapped Confidence: certain (source-level arithmetic; harness run on the audit guest)
How it reproduces
df3459_harness.c replicates the exact struct layout (with DF-0730's resize
already applied β 15 columns β so the only negative-index effect demonstrated is
this finding's) and the exact code paths:
rssadapt_rate()loop as shipped (ieee80211_rssadapt.c:253-255);:258rs_rates[rix]read;:259ra_rixstore;rssadapt_lower_rate()as shipped (:279u_int rix, :283rix = ra->ra_rix, :284/:285(*thrs)[rix]read+write).
Buggy run (run.log, guest DragonFly dfbsd 6.5-DEVELOPMENT #0, cc 8.3):
rssi=0 fresh node : rssadapt_rate -> rix=-1 ra_rix=-1 ni_txrate=0x08 (bogus: rs_nrates byte 8) -> rs_rates[-1] read returned the rs_nrates byte (1): in-struct OOB read confirmed -> iwm_setrates KASSERT(rix >= 0 && rix < nrates): VIOLATED (INVARIANTS kernel would PANIC here) lower_rate with ra_rix=-1: u_int rix = 0xffffffff -> access at chunk+0x200000032 (wild +7 GiB) -> WILD ACCESS confirmed: kernel equivalent is a ~+8GiB READ+WRITE from the rssadapt_node chunk -> kernel page fault -> PANIC RESULT: BUG REPRODUCED β rix=-1: in-struct rs_rates[-1] read, iwm KASSERT violation, and u_int-indexed ~+8GiB wild read+write in rssadapt_lower_rate()
The wild access is performed in a forked child with a SIGSEGV trapper so the
harness can report the fault the kernel cannot catch. The computed address
chunk+0x200000032 = offsetof(ra_rate_thresh)=52 + 2Γ0xffffffff β exactly the
kernel's arithmetic.
Why rix = -1 is reachable in the field (not just theory)
node_getrssi()(ieee80211_node.c:1146-1155) returns 0 whileni_avgrssi == IEEE80211_RSSI_DUMMY_MARKER(node created, no RX frame sampled yet) and clamps negative RSSI to 0. Fresh node + zeroed thresholds (M_ZERO, ieee80211_rssadapt.c:184-185) β(*thrs)[rix] < (rssi << 8)is0 < 0for every row β loop exhausts β rix = -1 on the first rate query of every new node with an unsampled RSSI.- Sustained-decay scenario:
rssadapt_lower_rateraises thresholds toward the failure-timerssi<<8withinterpolate()=(4*old+4*new)/8, which converges to exactlyrssi<<8; the comparison is strict<, so once every row β₯ currentrssi<<8the nextrssadapt_rateagain exhausts the loop.
Consequence chain (as shipped)
rix = -1returned by.ir_rateβiwm_setrates(sc, in, -1)(if_iwm.c:3540-3544, :3574-3578, :4646) βKASSERT(rix >= 0 && rix < nrates)(if_iwm.c:4366) β fatal kernel panic on INVARIANTS kernels. Other drivers ignore the return ((void) ieee80211_ratectl_rate(...)β rt2560/2661/2860, wpi, iwn).ra_rix = -1stored β next failed TX βrssadapt_lower_rate:u_int rix = 0xffffffffβ(*thrs)[rix]= read+write atchunk+0x1fffffffe(~+8 GiB). Unmapped KVA β page fault β panic on production kernels. Mapped (large-RAM direct map) β 2-byte wild write with valueinterpolate(last_thr, rssi<<8)(rssi is the remote peer's signal strength β semi-attacker-influenced).ni_txrate = rs_nrates & 0x7f(bogus rate code, in-struct read at :206 for empty ratesets too).
Latency caveat (why Medium, not High)
Identical to DF-0730/DF-0731: wlan_rssadapt is optional (sys/conf/files:1655),
NOT in X86_64_GENERIC, no in-tree driver calls
ieee80211_ratectl_set(vap, RSSADAPT) (default is AMRR,
ieee80211_ratectl.c:121-122), and the audit guest has no WiFi radio
(ifconfig -l β vtnet0 lo0). Runtime verification on this guest is therefore a
hard blocker; the code-level harness plus source trace is the strongest
demonstration available. If a driver wires RSSADAPT up, this is a
remote-triggerable (any TX failure on a fresh/zero-RSSI node) ~+8GiB wild write β
High.
Exploit chain
Not developed: module cannot be loaded/selected on the audit guest (no radio, no
selector). In a hypothetical wired-up deployment the primitive would be:
fresh-node RSSI=0 β forced TX failure (remote deauth/flood) β wild 2-byte write at
ra+0x1fffffffe β a fixed-offset corruption with a bounded controlled value; on
typical RAM footprints the offset lands in unmapped KVA β DoS (panic) is the
realistic ceiling.
Fix validation
fix.diff: clamprix = 0when the pick loop exhausts (:253), bail on empty ratesets inrssadapt_node_init, and defensively refuse to adapt whenra_rix < 0inrssadapt_lower_rate(the u_int hazard).- Applied to the guest's /usr/src copy:
wlan_rssadaptmodule rebuilds clean (makerc=0, full kernel-WerrorCFLAGS βfix_module_build.log). - Harness
fixedmode: rix=0, no KASSERT violation, no fault, canaries intact (run.fixed.log). - Guest source restored to pristine afterwards (sha256 verified against the audit tree).
Kernel references
sys/netproto/802_11/wlan/ieee80211_rssadapt.c:202-206 (node_init pick loop / empty rateset) sys/netproto/802_11/wlan/ieee80211_rssadapt.c:253-259 (rate pick loop, rs_rates[rix], ra_rix store) sys/netproto/802_11/wlan/ieee80211_rssadapt.c:279,283-286 (u_int rix; wild read+write) sys/netproto/802_11/wlan/ieee80211_node.c:1146-1155 (node_getrssi returns 0 / clamps) sys/dev/netif/iwm/if_iwm.c:3540,3574,4366,4646 (KASSERT consumer) sys/netproto/802_11/ieee80211_ratectl.h:91-95 (unclamped ir_rate return)
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
fixedfix.diff applies cleanly (git apply --check on the guest's /usr/src); the patched wlan_rssadapt module rebuilds with full kernel -Werror CFLAGS (rc=0, fix_module_build.log); harness fixed-mode shows rix=0, no iwm KASSERT violation, and no wild access (run.fixed.log). Module cannot be exercised at runtime on this guest (no radio), so validation is compile + harness-level, matching the DF-0730 precedent.
['findings/poc/DF-3459/fix.diff', 'findings/poc/DF-3459/fix_module_build.log', 'findings/poc/DF-3459/run.fixed.log']
Confirmed kernel references
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:253
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:258
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:259
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:279
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:283
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:284
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:285
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:202
- sys/netproto/802_11/wlan/ieee80211_node.c:1146
- sys/dev/netif/iwm/if_iwm.c:3540
- sys/dev/netif/iwm/if_iwm.c:4366
- sys/netproto/802_11/ieee80211_ratectl.h:91
Detail
Exploit chain
Hypothetical wired-up deployment only (module unselectable in-tree): remote peer associates (fresh node, RSSI unsampled -> 0) -> attacker forces TX failure (deauth/RTS flood) -> rssadapt_lower_rate wild 2-byte write at ra+0x1fffffffe with value interpolate(last_thr, rssi<<8). Fixed-offset, bounded-value corruption; realistic ceiling is DoS panic (offset usually unmapped). No chain developed on the guest: hard blocker (module cannot be loaded/selected β no radio, no ratectl selector).
Evidence (decisive lines)
['findings/poc/DF-3459/run.log β buggy run: rix=-1, rs_rates[-1] read confirmed, iwm KASSERT condition VIOLATED, wild access at chunk+0x200000032 (~+8GiB) caught by SIGSEGV trapper', 'findings/poc/DF-3459/run.fixed.log β fix.diff logic active: rix=0, no KASSERT violation, no fault, canaries intact', 'findings/poc/DF-3459/fix_module_build.log β patched wlan_rssadapt.ko builds clean, full kernel -Werror CFLAGS, rc=0', 'findings/poc/DF-3459/df3459_harness.c β exact struct layout (DF-0730 resize applied) + exact kernel code paths']
PoC changes
Harness is self-authored (no seed). Two iterations were needed: (1) canary region initially misaligned the struct (zone+1) causing an unrelated SSE fault β moved to a 32-byte-aligned slot; (2) the wild access is performed in a forked child with a SIGSEGV trapper (_exit(42)) so the fault the kernel cannot catch is reported instead of silently killing the harness; buggy/fixed modes selected by build.sh arg, following the DF-0730 pack conventions.
Verified recommended fix
Clamp rix to 0 when the threshold pick loop exhausts, refuse to adapt when ra_rix < 0 in rssadapt_lower_rate (u_int hazard), and bail out of rssadapt_node_init on empty ratesets β see findings/poc/DF-3459/fix.diff.
Verdict
REPRODUCED (code-level harness; module latent on this guest). rssadapt_rate (ieee80211_rssadapt.c:253-255) exhausts its pick loop with rix=-1 whenever no threshold is STRICTLY below rssi<<8 β deterministically for any fresh node because node_getrssi (ieee80211_node.c:1146-1155) returns 0 while ni_avgrssi is IEEE80211_RSSI_DUMMY_MARKER and all M_ZERO'd thresholds are 0 (0<0 false for every row), and after threshold convergence under signal decay. rix=-1 then (a) reads rs_rates[-1] at :258 (in-struct, returns the rs_nrates byte into ni_txrate), (b) is stored into ra_rix and returned to callers β if_iwm.c:3540/3574 feeds it to iwm_setrates whose KASSERT(rix>=0&&rix
No comments yet.