DF-3363 / verdict.json
{ "finding_id": "DF-3363", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "maxrate() dereferences se_ies.htcap_ie without checking the IEs declared length; a beacon whose trailing HTCAP IE is shorter than 16 bytes makes the kernel read up to 16 bytes past the end of the exactly-sized kmalloc(ies_len) heap blob, and the out-of-bounds bytes demonstrably steer AP selection: with 0xff groomed beyond the allocation a 5.5 Mbps phantom-MCS31 HT40 AP beat a real 54 Mbps AP and was joined (iv_bss 02:22:22:22:22:22); with 0x00 the correct AP won. Reproduced on the stock INVARIANTS kernel with byte-level proof of adjacency (victim blob and groom chunk differ by exactly 0x40); fixed in-guest and re-verified (fix.diff: length check in maxrate; phantom selection gone, valid traffic unaffected).", "exploit_chain": "unauthenticated 802.11 beacon during scanning -> sta_add saves raw IE blob -> ies_expand exposes htcap_ie for any-length HTCAP IE -> select_bss/sta_compare -> maxrate reads hc_cap/hc_mcsset up to 17 bytes into a 2-byte trailing IE = 16 bytes past the heap allocation -> OOB bytes fabricate HT rates -> sta station joins the wrong/attacker AP. Impact ceiling: kernel-heap content oracle influencing association; no write primitive, no uid0 route for this class.", "evidence": [ "run.log: variant differentials (0xff groom -> B joined / 0x00 -> A joined / valid-HTCAP -> A joined)", "run.log: entry blobs at ..d240 (victim) and ..d280 (groom) differ by exactly 0x40; htcap_ie off 62 ie_len 0 -> all 18 maxrate bytes past the 64-byte allocation", "fix_validation.log: post-fix matrix - phantom selection eliminated, controls unchanged" ], "kernel_refs": [ "sys/netproto/802_11/wlan/ieee80211_scan_sta.c:380", "sys/netproto/802_11/wlan/ieee80211_scan_sta.c:775", "sys/netproto/802_11/wlan/ieee80211_scan_sta.c:787", "sys/netproto/802_11/wlan/ieee80211_scan_sta.c:797", "sys/netproto/802_11/wlan/ieee80211_scan_sta.c:799", "sys/netproto/802_11/wlan/ieee80211_node.c:983", "sys/netproto/802_11/wlan/ieee80211_input.c:733" ], "poc_changes": "harness written fresh (fwscan.ko software radio, adapted from the DF-3282 pattern); vap creation via the ifconfig clone path (in-kernel creation deadlocks without the wlan serializer); single-channel radio so curchan never moves; IE defines in-source (kmod opt_wlan.h generation yields an empty file); candidate re-injection before scan_end to defeat background-scan se_notseen bumps; ic lock only around scan_end (scan_signal asserts it is not held).", "attempts": 6, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 root:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline); #1/#2 custom builds for instrumentation/fix", "runtime_sec": 4800, "guest_dirty": 0, "build_cmd": "sh findings/poc/DF-3363/build.sh (make SYSDIR=/usr/src/sys in guest, /root/fwscan/fwscan.ko)", "run_cmd": "ifconfig wlan create wlandev fws0 wlanmode sta up; sysctl kern.fwscan_test=1|2|3", "code_hash": "54859c80a66d25ec2d056f0dec66495fd2fc3b3c665fe93c13f0283adf0f847f", "notes": "leak-grade: the join decision is an oracle on heap bytes past the allocation (0xff vs 0x00 -> opposite outcomes). No panic, no direct disclosure. DF-0394 (known) covers the se_rates/se_ssid copies; this is a different sink (the saved-blob consumers). The same root cause powers DF-3364 (tdma_ie).", "recommended_fix": "validate htcap_ie length (>= sizeof(struct ieee80211_ie_htcap)-2) in maxrate(); see fix.diff (also length-validates the TDMA IE consumers and clamps tdma slotcnt).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #2 Fri Sep 11 12:44:59 UTC 2026 root:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "with fix.diff applied (in-guest nativekernel build #2) the identical PoC no longer shows OOB-dependent behavior: variant 1 now selects the correct 54 Mbps AP (A) like the controls; short HTCAP/TDMA IEs are rejected as malformed; a full-length valid TDMA IE (v17) is still accepted. fix.diff is git-apply-able against the read-only sys/ tree and was never applied there.", "fix_evidence": "findings/poc/DF-3363/fix_validation.log" } |