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)
PoC verification
Evidence pack
findings/poc/DF-3576 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
iwn_timing_div0.cexecutes the exact arithmetic ofiwn_set_timing()(copied verbatim from if_iwn.c:5590-5598) withni_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 hardwarediv/idivfault β in the kernel it is a fatal #DE.- 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.
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
- 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 bymod = le64toh(cmd.tstamp) % val;with no zero check. -iwn_set_timing()is reached fromiwn_run()(if_iwn.c:7237) on the IEEE80211_S_RUN transition (association complete). -ni->ni_intvalis 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). - Noteieee80211_init_neighbor()(node.c:1525) copiessp->bintvalunvalidated too β same latent issue for other drivers/modes. - Runtime primitive.
iwn_timing_div0.cruns the verbatim arithmetic withni_intval == 0on the audit guest (DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC, gcc 8.3): - control (ni_intval=100): survives, prints a sanebinitval. - 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)
- 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.
- Victim scans: scan cache entry gets
se_intval = 0(validation bit ignored). - Victim associates:
ieee80211_sta_joinβni->ni_intval = 0. 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_testablefix.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
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.
No comments yet.