# 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

```c
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 while `ni_avgrssi` is still
  `IEEE80211_RSSI_DUMMY_MARKER`, and clamps negatives to 0. All thresholds are 0
  (M_ZERO allocation) → `0 < 0` is false for every row → **deterministic** rix=-1
  on the first rate query of any new node.
* threshold convergence — `rssadapt_lower_rate()` raises thresholds toward
  `rssi<<8` with integer interpolation that can converge to exactly `rssi<<8`
  (e.g. a station's signal decays below every previously-raised threshold).

With rix == -1:

* `:258` `ni->ni_rates.rs_rates[-1]` — in-struct OOB read (returns the `rs_nrates`
  byte) → bogus `ni_txrate`;
* `:259` `ra->ra_rix = -1` is stored; `-1` is returned to every caller of
  `ieee80211_ratectl_rate()` — e.g. `if_iwm.c:3540/3574` feed it straight into
  `iwm_setrates()`, whose first line is `KASSERT(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: `:279` declares **`u_int rix;`**, so `rix = ra->ra_rix` makes
  `0xffffffff`, and `(*thrs)[rix]` at `:284/:285` addresses

      chunk + 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 patched
  `wlan_rssadapt` module builds clean with full kernel `-Werror` CFLAGS
  (`fix_module_build.log`, rc=0).
* Harness `fixed` mode shows all three effects gone (no rix<0, no KASSERT
  violation, no wild access).
