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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0730 Β· 14 files
FileTypeDescriptionSize
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
README.md readme reproduce instructions
↓ download 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_rate write 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: resize ra_rate_thresh to [IEEE80211_RATE_MAXSIZE]
  • update the 3 thrs typedefs.
  • VERDICT.md β€” full narrative + mechanism + citations.
  • build.log / run.log β€” baseline (buggy) build+run on the unpatched #0 kernel.
  • fix_run.log β€” fixed-dim build+run on the patched #1 kernel.
  • fix_build.log β€” full make nativekernel log for the single-fix kernel.
  • fix_install.log β€” make installkernel log.
  • 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 emits array 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.

VERDICT.md verdict full narrative: mechanism, citations, reachability, fix validation
↓ download raw

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)

  1. The array is too small for its index space. - sys/netproto/802_11/ieee80211_rssadapt.h:65-66 declares uint16_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.

  2. rs_nrates can 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 at IEEE80211_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), so ra->ra_rates.rs_nrates ∈ [0, 15].

  3. The rateset drives the OOB index. - rssadapt_rate :253-254 loops for (rix = rs->rs_nrates-1; rix >= 0; rix--) and reads (*thrs)[rix] β€” rix can be 14. - rssadapt_lower_rate :285 writes (*thrs)[rix] = interpolate(...). - rssadapt_raise_rate :310 writes (*thrs)[rix+1] (up to index 15). - thrs = &ra->ra_rate_thresh[bucket(pktlen)] (:249/:281/:301), and bucket() returns 2 for pktlen > 1024 (:220-233, IEEE80211_RSSADAPT_BKT0 * 2^BKTPOWER = 128*8 = 1024). - The typedef uint16_t (*thrs)[IEEE80211_RATE_SIZE] at :241/:278/:297 bakes the wrong (8) row stride into every dereference.

  4. Worst case β†’ heap OOB. For bucket=2, rix=14: (*thrs)[14] = ra->ra_rate_thresh[2][14], which is 6 Γ— uint16_t (12 bytes) past ra->ra_rate_thresh[2][7], i.e. past the end of the struct ieee80211_rssadapt_node (it is the last field). Since the node is kmalloc(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 for rix=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); no wlan_* modules loaded. Net80211 is never instantiated with a live vap.
  • Default kernel: wlan_rssadapt is optional wlan_rssadapt (sys/conf/files:1655) β€” not compiled into X86_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:671 seeds 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-bounds warning (see build.log, run.log).
  • Patched single-fix kernel: applied fix.diff to /usr/src, built make -j6 nativekernel KERNCONF=X86_64_GENERIC (fix_build.log, === NK_DONE rc=0 ===), installed via make installkernel, rebooted to 6.5-DEVELOPMENT #1 (Thu Jul 9 01:07:37 UTC 2026), sha256 d28b91cedf66….
  • After: harness built against the fixed dim (THRESH_COLS=15) β†’ write in-bounds (offset 140 < struct end 144), canary intact, exit 0, no -Warray-bounds warning (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 rix to IEEE80211_RATE_SIZE-1 in 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/kernel is rejected by the loader ("Unable to load /kernel/kernel"); use make installkernel instead (it installs the full debug kernel ELF).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Thu Jul 9 01:07:37 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC (sha256 d28b91cedf66f8f8ff3e4900fa4f441939b1cf40adbc6ed52d0fff409e3876ad)

Confirmed kernel references

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.