⬢ DragonFlyBSD Kernel Audit
DF-3283 / verdict.json
← back to finding ↓ download raw
{
 "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."
}