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

Unvalidated legacy-rate lookups: rt2860_rates[255] OOB read (tx) and 1<<255 UB (set_basicrates, peer-fed rateset)

Summary

ieee80211_legacy_rate_lookup returns 0xff for unknown rates; rt2860_tx_raw checks the sentinel but rt2860_tx does not - rt2860_rates[0xff] reads ~4.3KB past the 12-entry table, garbage mcs/ack-durations over the air - and rt2860_set_basicrates shifts 1<<0xff where the rateset is peer-supplied via beacon/assoc IEs (UB; wrong BASIC_RATE MMIO value). Robustness/hardening only. Fix: mirror the tx_raw guard.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3753 Β· 5 files
FileTypeDescriptionSize
README.md β€” 1.7 KB ↓ raw
fix.diff β€” 1016 B view raw
verdict.json β€” 2.1 KB view raw
env.txt β€” 275 B view raw
manifest.json β€” 881 B view raw

DF-3753 β€” rt2860 unvalidated legacy-rate lookups: rt2860_rates[255] OOB read and 1 << 255 UB (hardening)

Two sites consume ieee80211_legacy_rate_lookup() (returns rateCodeToIndex[rate], i.e. a uint8_t that is 0xff for unknown rates, ieee80211_phy.h:172-177) without checking the sentinel β€” while a third site in the very same file does check it (rt2860_tx_raw, rt2860.c:1834-1838), proving the authors' own awareness:

  1. rt2860_tx rt2860.c:1576-1579: ridx = lookup(...); mcs = rt2860_rates[ridx].mcs; plus rt2860_rates[ridx].sp_ack_dur/lp_ack_dur/phy (rt2860.c:1591-1627). With ridx = 0xff this reads rt2860_rates[255] β€” the table has 12 entries (rt2860reg.h:968-991, RT2860_RIDX_MAX = 11) β€” a ~4.3 KB out-of-bounds const-table read; the garbage mcs/durations only affect the over-air encoding/ACK-duration (no kernel memory corruption). Rate sources (tp->mcastrate/mgmtrate/ucastrate, ni->ni_txrate) are net80211-validated today, so reachability needs a misconfiguration β€” hence Info.

  2. rt2860_set_basicrates rt2860.c:2356-2364: mask |= 1 << lookup(...) where the rateset comes from ni->ni_rates β€” in hostap/STA modes directly fed by the peer's association/beacon IEs. An unknown basic rate (e.g. an 11a rate element while on 11g) makes the lookup return 0xff β†’ 1 << 255 is undefined behavior (on amd64 shifts mod 32 β†’ silently sets bit 31) β†’ wrong RT2860_LEGACY_BASIC_RATE MMIO value. Remotely influenced robustness/UB, no memory unsafety.

Fix (fix.diff): mirror the existing ridx == (uint8_t)-1 guard of rt2860_tx_raw at both sites. Status: Info hardening, source-certain; not runnable on this guest (no Ralink NIC β€” env.txt).

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Mirrors the driver's own existing guard; compile-consistent.

fix.diff (git apply --check passes against pristine sys/)
↓ fix.diffper-fix-DF-3753

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (both sites + the contrasting checked site), fix.diff (guards both, git apply --check passes)

PoC changes

Analysis-only.

Verified recommended fix

Add the same ridx == (uint8_t)-1 guard rt2860_tx_raw already has to rt2860_tx and rt2860_set_basicrates.

Verdict

Two unvalidated uses of ieee80211_legacy_rate_lookup (sentinel return 0xff for unknown rates, ieee80211_phy.h:172-177): rt2860_tx (rt2860.c:1576-1579, rt2860_rates[255] ~4.3KB past the 12-entry table at rt2860reg.h:975, garbage mcs/ack-durations over the air) and rt2860_set_basicrates (rt2860.c:2362-2363, 1 << 255 UB -- rateset is peer-supplied via assoc/beacon IEs, affecting only the BASIC_RATE MMIO value). The same file checks the sentinel at rt2860_tx_raw (rt2860.c:1834), so the omission is clearly a gap. No kernel memory corruption on either path; classified Info hardening. Not reproduced: guest has no Ralink NIC (env.txt); reachability of path 1 additionally requires a mis-set fixed rate (net80211 validates its rate knobs today).