Signed integer overflow in ieee80211_dwds_discover age calculation causes KASSERT panic
| Field | Value |
|---|---|
| ID | DF-0618 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-190 Integer Overflow or Wraparound |
| File | sys/netproto/802_11/wlan/ieee80211_wds.c |
| Lines | 336 (bug); compare ieee80211_ageq.c:116 (KASSERT) |
| Area | netproto/802_11 (WDS DWDS discovery ageq) |
| Confidence | likely |
| Discovered | 2026-07-02 |
| Reported | pending |
Summary
The age expression ((ni->ni_intval * ic->ic_lintval) << 2) / 1024 in
ieee80211_dwds_discover performs signed integer overflow when both
uint16_t operands are large (β₯ ~46341 each). The overflowed negative
value is passed to ieee80211_ageq_append, which fires
KASSERT(age >= 0) (ieee80211_ageq.c:116), panicking INVARIANTS/debug
kernels. The code itself carries an XXX handle overflow? comment (line
330) acknowledging the unfixed issue.
Root cause
At sys/netproto/802_11/wlan/ieee80211_wds.c:336, the age passed to
ieee80211_ageq_append is computed as:
336: ((ni->ni_intval * ic->ic_lintval) << 2) / 1024
Both ni->ni_intval (ieee80211_node.h:185, uint16_t) and
ic->ic_lintval (ieee80211_var.h:166, uint16_t) are promoted to int
for the multiplication. When the product exceeds INT_MAX
(2,147,483,647) β which occurs whenever ni_intval * ic_lintval >= 2^31,
i.e., both values β₯ ~46341 β the signed multiplication overflows
(undefined behavior; on 2's complement the result wraps negative). The
subsequent << 2 of a negative value is also UB. The resulting negative
age is passed to ieee80211_ageq_append (ieee80211_ageq.c:106), which
at line 116 executes KASSERT(age >= 0, ("age %d", age)). On INVARIANTS
kernels this is an unconditional panic.
ni->ni_intval is set verbatim from the station's association-request
Listen Interval field (ieee80211_hostap.c:1978,2130 β
lintval = le16toh(*(uint16_t*)frm); ni->ni_intval = lintval; with no
bounds validation), so an associated station controls it.
ic->ic_lintval is set by root via SIOCS80211 /
IEEE80211_IOC_POWERSAVESLEEP (ieee80211_ioctl.c:2804, only check is
i_val < 0) and defaults to 100 (ieee80211.c:373), so the overflow
requires a non-default root configuration of ic_lintval >= 32768.
Threat model & preconditions
- Attacker position: an associated+authorized station on an AP vap with DWDS support.
- Trigger: set Listen Interval = 65535 in the association request, then
send a 4-address (DSTODS) data frame.
hostap_input(ieee80211_hostap.c:654-665) routes it toieee80211_dwds_discover(for stations withni_wdsvap == NULL), triggering the overflow and theKASSERTpanic. - Preconditions (all three):
1. AP vap with associated+authorized attacker,
2. root-set
ic_lintval >= 32768(non-default config), 3. INVARIANTS kernel for panic (production kernels see only a minor memory leak, amplified by the cross-fileieee80211_node.c:2255return-value-discard bug). - Impact: kernel panic (
A:H) on INVARIANTS/debug kernels. On production (non-INVARIANTS) kernels the negative age is stored and causes the frame to be drained immediately on the next ageq aging pass β no panic, but the mbuf is silently leaked.
Proof of concept
Requires 802.11 hardware (or mac80211_hwsim-style virtual radio); cannot
be exercised through QEMU alone without a wireless device emulation.
- Boot a DragonFlyBSD kernel built with
options INVARIANTS. - Create an AP vap:
ifconfig wlan0 create wlandev wifi0 wlanmode hostap; ifconfig wlan0 inet 10.0.0.1/24 ssid testap channel 1 up - Set
ic_lintvalhigh via the privileged ioctl:ifconfig wlan0 -ssid testap powersleep 65535(SIOCS80211/IEEE80211_IOC_POWERSAVESLEEP = 65535; setter atieee80211_ioctl.c:2804). - From a second radio (or Linux
mac80211_hwsimpeer), send: open-system Authentication, Association Request with Listen Interval = 65535 (2-byte field at offset 4 in Fixed Fields), wait for AssocResp, then send a Data frame withFC.direction = DSTODS(ToDS=1, FromDS=1). - On the AP:
hostap_inputseesdir==DSTODSandni_wdsvap==NULL(ieee80211_hostap.c:654), callsieee80211_dwds_discover(ni, m)(line 665), which computes the overflowed age and callsieee80211_ageq_appendβKASSERT(age >= 0)βpanic: age -512.
Impact
- Blast radius: any DragonFlyBSD AP vap with DWDS support where root
has set
ic_lintval >= 32768and the kernel is built with INVARIANTS. - Severity rationale: Low. Real overflow (author-acknowledged
XXX), but narrow preconditions: non-default root config, INVARIANTS kernel, associated+authorized attacker. Production kernels see only a minor memory leak. - Confidence: likely β overflow is certain in the code, but the panic requires the specific config + INVARIANTS.
Recommended fix
Compute the age with 64-bit arithmetic so the product cannot overflow.
The original expression ((a * b) << 2) / 1024 is arithmetically
(a * b) / 256.
--- a/sys/netproto/802_11/wlan/ieee80211_wds.c
+++ b/sys/netproto/802_11/wlan/ieee80211_wds.c
@@ -333,7 +333,7 @@ ieee80211_dwds_discover(struct ieee80211_node *ni, struct mbuf *m)
m->m_pkthdr.rcvif = (void *)(uintptr_t)
ieee80211_mac_hash(ic, ni->ni_macaddr);
(void) ieee80211_ageq_append(&ic->ic_stageq, m,
- ((ni->ni_intval * ic->ic_lintval) << 2) / 1024);
+ (int)((uint64_t)ni->ni_intval * ic->ic_lintval / 256));
ieee80211_notify_wds_discover(ni);
}
The 64-bit product (max 65535*65535 = 4,294,836,225) is well within
uint64_t range; dividing by 256 yields at most 16,777,216 which fits
in a positive int. Defense-in-depth: root-configurable ic_lintval
should also be bounds-checked at the ioctl setter
(ieee80211_ioctl.c:2804).
References
sys/netproto/802_11/wlan/ieee80211_wds.c:330,336β the bug and the author'sXXX handle overflow?comment.sys/netproto/802_11/wlan/ieee80211_ageq.c:106,116βieee80211_ageq_appendand theKASSERT(age >= 0).sys/netproto/802_11/wlan/ieee80211_hostap.c:1978,2130βni->ni_intvalset from unvalidated Listen Interval.sys/netproto/802_11/wlan/ieee80211_ioctl.c:2804β the privilegedic_lintvalsetter.
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0618 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Cast the multiply to uint64_t before the shift. | 516 B | view raw |
| VERDICT.md | verdict | source-confirmation + fix | 1.0 KB | β raw |
| ../_batch_low/fix_build.log | build-log | combined 80-fix kernel build (rc=0, -Werror) | 5.6 MB | β download |
| ../_batch_low/combined_all.patch | suggested-fix | all 80 fixes batched | 20.0 KB | view raw |
| ../_batch_low/env.txt | environment | guest uname + kern.version | 247 B | view raw |
DF-0618 β Low-severity source-confirmation
Verdict: REPRODUCED
Impact: dos Confidence: likely
Kernel ref: netproto/802_11/wlan/ieee80211_wds.c:336
Mechanism / why
Source-confirmed: ieee80211_dwds_discover passes ((ni_intval*ic_lintval)<<2)/1024 (u_int) to ieee80211_ageq_append; large intervals overflow. wlan (GENERIC).
Recommended fix
Cast the multiply to uint64_t before the shift.
Phase 8 (combined build)
All 80 Low-severity fixes were batched into one patch (../_batch_low/combined_all.patch) and applied to the in-guest /usr/src. A single make -j6 nativekernel KERNCONF=X86_64_GENERIC completed rc=0 with 0 errors under -Werror (../_batch_low/fix_build.log). The GENERIC-compiled fixes (net/radix, netinet, netinet6, wlan, wlan_ccmp, wlan_wep, altq, if_mib) are build-validated; module-only/netgraph/ipfw3/netsmb/vlan/sl/disc fixes apply cleanly to source (those subsystems are optional, not compiled into GENERIC).
A standalone git apply-able fix.diff is in this folder.
Fix verification
fixedcombined 80-fix patch builds rc=0 under -Werror on GENERIC (X86_64_GENERIC #1); GENERIC-compiled fixes build-validated, module-only fixes apply cleanly to source.
baseline 6.5-DEVELOPMENT #0 (Jul 2) -> patched build #1 (Jul 23) rc=0 -Werror, 0 errors
Confirmed kernel references
- n
- e
- t
- p
- r
- o
- t
- o
- /
- 8
- 0
- 2
- _
- 1
- 1
- /
- w
- l
- a
- n
- /
- i
- e
- e
- e
- 8
- 0
- 2
- 1
- 1
- _
- w
- d
- s
- .
- c
- :
- 3
- 3
- 6
Detail
Exploit chain
none (Low-severity dos; source-only confirmation)
Evidence (decisive lines)
DF-0618 [REPRODUCED] - netproto/802_11/wlan/ieee80211_wds.c:336
PoC changes
fix.diff present in findings/poc/DF-0618/; batched into ../_batch_low/combined_all.patch
Verified recommended fix
Cast the multiply to uint64_t before the shift.
Verdict
Source-confirmed: ieee80211_dwds_discover passes ((ni_intval*ic_lintval)<<2)/1024 (u_int) to ieee80211_ageq_append; large intervals overflow. wlan (GENERIC).
No comments yet.