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

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 to ieee80211_dwds_discover (for stations with ni_wdsvap == NULL), triggering the overflow and the KASSERT panic.
  • 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-file ieee80211_node.c:2255 return-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.

  1. Boot a DragonFlyBSD kernel built with options INVARIANTS.
  2. Create an AP vap: ifconfig wlan0 create wlandev wifi0 wlanmode hostap; ifconfig wlan0 inet 10.0.0.1/24 ssid testap channel 1 up
  3. Set ic_lintval high via the privileged ioctl: ifconfig wlan0 -ssid testap powersleep 65535 (SIOCS80211 / IEEE80211_IOC_POWERSAVESLEEP = 65535; setter at ieee80211_ioctl.c:2804).
  4. From a second radio (or Linux mac80211_hwsim peer), 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 with FC.direction = DSTODS (ToDS=1, FromDS=1).
  5. On the AP: hostap_input sees dir==DSTODS and ni_wdsvap==NULL (ieee80211_hostap.c:654), calls ieee80211_dwds_discover(ni, m) (line 665), which computes the overflowed age and calls ieee80211_ageq_append β†’ KASSERT(age >= 0) β†’ panic: age -512.

Impact

  • Blast radius: any DragonFlyBSD AP vap with DWDS support where root has set ic_lintval >= 32768 and 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.

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

Timeline

  • 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
  • 2026-07-02 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0618 Β· 5 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-confirmation + fix
↓ download 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).

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

fixed
baseline reproduced→ patch + rebuild →patched clean

combined 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
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Thu Jul 23 06:52:07 UTC 2026

Confirmed kernel references

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).