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

Remote kernel panic: division by zero in iwn_set_timing() via AP beacon with bintval=0 (net80211 stores the invalid value anyway)

Summary

iwn_set_timing() computes val = (uint64_t)ni->ni_intval * 1024 and then le64toh(cmd.tstamp) % val with no zero check. ni_intval arrives unvalidated from the beacon/probe-response bintval: ieee80211_input.c parses it and flags IEEE80211_BPARSE_BINTVAL_INVALID, but the STA scan consumer never checks that bit (ieee80211_scan_sta.c:314 se_intval = sp->bintval) and ieee80211_sta_join copies it into the node. On association (iwn_run) a bintval=0 AP yields % 0 -> #DE -> kernel panic. Unauthenticated adjacent-network attacker (evil-twin AP or injected beacons with a spoofed BSSID of a network the victim auto-joins); one 16-bit field in a beacon reliably panics any iwn-equipped DragonFly station that joins. VERIFIED: verbatim-arithmetic harness - control (ni_intval=100) survives, ni_intval=0 dies SIGFPE exit 136, 4/4 deterministic runs on the guest (runtime hardware divide, not compiler-folded). Full in-kernel repro impossible on virtio-only guest -> inconclusive/panic. Fix: driver-side zero-guard + net80211 BPARSE_BINTVAL_INVALID rejection in sta_add_scan (protects every wifi driver).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3576 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
iwn_timing_div0.c β€” 2.8 KB view raw
build.sh β€” 104 B view raw
run.sh β€” 98 B view raw
run.log β€” 284 B view raw
run.2.log β€” 516 B view raw
env.txt β€” 321 B view raw
fix.diff β€” 586 B view raw
verdict.json β€” 3.4 KB view raw
manifest.json β€” 1.0 KB view raw

DF-3576 β€” Divide-by-zero kernel panic in iwn_set_timing() via beacon with bintval=0

What this is

Evidence pack for a remote-triggerable kernel division-by-zero (panic) in sys/dev/netif/iwn/if_iwn.c:5596-5597 (iwn_set_timing()).

Root cause (source trace, all lines verified in this tree)

