⬢ DragonFlyBSD Kernel Audit
DF-3295 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-3295",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "Reproduced on the stock INVARIANTS guest: SIOCS80211 IEEE80211_IOC_SCAN_REQ with sr_nssid=1 and sr_ssid[0].len=-1 (signed int passes the len > IEEE80211_NWID_LEN check at ieee80211_ioctl.c:2496) reaches memcpy(vap->iv_scanreq_ssid[0].ssid, sr->sr_ssid[0].ssid, (size_t)-1) in the S_INIT stash branch at ieee80211_ioctl.c:2538-2540 -> Fatal trap 12, supervisor write, Stopped at memcpy+0xfa repe movsq, guest down. Preconditions arranged by the PoC: STA vap UP (ic_nrunning!=0) and pushed back to S_INIT via MLME DISASSOC (ieee80211_ioctl.c:1396-1400). Primitive: unbounded linear kernel-heap copy; destination starts inside the ieee80211vap (overwrites the vap tail incl. function pointers iv_newstate/iv_reset/iv_key_*), source is the 128-byte fully user-controlled copyind scan_req then adjacent heap. Entry gated by SYSCAP_NONET_WIFI (ieee80211_ioctl.c:3472) so no unprivileged path exists; ceiling is delegated wifi-cap -> heap-groomed controlled-pointer corruption -> kernel compromise, demonstrated floor is a deterministic privileged panic. Distinct from DF-0642 (signed sr_nssid -> scan.c copy_ssid sink, non-INIT branch); this is the signed sr_ssid[].len -> ioctl.c memcpy sink, INIT branch. fix.diff (reject len<0 and nssid<0 with EINVAL) validated on an in-guest rebuilt kernel: PoC returns EINVAL on every attempt, guest stays up, valid scan request still accepted (rc=0).",
 "exploit_chain": "wifi-cap holder (SYSCAP_NONET_WIFI, e.g. delegated wpa_supplicant operator): (1) create/bring up STA vap, (2) MLME DISASSOC to park vap in S_INIT while UP, (3) SCAN_REQ with sr_ssid[0].len=-1 -> (size_t)-1 memcpy from attacker-controlled kmallocd scan_req into vap->iv_scanreq_ssid[0].ssid, (4) linear overwrite of the ieee80211vap tail (function pointers) and adjacent heap with ~128 attacker-controlled bytes followed by heap contents, (5) heap-groom a victim object behind the vap, overwrite a pointer, pivot on a vap callback (iv_newstate/iv_reset) -> kernel code exec -> full root. Demonstrated stop: deterministic kernel panic (write fault) at step 3-4 on the stock guest. No unprivileged entry path exists (SIOCS80211 privilege gate), so no uid0-by-unprivileged chain is possible for this bug.",
 "evidence": [
  "findings/poc/DF-3295/run.log.baseline: firing SCAN_REQ: sr_nssid=1 sr_ssid[0].len=-1 followed by guest death mid-loop",
  "findings/poc/DF-3295/panic.txt: Fatal trap 12: page fault while in kernel mode, fault virtual address = 0xfffff80118830000, supervisor write data, current process = 1229, Stopped at memcpy+0xfa: repe movsq",
  "findings/poc/DF-3295/VERDICT.md: full narrative + fix-validation transcript (patched kernel: scanreq -> -1 (errno 22) x11, guest up, valid request rc=0)"
 ],
 "kernel_refs": [
  "sys/netproto/802_11/wlan/ieee80211_ioctl.c:2495",
  "sys/netproto/802_11/wlan/ieee80211_ioctl.c:2536",
  "sys/netproto/802_11/wlan/ieee80211_ioctl.c:2538",
  "sys/netproto/802_11/wlan/ieee80211_ioctl.c:2544",
  "sys/netproto/802_11/wlan/ieee80211_ioctl.c:3472",
  "sys/netproto/802_11/ieee80211_ioctl.h:788"
 ],
 "poc_changes": "fw.c/Makefile reused verbatim from the DF-3282 software-radio harness. scanreq.c written new: MLME DISASSOC ioctl (park STA vap in S_INIT while UP) then SCAN_REQ with sr_ssid[0].len=-1 in a 10x retry loop (first iteration typically hits the non-INIT branch and returns EINPROGRESS=36). One compile fix vs first draft: added <errno.h>.",
 "attempts": 2,
 "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",
 "runtime_sec": 420,
 "guest_dirty": 0,
 "build_cmd": "sh findings/poc/DF-3295/build.sh (in-guest: make SYSDIR=/usr/src/sys; cc -O -o scanreq scanreq.c)",
 "run_cmd": "sh findings/poc/DF-3295/run.sh (kldload fw.ko; ifconfig wlan create wlandev fw0 wlanmode sta up; /root/fw3295/scanreq wlan0)",
 "code_hash": "385d16fa202a086dc413b2fdc6cfba58e2aa1cd103acdcad973beffc2f3c1475",
 "notes": "Non-INIT branch also passes the signed len into ieee80211_start_scan/ieee80211_check_scan; ieee80211_scan_copy_ssid (ieee80211_scan.c:305) struct-copies it (no immediate overflow) but the negative len stays resident in scan state for probe-request builders -- fixed by the same len<0 rejection. Signed sr_nssid sibling is DF-0642 (scan.c sink); fix.diff also rejects sr_nssid<0 at the ioctl dispatch for defense in depth. Guest reset (vm.sh reset with-src) after validation.",
 "recommended_fix": "In ieee80211_scanreq() reject negative values before use: if (sr->sr_nssid < 0) return EINVAL; and if (sr->sr_ssid[i].len < 0 || sr->sr_ssid[i].len > IEEE80211_NWID_LEN) return EINVAL;",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Thu Sep 10 07:24:45 UTC 2026   root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "fix.diff applied to guest /usr/src, kernel rebuilt (make nativekernel KERNCONF=X86_64_GENERIC) and booted: the exact PoC that panicked the stock kernel now returns EINVAL (errno 22) on every attempt, no trap/panic, guest stays up, wlan0 functional, and a valid scan request (len=4) is still accepted (rc=0). Baseline bad behavior gone.",
 "fix_evidence": [
  "findings/poc/DF-3295/fix.diff",
  "findings/poc/DF-3295/fix_build.log (kernel build/install + KLD rebuild on patched kernel)",
  "VERDICT.md fix-validation transcript: scanreq -> -1 (errno 22) x11 + valid scanreq -> 0 + guest still up"
 ]
}