ra_rate_thresh[3][8] heap OOB read/write β indexed up to rs_nrates-1(14) from remote peer rates IE
Summary
ieee80211_rssadapt.h:65-66 ra_rate_thresh[3][IEEE80211_RATE_SIZE=8] but rs_rates is rs_rates[IEEE80211_RATE_MAXSIZE=15] (_ieee80211.h:374-379). ieee80211_setup_rates (ieee80211_input.c:420-436) merges Supported+Extended Rates IEs from unauthenticated remote peer up to 15 rates ieee80211_fix_rate does NOT cap at 8. rssadapt_node_init :198 copies rateset verbatim. rssadapt_rate :253-254 loop rix=rs_nrates-1 down (*thrs)[rix] OOB read for rix>=8. rssadapt_lower_rate :283-285 (*thrs)[rix]=interpolate() OOB WRITE up to 14 bytes past struct end. rssadapt_raise_rate :303-310 (*thrs)[rix+1] OOB read+write. uint16_t(*thrs)[IEEE80211_RATE_SIZE] typedef :241,278,297 locks wrong stride. LATENT: no in-tree driver selects rssadapt default is AMRR. Trigger: peer advertises >8 rates (11g standard 12 rates) + frame >1024 bytes TX complete. Fix: resize to [IEEE80211_RATE_MAXSIZE] update all 3 typedefs.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0730 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0730_harness.c | trigger-source | deterministic code-level OOB harness mirroring the kernel struct + rssadapt_lower_rate write logic, canaried | 8.6 KB | view raw |
| build.sh | build-script | builds harness buggy ([8]) or fixed ([15]) via -DTHRESH_COLS | 566 B | view raw |
| run.sh | run-script | runs the built harness | 139 B | view raw |
| fix.diff | suggested-fix | git-apply-able: resize ra_rate_thresh to [IEEE80211_RATE_MAXSIZE] + 3 typedef updates | 1.7 KB | view raw |
| VERDICT.md | verdict | full narrative: mechanism, citations, reachability, fix validation | 7.1 KB | β raw |
| README.md | readme | reproduce instructions | 1.8 KB | β raw |
| build.log | build-log | baseline buggy build (gcc -Warray-bounds warning fired) | 351 B | view raw |
| run.log | run-log | baseline buggy run on #0: OOB confirmed, canary clobbered, exit 1 | 689 B | view raw |
| fix_run.log | run-log | fixed-dim run on patched #1: IN-BOUNDS, canary intact, exit 0 | 781 B | view raw |
| fix_build.log | build-log | full make nativekernel log of the single-fix kernel (NK_DONE rc=0) | 5.6 MB | β download |
| fix_install.log | build-log | make installkernel log | 80.4 KB | view raw |
| env.txt | environment | uname, cc, no wlan modules, ifconfig, default-ratectl citations | 1.0 KB | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0730 β ra_rate_thresh[3][8] heap OOB read/write (latent)
Severity: Medium (latent). Real array-size/index mismatch; unreachable in
the default config (no driver selects wlan_rssadapt).
Files
df0730_harness.cβ deterministic code-level harness mirroring the kernel struct +rssadapt_lower_ratewrite logic, with a canary to detect OOB.build.sh [buggy|fixed]β builds the harness with the buggy[8]dim (default) or the fixed[15]dim (-DTHRESH_COLS=IEEE80211_RATE_MAXSIZE).run.shβ runs the already-built harness.fix.diffβ git-apply-able fix: resizera_rate_threshto[IEEE80211_RATE_MAXSIZE]- update the 3
thrstypedefs. VERDICT.mdβ full narrative + mechanism + citations.build.log/run.logβ baseline (buggy) build+run on the unpatched#0kernel.fix_run.logβ fixed-dim build+run on the patched#1kernel.fix_build.logβ fullmake nativekernellog for the single-fix kernel.fix_install.logβmake installkernellog.env.txtβ guest uname / cc / module state / default-ratectl citations.
Reproduce
sh build.sh buggy && ./df0730_harness # exit 1: OOB / canary clobbered / -Warray-bounds sh build.sh fixed && ./df0730_harness # exit 0: IN-BOUNDS / canary intact
Expected
- Buggy build (matches master): harness prints
OUT-OF-BOUNDS ... heap OOB WRITE confirmed, exit 1; gcc emitsarray subscript 14 is above array bounds. - Fixed build: harness prints
IN-BOUNDS ... BUG FIXED, exit 0; no warning. - On the patched single-fix kernel (
6.5-DEVELOPMENT #1) the OOB is gone.
Why not a live kernel trigger
No WiFi radio on the guest (ifconfig -l β vtnet0 lo0), wlan_rssadapt is
optional (not in the default kernel), and no in-tree driver selects RSSADAPT
(default is AMRR). The bug is latent; the harness is the deterministic proof.
DF-0730 β ra_rate_thresh[3][8] heap OOB read/write (latent / code-level proof)
Verdict: REPRODUCED (code-level harness) β bug is REAL but LATENT on this guest
The array-dimension/index mismatch described in the finding is real and
confirmed at the source level. It is not triggerable live on this audit
guest because the guest has no WiFi radio and the wlan_rssadapt module is
not in the default kernel and not selected by any in-tree driver
(default rate-adapt is AMRR). It is therefore a latent code-correctness /
defense-in-depth defect: a remote-triggered kernel heap OOB read+write that
would become live the moment any driver selects the RSSADAPT algorithm.
Mechanism (every hop cited path:line)
-
The array is too small for its index space. -
sys/netproto/802_11/ieee80211_rssadapt.h:65-66declaresuint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS=3][IEEE80211_RATE_SIZE=8]. -sys/netproto/802_11/_ieee80211.h:374-375:IEEE80211_RATE_SIZE = 8,IEEE80211_RATE_MAXSIZE = 15. -
rs_nratescan legitimately reach 15. -sys/netproto/802_11/wlan/ieee80211_input.c:413-439(ieee80211_setup_rates) merges the Supported Rates IE (β€8) with the Extended Rates IE, capping the total atIEEE80211_RATE_MAXSIZE = 15(:428-429). A standard 802.11g AP advertises 12 rates; 15 is the spec max. -sys/netproto/802_11/wlan/ieee80211_rssadapt.c:198(rssadapt_node_init) copies the rateset verbatim (ra->ra_rates = *rs), sora->ra_rates.rs_nrates β [0, 15]. -
The rateset drives the OOB index. -
rssadapt_rate:253-254loopsfor (rix = rs->rs_nrates-1; rix >= 0; rix--)and reads(*thrs)[rix]βrixcan be 14. -rssadapt_lower_rate:285writes(*thrs)[rix] = interpolate(...). -rssadapt_raise_rate:310writes(*thrs)[rix+1](up to index 15). -thrs = &ra->ra_rate_thresh[bucket(pktlen)](:249/:281/:301), andbucket()returns 2 forpktlen > 1024(:220-233,IEEE80211_RSSADAPT_BKT0 * 2^BKTPOWER = 128*8 = 1024). - The typedefuint16_t (*thrs)[IEEE80211_RATE_SIZE]at:241/:278/:297bakes the wrong (8) row stride into every dereference. -
Worst case β heap OOB. For
bucket=2, rix=14:(*thrs)[14]=ra->ra_rate_thresh[2][14], which is 6 Γuint16_t(12 bytes) pastra->ra_rate_thresh[2][7], i.e. past the end of thestruct ieee80211_rssadapt_node(it is the last field). Since the node iskmalloc(sizeof(...), M_80211_RATECTL)(:184), this is a heap OOB read+write of up to 12β14 bytes. The harness proves it lands 10 bytes past the struct forrix=14, bucket=2(struct=104 B, write at offset 112).
Proof (deterministic code-level harness)
df0730_harness.c mirrors the exact struct layout and the rssadapt_lower_rate
write logic, allocates the struct inside a canaried region, and triggers the
write at bucket=2, rix=14. Built two ways:
| Build | Array dim | Struct size | Write offset | Verdict |
|---|---|---|---|---|
buggy (THRESH_COLS=8) |
[3][8] |
104 B | 112 | OOB, canary clobbered (exit 1) |
fixed (THRESH_COLS=15) |
[3][15] |
144 B | 140 | in-bounds, canary intact (exit 0) |
The buggy build is additionally flagged by gcc itself:
warning: array subscript 14 is above array bounds of 'uint16_t[8]' [-Warray-bounds]
(see build.log). The fixed build produces no such warning.
Reachability / threat model (why "latent")
- Guest: no WiFi interface (
ifconfig -lβvtnet0 lo0); nowlan_*modules loaded. Net80211 is never instantiated with a live vap. - Default kernel:
wlan_rssadaptisoptional wlan_rssadapt(sys/conf/files:1655) β not compiled intoX86_64_GENERIC. - No driver selects it: no in-tree driver calls
ieee80211_ratectl_set(vap, IEEE80211_RATECTL_RSSADAPT). The default is AMRR (ieee80211_ratectl.c:122;ieee80211.c:671seeds NONE).
If a driver (in-tree port or out-of-tree module) selected RSSADAPT and a peer advertised >8 rates (trivial β every 11g AP), then a single TX-complete on a >1024-byte frame would corrupt the per-node slab object. With an attacker controlling the advertised rates and frame timing, this is a remote, unauthenticated kernel heap OOB read+write β a serious corruption primitive. Today it is dormant.
Exploit chain
none β the bug is not reachable from an unprivileged user on this guest
(no WiFi radio, module not loaded/selected). This is a valid hard blocker
(dead/unreachable-at-runtime on this guest AND not selectable in the default
config), so no uid=0 chain is attempted or possible here. The primitive is
characterized at the code level: it is a 12β14-byte heap OOB write past a
kmalloc(M_80211_RATECTL) per-node object, attacker-shapeable via the peer's
rate IE, on the wlan_rssadapt TX-complete path.
Fix
Resize the array to match the index space and update the three typedef sites:
- uint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS]
- [IEEE80211_RATE_SIZE];
+ uint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS]
+ [IEEE80211_RATE_MAXSIZE];
and the three uint16_t (*thrs)[IEEE80211_RATE_SIZE]; β [IEEE80211_RATE_MAXSIZE]
in ieee80211_rssadapt.c (lines 241, 278, 297).
Full diff in fix.diff. Matches the finding's proposed fix.
Fix validation (Phase 8)
- Baseline (unpatched
#0): buggy harness β OOB confirmed, canary clobbered, gcc-Warray-boundswarning (seebuild.log,run.log). - Patched single-fix kernel: applied
fix.diffto/usr/src, builtmake -j6 nativekernel KERNCONF=X86_64_GENERIC(fix_build.log,=== NK_DONE rc=0 ===), installed viamake installkernel, rebooted to6.5-DEVELOPMENT #1(Thu Jul 9 01:07:37 UTC 2026), sha256d28b91cedf66β¦. - After: harness built against the fixed dim (
THRESH_COLS=15) β write in-bounds (offset 140 < struct end 144), canary intact, exit 0, no-Warray-boundswarning (fix_run.log).
The fix closes the OOB: struct grows 104β144 B (array +42 B), the worst-case
rix=14 write now lands inside the array, and the compiler no longer flags an
array-bounds violation.
How to reproduce
ssh dfbsd-maxx # or any DragonFly host with cc cd poc/DF-0730 sh build.sh buggy && ./df0730_harness # exit 1, prints OOB / canary clobbered sh build.sh fixed && ./df0730_harness # exit 0, prints IN-BOUNDS / canary intact
Caveats / next steps
- The bug is genuinely latent in master; severity Medium is appropriate (would be High/Critical if any driver wired up RSSADAPT).
- A defense-in-depth alternative is to also cap
rixtoIEEE80211_RATE_SIZE-1in the loops, but resizing the array is the correct root-cause fix and is what the finding proposes. - Note for future runs: on DragonFly,
cp kernel.stripped /boot/kernel/kernelis rejected by the loader ("Unable to load /kernel/kernel"); usemake installkernelinstead (it installs the full debug kernel ELF).
Fix verification
fixedVALIDATED the fix. Applied fix.diff to /usr/src, built make -j6 nativekernel KERNCONF=X86_64_GENERIC (=== NK_DONE rc=0 ===), installed via make installkernel, rebooted to 6.5-DEVELOPMENT #1 (sha256 d28b91ce...). The buggy harness (THRESH_COLS=8) on the unpatched baseline shows the OOB (write at off 112, struct ends 104, 10 bytes OOB, canary clobbered, gcc -Warray-bounds fired, exit 1); the fixed harness (THRESH_COLS=15) on the single-fix kernel shows IN-BOUNDS (write at off 140, struct ends 144, canary intact, no warning, exit 0). The struct grows 104->144 B so the worst-case rix=14 write now lands inside ra_rate_thresh => the OOB is eliminated. fix_patched_reproduced=false because the fix correctly SUPPRESSES the bug (no OOB on the patched kernel) -- i.e. the bad behavior is gone, which is the success criterion.
baseline (#0, buggy dim 8): sizeof=104, 'Write landed at offset 112', 'struct end is at offset 104', 'OUT-OF-BOUNDS: write at off 112 is 10 byte(s) PAST the struct', 'Canary clobbered past struct end: YES (heap OOB WRITE confirmed)', exit 1, plus gcc 'array subscript 14 is above array bounds of uint16_t[8]'. patched (#1, fixed dim 15): sizeof=144, 'Write landed at offset 140', 'struct end is at offset 144', 'IN-BOUNDS: write at off 140 is within the struct (ends 144). No OOB.', 'Canary intact: YES', exit 0, no -Warray-bounds warning.
Confirmed kernel references
- sys/netproto/802_11/ieee80211_rssadapt.h:65
- sys/netproto/802_11/ieee80211_rssadapt.h:66
- sys/netproto/802_11/_ieee80211.h:374
- sys/netproto/802_11/_ieee80211.h:375
- sys/netproto/802_11/_ieee80211.h:377
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:198
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:241
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:249
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:253
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:254
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:278
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:285
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:297
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:310
- sys/netproto/802_11/wlan/ieee80211_input.c:413
- sys/netproto/802_11/wlan/ieee80211_input.c:428
- sys/netproto/802_11/wlan/ieee80211_input.c:436
- sys/netproto/802_11/wlan/ieee80211_ratectl.c:122
- sys/conf/files:1655
Detail
Exploit chain
none (valid hard blocker): the primitive is unreachable from an unprivileged user on this guest and in the default config -- no WiFi radio (ifconfig -l => vtnet0 lo0), wlan_rssadapt not compiled into the default kernel (optional wlan_rssadapt, sys/conf/files:1655), and no in-tree driver selects the RSSADAPT ratectl (default AMRR, ieee80211_ratectl.c:122). This is the 'dead/unreachable at runtime on this guest AND not selectable in default config' blocker from Phase 6. Primitive characterized at code level: a 12-14-byte heap OOB read+write past a kmalloc(M_80211_RATECTL) per-node struct ieee80211_rssadapt_node, attacker-shapeable via the remote peer's rate IE, on the wlan_rssadapt TX-complete path (rssadapt_lower_rate/raise_rate). No uid=0 chain is possible or attempted. Harness proof lives in findings/poc/DF-0730/df0730_harness.c.
Evidence (decisive lines)
BEFORE (buggy, unpatched #0 kernel source, harness built with THRESH_COLS=8): df0730_harness.c:117:12: warning: array subscript 14 is above array bounds of 'uint16_t[8]' [-Warray-bounds] sizeof(struct ieee80211_rssadapt_node) = 104 Write landed at offset 112 within the allocation. struct end is at offset 104. OUT-OF-BOUNDS: write at off 112 is 10 byte(s) PAST the struct (struct ends at 104). Canary clobbered past struct end: YES (heap OOB WRITE confirmed) harness exit code: 1 AFTER (fixed-dim THRESH_COLS=15, on patched single-fix kernel 6.5-DEVELOPMENT #1): sizeof(struct ieee80211_rssadapt_node) = 144 Write landed at offset 140 within the allocation. struct end is at offset 144. IN-BOUNDS: write at off 140 is within the struct (ends 144). No OOB. Canary intact: YES harness exit code: 0
PoC changes
Authored the evidence pack from scratch (the finding had only a DB row, no markdown/PoC folder). Wrote df0730_harness.c (deterministic code-level harness mirroring the exact kernel struct ieee80211_rssadapt_node layout + rssadapt_lower_rate write logic, with a 64-byte poison canary to detect OOB; parameterized by THRESH_COLS so the same source proves the bug at [8] and the fix at [15]); build.sh (buggy/fixed modes); run.sh; fix.diff (resize ra_rate_thresh to [IEEE80211_RATE_MAXSIZE] + update the 3 uint16_t (*thrs)[] typedefs at ieee80211_rssadapt.c:241/278/297); VERDICT.md, README.md, manifest.json.
Verified recommended fix
Resize the array to match its index space and fix the three typedefs that bake the wrong row stride: in sys/netproto/802_11/ieee80211_rssadapt.h:66 change ra_rate_thresh[IEEE80211_RSSADAPT_BKTS][IEEE80211_RATE_SIZE] -> [IEEE80211_RATE_MAXSIZE]; in sys/netproto/802_11/wlan/ieee80211_rssadapt.c:241,278,297 change uint16_t (*thrs)[IEEE80211_RATE_SIZE] -> [IEEE80211_RATE_MAXSIZE]. Matches the finding's proposed fix. Full git-apply-able diff in findings/poc/DF-0730/fix.diff.
Verdict
REPRODUCED (code-level harness). The bug is REAL at the source level: sys/netproto/802_11/ieee80211_rssadapt.h:65-66 declares ra_rate_thresh[3][IEEE80211_RATE_SIZE=8] but rs_nrates can be up to IEEE80211_RATE_MAXSIZE=15 (ieee80211_setup_rates merges Supported+XRATES, capped at 15, ieee80211_input.c:413-436; copied verbatim at ieee80211_rssadapt.c:198). rssadapt_rate (:253-254) loops rix=rs_nrates-1 (up to 14) and reads (thrs)[rix]; rssadapt_lower_rate (:285) and rssadapt_raise_rate (:310) WRITE (thrs)[rix]/[rix+1]; thrs=&ra_rate_thresh[bucket(pktlen)] and bucket=2 for pktlen>1024. For bucket=2,rix=14 the access is 12 bytes PAST the struct end (heap OOB). gcc itself confirms it: building the harness against the [8] dim emits 'array subscript 14 is above array bounds of uint16_t[8]' (-Warray-bounds), and the canaried harness shows the write at offset 112 vs struct end 104 -> 10 bytes OOB, canary clobbered, exit 1. LATENT on this guest: no WiFi radio (ifconfig -l => vtnet0 lo0), wlan_rssadapt is 'optional wlan_rssadapt' (sys/conf/files:1655) NOT in the default X86_64_GENERIC kernel, and NO in-tree driver calls ieee80211_ratectl_set(vap,RSSADAPT) -- default is AMRR (ieee80211_ratectl.c:122). If any driver selected RSSADAPT + a peer advertised >8 rates (every 11g AP), one TX-complete on a >1024-byte frame would corrupt the per-node slab -> remote unauthenticated kernel heap OOB read+write. Today dormant; Medium severity is correct.
No comments yet.