DF-3283 / verdict.json
{ "finding_id": "DF-3283", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "ieee80211_fragment() computes fragsize = min(totalhdrsize+remainder, iv_fragthreshold) with the DEFAULT threshold IEEE80211_FRAG_MAX=2346 > MCLBYTES=2048; each fragment is built in a 2048-byte cluster and m_align(m, fragsize-ciphdrsize) computes a negative adjust, moving m_data ~288 bytes BEFORE the cluster before ~2346 controlled bytes are copied (heap underflow on release kernels). On the stock INVARIANTS guest the guard KASSERT fires: panic: fragment size 2346 too big! from ieee80211_encap+0xb01, reached via the exported ieee80211_vap_pkt_send_dest() with a single 5000-byte frame on a C_TXFRAG-advertising vap. Unprivileged reach is currently blocked only by the SIOCSIFMTU clamp to IEEE80211_MTU_MAX=2290 (frames stay < ~4.1KB); privileged callers (BPF write, in-kernel callers) bypass it, hence Low severity despite the corruption-class primitive. fix.diff clamps mtu to MCLBYTES-1; validated by kernel rebuild: same trigger now returns cleanly (fw.fire: returned (no panic)) twice, guest stays up.", "exploit_chain": "privileged: BPF write / in-kernel mbuf >= ~4.1KB to ieee80211_vap_pkt_send_dest on a TXFRAG driver (default fragthreshold 2346) -> ieee80211_encap -> ieee80211_fragment -> m_align negative offset -> ~262 bytes of packet-controlled data written before the mbuf cluster + ~2 bytes past it (release kernels) or KASSERT panic (INVARIANTS). No unprivileged trigger found: ifnet MTU clamp 2290 keeps stack frames under the trigger length.", "evidence": [ "panic.txt (panic: fragment size 2346 too big! + ieee80211_encap backtrace)", "run.log (transcript incl. patched no-panic runs)", "fw.c (C_TXFRAG software radio + kern.fw_fire sysctl pushing 5000B frame)", "build.log" ], "kernel_refs": [ "sys/netproto/802_11/wlan/ieee80211_output.c:1703", "sys/netproto/802_11/wlan/ieee80211_output.c:1727", "sys/netproto/802_11/wlan/ieee80211_output.c:1804", "sys/netproto/802_11/wlan/ieee80211_output.c:1808", "sys/netproto/802_11/wlan/ieee80211_output.c:1817", "sys/kern/uipc_mbuf.c:1940", "sys/netproto/802_11/ieee80211.h:1330", "sys/netproto/802_11/ieee80211_var.h:85", "sys/netproto/802_11/wlan/ieee80211_ioctl.c:3483" ], "poc_changes": "No seed; harness authored from scratch (fw.c). Discovered during fix validation that clamping to exactly MCLBYTES still trips the strict < KASSERT (second panic, fragment size 2048 too big!) and corrected the fix to MCLBYTES-1.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline); patched #1: Thu Sep 10 04:41:20 UTC 2026", "runtime_sec": 8, "guest_dirty": 0, "build_cmd": "sh findings/poc/DF-3283/build.sh (make SYSDIR=/usr/src/sys in /root/fw)", "run_cmd": "kldload /root/fw/fw.ko; ifconfig wlan create wlandev fw0 wlanmode ap; ifconfig wlan0 ssid fwtest mode 11b channel 1 up; sysctl kern.fw_fire=1", "code_hash": "sha256 fw.c=87681503e55ec4780341b89f90c1f33243da3d34acc2df27746e005c14a5529f", "notes": "The release-kernel heap-underflow form was not executed (guest is INVARIANTS-only; a second no-INVARIANTS build was out of budget); the KASSERT panic plus line-level m_align analysis (uipc_mbuf.c:1940 adjust=2048-2338=-290) establishes the primitive. Fix validated on rebuilt kernel #1 with both fixes. Guest reset with-src afterwards.", "recommended_fix": "Clamp the fragment threshold to MCLBYTES-1 at the top of ieee80211_fragment() (or reject fragthreshold > MCLBYTES in the IEEE80211_IOC_FRAGTHRESHOLD ioctl) so m_align() can never compute a negative offset.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Thu Sep 10 04:41:20 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Patched kernel (make nativekernel + installkernel in-guest) with the MCLBYTES-1 clamp: the exact 5000-byte-frame trigger fired twice, fw.fire: returned (no panic), fragments built and freed through the transmit path, guest stayed up. Baseline stock kernel panicked at ieee80211_encap+0xb01. (First fix attempt clamping to MCLBYTES was insufficient due to the strict KASSERT and was corrected.)", "fix_evidence": "panic.txt (baseline), run.log (patched no-panic runs), serial console fw.fire: returned (no panic) x2 with vm.sh status => up." } |