# DF-4881 — onoe ath_rate_getxtxrates() writes hardware rate codes into
# ath_rc_series.rix (rate-table INDEX) fields — DF-4850 (amrr) twin

## What
`sys/dev/netif/ath/ath_rate/onoe/onoe.c:135-152`:

```c
rc[0].rix = on->on_tx_rate0;      /* on_tx_rateN are HW RATE CODES */
rc[1].rix = on->on_tx_rate1;
rc[2].rix = on->on_tx_rate2;
rc[3].rix = on->on_tx_rate3;
```

`struct ath_rc_series.rix` is documented `uint8_t rix; /* ratetable index,
not rate code */` (`sys/dev/netif/ath/ath/if_athrate.h:90-97`). onoe caches
**hardware rate codes** into `on_tx_rate0..3` in `ath_rate_update`
(onoe.c:221 `on->on_tx_rate0 = rt->info[on->on_tx_rix0].rateCode;` and
:237/:246/:254 for series 1-3). This is the exact shape filed as DF-4850
for amrr.c:128 — verified here as an independent instance in onoe.

## Consequences (code-proven)
1. **Clobber of the correct series-0 index**: the caller sets the true
   index just before the call (`if_ath_tx.c:1392 bf->bf_state.bfs_rc[0].rix
   = rix;` from `ath_rate_findrate`), and getxtxrates immediately
   overwrites it with the rate code (onoe.c:143).
2. Every downstream consumer then indexes `rt->info[]` with a rate code:
   - `ath_tx_calc_protduration`: `rt->info[rix].phy` (if_ath_tx.c:1073)
   - `ath_tx_calc_duration`: `rt->info[rix].spAckDuration/.lpAckDuration`
     (if_ath_tx.c:1133-1135)
   - `ath_tx_set_rtscts`: `rt->info[rix].controlRate` (if_ath_tx.c:1298)
   - 11n path: `ath_rateseries_setup`:
     `series[i].Rate = rt->info[rc[i].rix].rateCode;`
     (if_ath_tx_ht.c:588) then programs the value into
     `ath_hal_set11nratescenario`.
3. **OOB read on 11n HALs**: HAL rate codes for HT are 0x80..0x8f
   (128-143, e.g. ar5416_11na_table, ar5416_phy.c:80-90) while
   `HAL_RATE_TABLE.info[]` has 64 entries (ah.h:681) — `rt->info[143]`
   reads ~80 entries (~1.2 KB) past the allocated table; the garbage
   `.rateCode/.phy/.controlRate/durations` are then programmed into TX
   descriptors. On pre-11n HALs the codes (0x0b-0x1e) stay under 64, so
   they are in-bounds but **wrong-entry** derefs (wrong ACK/RTS durations,
   wrong protection decisions).

## Trigger conditions
`if_ath_tx.c:1396-1397`: called for every frame with
`bfs_ismrr && try0 != ATH_TXMAXTRY` — i.e. every multi-rate-retry data
frame on `sc_mrretry` hardware (onoe sets `on_tx_try0 = 4` ≠ ATH_TXMAXTRY,
onoe.c:233). Requires an Atheros NIC running the onoe ratectl module
(non-default; sample ships as default, X86_64_GENERIC:266).

## Threat
Attacker: any 802.11 peer (traffic alone triggers it — no crafted input
needed; the bug is unconditional once onoe+mrr hardware is in use).
Impact: heap OOB read feeding hardware TX programming — wrong
durations/rates/mis-programmed 11n scenarios; no direct info leak to
userspace. Functional/degradation impact; consistent with DF-4850 (Low).

## Why not reproduced on the QEMU guest
No Atheros NIC in the guest (env.txt); getxtxrates is only reached from
the ath TX path. Static chain fully cited above.

## Fix
Populate `rc[].rix` with the table *indices* and put the codes in
`rc[].ratecode` — see fix.diff (mirrors the amrr fix shape).
