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

onoe ath_rate_getxtxrates() stores hardware rate codes into ath_rc_series.rix (rate-table INDEX) fields and clobbers the correct series-0 index β€” wrong-entry rt->info[] derefs on every multi-rate-retry frame, heap OOB read on 11n HALs (DF-4850 amrr twin)

Summary

Same type confusion as amrr DF-4850: on_tx_rate0..3 are HW rate codes cached by ath_rate_update but written into index-typed rc[].rix, clobbering the caller's correct index - confirmed sinks at if_ath_tx.c:1073/1133/1298 and the 11n path at if_ath_tx_ht.c:588 where HT codes 0x80-0x8f index info[64] (OOB up to ~80 entries). Fires on every mrr data frame; read-only (wrong durations/protection/rates). Cache-indices fix mirroring the amrr shape.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4881 Β· 5 files
FileTypeDescriptionSize
README.md β€” 3.1 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
env.txt β€” 1.1 KB view raw
fix.diff β€” 3.3 KB view raw
verdict.json β€” 3.6 KB view raw

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:

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).

VERDICT.md
↓ download raw

DF-4881 VERDICT

Status: inconclusive (guest has no Atheros hardware; the defect is proven statically with the same rigor as its amrr twin DF-4850).

What was verified

  • The type confusion is in the source, unconditionally: on_tx_rate0..3 are assigned rt->info[...].rateCode at onoe.c:221, :237, :246, :254 and stored into rc[0..3].rix at onoe.c:143-146. ath_rc_series.rix is documented "ratetable index, not rate code" (if_athrate.h:91).
  • The caller's correct rc[0].rix (set at if_ath_tx.c:1392 from ath_rate_findrate's on_tx_rix0) is clobbered by onoe.c:143.
  • Downstream index-derefs of bfs_rc[].rix confirmed at: if_ath_tx.c:1073 (rt->info[rix].phy), if_ath_tx.c:1133-1135 (spAckDuration/lpAckDuration), if_ath_tx.c:1298 (controlRate), if_ath_tx_ht.c:588 (series[i].Rate = rt->info[rc[i].rix].rateCode).
  • OOB reachability on 11n HALs: HT rateCode values 0x80-0x8f (128-143; ar5416_phy.c:80ff) vs HAL_RATE_TABLE.info[64] (ah.h:681) β†’ up to ~80 entries past the table.
  • Trigger: every mrr frame (bfs_ismrr && try0 != ATH_TXMAXTRY, if_ath_tx.c:1396-1397); onoe always sets try0=4 on sc_mrretry parts (onoe.c:233).

Guest environment

No Atheros NIC (pciconf: only virtio vgapci, env.txt); onoe module not shipped (ath_rate.ko = sample). The ath TX path cannot be entered.

Impact ceiling

Heap OOB read (bounded, read-only) whose garbage output is programmed into TX descriptors β†’ wrong ACK/RTS durations, wrong protection rate decisions, mis-programmed 11n rate scenarios (protocol misbehavior / link degradation). No confidentiality impact. Low severity, matching DF-4850.

Fix validation

Not possible on this guest. fix.diff authored against the read-only tree. fix_status: not_applicable.

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 (cache per-series indices in onoe.h, populate .rix with indices and .ratecode with codes) but not kernel-buildable-testable here - the ath TX path needs Atheros hardware the guest lacks.

findings/poc/DF-4881/fix.diff
↓ fix.diffper-fix-DF-4881

Confirmed kernel references

Detail

Exploit chain

802.11 peer exchanges traffic with an ath+onoe+mrr station -> every multi-rate-retry frame takes ath_tx_do_ratelookup -> onoe getxtxrates poisons bfs_rc[0..3].rix with rate codes -> rt->info[rateCode] wrong-entry/OOB reads (if_ath_tx.c:1073/1133/1298; if_ath_tx_ht.c:588) -> garbage durations/protection/rates programmed to hardware. No path to memory write or info leak to userspace; impact is mis-programmed transmission.

Evidence (decisive lines)

README.md (sink-by-sink citations), VERDICT.md, env.txt (no ath hardware). Decisive lines: onoe.c:143-146 (.rix = rate codes), onoe.c:221 (rate0 = rt->info[].rateCode), if_athrate.h:91 (.rix contract), if_ath_tx.c:1392 (clobbered index), if_ath_tx_ht.c:588 (series Rate = rt->info[rix].rateCode), ar5416_phy.c:80-90 (HT codes 0x80-0x8f), ah.h:681 (info[64]).

PoC changes

n/a - no runnable PoC without ath hardware; evidence is the fully-cited static chain

Verified recommended fix

Cache rate-table indices per series in onoe_node and write them to rc[].rix (codes to rc[].ratecode), mirroring the amrr DF-4850 fix

Verdict

Type confusion proven statically (DF-4850/amrr twin): onoe ath_rate_getxtxrates (onoe.c:143-146) stores cached hardware rate codes (on_tx_rate0..3, assigned rt->info[...].rateCode at onoe.c:221/237/246/254) into struct ath_rc_series.rix fields whose contract is 'ratetable index, not rate code' (if_athrate.h:91), clobbering the correct series-0 index the caller installed at if_ath_tx.c:1392. Downstream rt->info[rc[i].rix] derefs confirmed at if_ath_tx.c:1073, 1133-1135, 1298 and if_ath_tx_ht.c:588 (11n rate scenario programming). On 11n HALs HT rate codes are 0x80-0x8f (128-143) vs HAL_RATE_TABLE.info[64] (ah.h:681) => heap OOB read up to ~80 entries past the table; garbage .rateCode/.phy/.durations then program TX descriptors. Triggered by ordinary mrr-frame transmission (if_ath_tx.c:1396-1397, try0=4 != ATH_TXMAXTRY on sc_mrretry parts). Not exercisable on this guest: no Atheros NIC, no onoe module (env.txt). Low severity: read-only OOB feeding HW registers, no confidentiality impact.