attacker AP beacons with bintval = 0 (one 16-bit field in an 802.11 beacon)
  -> sys/netproto/802_11/wlan/ieee80211_input.c:545   scan->bintval = le16toh(...)
  -> ieee80211_input.c:700-707    marks IEEE80211_BPARSE_BINTVAL_INVALID
     ...but the STA scan-table consumer NEVER checks that status bit:
  -> sys/netproto/802_11/wlan/ieee80211_scan_sta.c:314  ise->se_intval = sp->bintval;
  -> sys/netproto/802_11/wlan/ieee80211_node.c:818      ni->ni_intval = se->se_intval;   (ieee80211_sta_join)
  -> station completes association (evil-twin AP answers auth/assoc)
  -> iwn_newstate RUN -> iwn_run (if_iwn.c:7237) -> iwn_set_timing (if_iwn.c:5583)
  -> if_iwn.c:5596   val = (uint64_t)ni->ni_intval * IEEE80211_DUR_TU;   == 0
  -> if_iwn.c:5597   mod = le64toh(cmd.tstamp) % val;                    % 0
  -> integer divide error (#DE) in kernel mode -> kernel panic

What this pack proves (and what it cannot)

The QEMU audit guest is virtio-only: it has if_iwn.ko and the driver registered (kldstat -v shows pci/iwn) but no Intel WiFi device, so the in-kernel path cannot be executed end-to-end. Therefore:

  1. iwn_timing_div0.c executes the exact arithmetic of iwn_set_timing() (copied verbatim from if_iwn.c:5590-5598) with ni_intval = 0 β€” the value that a bintval=0 beacon produces through the net80211 chain above. Result: SIGFPE (exit 136), 4/4 runs; the control value (ni_intval = 100) survives. A compile-time-folded division cannot fault, so this is a genuine hardware div/idiv fault β€” in the kernel it is a fatal #DE.
  2. The reachability chain (beacon -> scan cache -> node -> iwn_run) is proven by the source citations above; every hop was read in this tree.

Build / run (on the DragonFly guest)

cc -O2 -Wall -o iwn_timing_div0 iwn_timing_div0.c
./iwn_timing_div0        # expect: control OK, then "Floating point exception", exit 136

Expected output

See run.log β€” control prints survived, malicious call prints ni_intval=0 -> val=0 ; taking modulo and the process dies with SIGFPE (exit=136). Stress reruns in run.2.log.

Fix

Guard the divisor (mirrors what the driver already does elsewhere, e.g. iwn4965_get_temperature() guards r1 == r3): see fix.diff.

VERDICT.md
↓ download raw

DF-3576 β€” VERDICT

Status: inconclusive (runtime), source-certain (analysis). Impact: kernel panic (#DE). Reproduced: the fault primitive, yes; the kernel path, not on this guest.

What was verified and how

  1. Source-level certainty. Every hop of the chain was read and cited: - sys/dev/netif/iwn/if_iwn.c:5596-5597 β€” val = (uint64_t)ni->ni_intval * IEEE80211_DUR_TU; followed by mod = le64toh(cmd.tstamp) % val; with no zero check. - iwn_set_timing() is reached from iwn_run() (if_iwn.c:7237) on the IEEE80211_S_RUN transition (association complete). - ni->ni_intval is populated from the scan cache: sys/netproto/802_11/wlan/ieee80211_scan_sta.c:314 (ise->se_intval = sp->bintval;) β€” the only validation of bintval, IEEE80211_BPARSE_BINTVAL_INVALID (ieee80211_input.c:700-707), is set by the parser but never tested by this consumer (the only status-bit consumers in the tree are ieee80211_hostap.c:1707 and the OFFCHAN branches). ieee80211_sta_join() then copies it unfiltered into the node (sys/netproto/802_11/wlan/ieee80211_node.c:818). - Note ieee80211_init_neighbor() (node.c:1525) copies sp->bintval unvalidated too β€” same latent issue for other drivers/modes.
  2. Runtime primitive. iwn_timing_div0.c runs the verbatim arithmetic with ni_intval == 0 on the audit guest (DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC, gcc 8.3): - control (ni_intval=100): survives, prints a sane binitval. - malicious (ni_intval=0): SIGFPE, exit 136 β€” deterministic across 4 runs (run.log, run.2.log). Since constant-folded divisions do not fault, this is a real hardware divide fault; in kernel context the same instruction raises #DE and panics the system.

Why not "reproduced"

The guest has no iwn hardware (pciconf -l shows no Intel WiFi device; virtio-only), so the driver's RX/association path cannot be driven end-to-end. The honest classification is: primitive proven at runtime, full chain proven by source trace, end-to-end kernel panic not demonstrated on this guest.

Exploit chain (attack)

  1. Attacker runs an evil-twin AP (or injects beacons with a spoofed BSSID of a network the victim auto-joins) whose beacons/probe responses carry bintval = 0.
  2. Victim scans: scan cache entry gets se_intval = 0 (validation bit ignored).
  3. Victim associates: ieee80211_sta_join β†’ ni->ni_intval = 0.
  4. iwn_newstate(RUN) β†’ iwn_run β†’ iwn_set_timing β†’ % 0 β†’ kernel panic.

No memory disclosure, no code execution: impact ceiling is a reliable remote panic/DoS of any iwn-equipped DragonFly station that joins the attacker's AP.

Fix

fix.diff β€” reject (or clamp) a zero beacon interval in iwn_set_timing before the division. The complementary net80211 hardening (reject IEEE80211_BPARSE_BINTVAL_INVALID entries in sta_add_scan) would protect every driver, not just iwn.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

fix.diff authored against the read-only sys/ tree; not kernel-validated because the guest cannot execute the iwn driver path (no hardware). Baseline fault primitive reproduced (SIGFPE, exit 136); the guard makes the function return EINVAL instead of dividing.

findings/poc/DF-3576/fix.diff
↓ fix.diffper-fix-DF-3576

Confirmed kernel references

Detail

Exploit chain

evil-twin AP beacons bintval=0 -> victim scan cache se_intval=0 (BINTVAL_INVALID never checked) -> ieee80211_sta_join: ni_intval=0 -> victim associates -> iwn_newstate RUN -> iwn_run (if_iwn.c:7237) -> iwn_set_timing: 'tstamp % 0' -> #DE -> kernel panic (remote DoS).

Evidence (decisive lines)

run.log (control survives; malicious call prints 'ni_intval=0 -> val=0 ; taking modulo' then exit=136 SIGFPE), run.2.log (3/3 stress), iwn_timing_div0.c (verbatim arithmetic copy), VERDICT.md (full hop-by-hop source trace), fix.diff.

PoC changes

No seed existed; harness authored in this run. Verbatim copy of the iwn_set_timing arithmetic (if_iwn.c:5590-5598) plus a control run with ni_intval=100.

Verified recommended fix

Reject ni_intval==0 (return EINVAL) in iwn_set_timing before the modulo; also honor IEEE80211_BPARSE_BINTVAL_INVALID in ieee80211_scan_sta.c sta_add_scan.

Verdict

Remote-triggerable kernel divide-by-zero: iwn_set_timing() (if_iwn.c:5596-5597) computes 'val = ni_intval * 1024' then 'tstamp % val' with no zero check; ni_intval==0 reaches it because net80211 parses beacon bintval (ieee80211_input.c:545), flags BPARSE_BINTVAL_INVALID (ieee80211_input.c:700-707) but the STA scan consumer stores it anyway (ieee80211_scan_sta.c:314) and ieee80211_sta_join copies it into the node (ieee80211_node.c:818); association then drives iwn_run->iwn_set_timing. The verbatim arithmetic faulted with SIGFPE (exit 136) deterministically 4/4 runs on the DragonFly guest (a runtime hardware divide, not a compiler fold), and the control value survived. Full in-kernel repro was impossible: the audit guest is virtio-only and has no Intel WiFi device (if_iwn.ko exists, no 8086 wifi PCI device), so classification is inconclusive-at-runtime / certain-at-source.