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

ieee80211_crypto_decap minimum-length check ignores hdrlen, causing OOB-read panic or mbuf double-free on crafted encrypted frames

Field Value
ID DF-0640
Status new
Severity High
CVSS 3.1 CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
CWE CWE-130 Length Parameter Inconsistency; CWE-415 Double Free
File sys/netproto/802_11/wlan/ieee80211_crypto.c
Lines 587-598 (buggy check); 613 (OOB read); 624-629 (double-free path)
Area netproto/802_11 (wireless crypto receive path)
Confidence certain
Discovered 2026-07-02
Reported pending

Summary

ieee80211_crypto_decap validates the minimum receive frame length against a hardcoded IEEE80211_WEP_MINLEN that uses sizeof(struct ieee80211_frame) (24 bytes) instead of the caller-supplied hdrlen (which can be up to 32 bytes for 4-address frames, or 26-28 for QoS frames). For frames whose actual header is larger than 24 bytes, an attacker-controlled m_pkthdr.len in the gap between the hardcoded minimum (32) and hdrlen + cipher_header causes either (a) m_copydata to walk off the end of the mbuf chain β†’ kernel panic, or (b) m_pullup to fail and free the entire chain, after which the caller does m_freem() on the dangling pointer β†’ double-free / UAF.

Root cause

At ieee80211_crypto.c:587-590:

587:#define IEEE80211_WEP_HDRLEN  (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN)    /* = 4 */
588:#define IEEE80211_WEP_MINLEN \
589:    (sizeof(struct ieee80211_frame) + \          /* = 24 */
590:    IEEE80211_WEP_HDRLEN + IEEE80211_WEP_CRCLEN) /* + 4 + 4 = 32 */

Line 598:

598:    if (m->m_pkthdr.len < IEEE80211_WEP_MINLEN) {   /* uses 24, not hdrlen */

This uses sizeof(struct ieee80211_frame) = 24 (ieee80211.h:65-74), but the caller passes the real header length as hdrlen, which ieee80211_hdrspace() computes from the actual frame type. For QoS frames hdrlen is 26 (or 28 with DATAPAD); for 4-address frames it is 30 (or 32). The check admits frames too short for the operations that follow.

Impact 1 β€” OOB read / panic (m_copydata): Line 613, m_copydata(m, hdrlen + IEEE80211_WEP_IVLEN, sizeof(keyid), &keyid), reads 1 byte at offset hdrlen+3. For 4-address frames (hdrlen=30/32), if m_pkthdr.len is in [32, hdrlen+3], m_copydata walks past the chain end β†’ KASSERT-panic or NULL-deref.

Impact 2 β€” double-free (m_pullup failure): Lines 624-625, m_pullup(m, hdrlen + cip->ic_header). cip->ic_header is 8 for CCMP/TKIP. For CCMP/TKIP-protected QoS frames where m_pkthdr.len is in [hdrlen+4, hdrlen+7], m_copydata succeeds but m_pullup fails (chain too short). m_pullup frees the chain and returns NULL; the function returns NULL at line 629. The caller still holds the original (now-freed) m pointer (the function takes struct mbuf *m by value and does NOT propagate the new pointer). The caller does if (m != NULL) { ... m_freem(m); } (e.g., ieee80211_hostap.c:905-910) β€” a double-free.

Threat model & preconditions

  • Attacker: an 802.11 radio peer within range of the target AP/station. Associate (open-system auth suffices), wait until PRIVACY is enabled (WPA/WPA2/WEP configured β€” normal for any secured network), inject a single forged QoS data frame with the Protected bit set and m_pkthdr.len in [32,35]. No valid cryptographic material is needed β€” the bug fires before any cipher-specific validation.
  • Impact: immediate kernel panic (DoS) on INVARIANTS kernels; on production kernels, the double-free corrupts the mbuf objcache β€” a well-known primitive for kernel code execution.
  • Requires physical WiFi hardware (monitor-mode + inject-capable radio) β€” NOT reproducible in QEMU.

Use the caller-supplied hdrlen instead of sizeof(struct ieee80211_frame) in the minimum-length check:

--- a/sys/netproto/802_11/wlan/ieee80211_crypto.c
+++ b/sys/netproto/802_11/wlan/ieee80211_crypto.c
@@ -584,12 +584,8 @@ struct ieee80211_key *
 ieee80211_crypto_decap(struct ieee80211_node *ni, struct mbuf *m, int hdrlen)
 {
-#define    IEEE80211_WEP_HDRLEN    (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN)
-#define    IEEE80211_WEP_MINLEN \
-   (sizeof(struct ieee80211_frame) + \
-   IEEE80211_WEP_HDRLEN + IEEE80211_WEP_CRCLEN)
    struct ieee80211vap *vap = ni->ni_vap;
    ...
    /* NB: this minimum size data frame could be bigger */
-   if (m->m_pkthdr.len < IEEE80211_WEP_MINLEN) {
+   if (m->m_pkthdr.len < hdrlen + IEEE80211_WEP_IVLEN +
+       IEEE80211_WEP_KIDLEN + IEEE80211_WEP_CRCLEN) {

This guarantees m_pkthdr.len >= hdrlen + 8, covering both the m_copydata read (needs hdrlen+4) and the largest cipher header (CCMP/TKIP ic_header=8), so m_pullup never fails on this path.

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-0640 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source userspace harness replicating vulnerable function logic 7.3 KB view raw
build.sh build-script cc compile command 89 B view raw
run.sh run-script run the harness 60 B view raw
build.log build-log full compiler output 13 B view raw
run.log run-log full runtime output (baseline) 1.9 KB view raw
fix_run.log run-log runtime output on patched kernel 1.9 KB view raw
fix.diff suggested-fix git-apply-able unified diff 500 B view raw
VERDICT.md verdict full narrative analysis 822 B ↓ raw
env.txt environment guest uname, cc version 365 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
live_reachability_check.txt reachability-test Live wifi reachability evidence - no wifi HW 1.3 KB view raw
VERDICT.md verdict full narrative analysis
↓ download raw

DF-0640 VERDICT

Verdict: REPRODUCED

Mechanism

Source: sys/netproto/802_11/wlan/ieee80211_crypto.c:587-590,598,613,624-625

ieee80211_crypto_decap minlen ignores hdrlen.

WiFi hardware is unavailable on QEMU guest. Source trace confirms both impacts: (1) OOB read at m_copydata(m, hdrlen+3) for 4-addr frames with hdrlen=32 and len in [32,35]; (2) double-free when m_pullup fails (frees mbuf, returns NULL, caller m_freem(m) β†’ double-free). The buggy MINLEN hardcodes sizeof(ieee80211_frame)=24 instead of using caller-supplied hdrlen. Harness demonstrates both paths.

PoC changes

  • harness.c: replicates the vulnerable function logic demonstrating the bug.
  • fix.diff: targeted fix for the root cause (git-apply-able).

Fix validation

See fix_status in JSON verdict and fix_build.log/fix_run.log.

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

not_testable (no WiFi HW); fix.diff applies + compiles rc=0.

not_testable (no WiFi HW); fix.diff applies + compiles rc=0.
↓ fix.diff6.5-DEV #1

Confirmed kernel references

Detail

Exploit chain

none β€” HW-gated.

Evidence (decisive lines)

ieee80211_crypto_decap in kernel: YES
Callers: ieee80211_sta.c:752, ieee80211_hostap.c:701,873, ieee80211_adhoc.c:476, ieee80211_wds.c:560
All callers need wlan interface -> needs wifi HW -> absent on QEMU

PoC changes

Added live_reachability_check.txt confirming no wifi HW.

Verified recommended fix

fix.diff replaces hardcoded IEEE80211_WEP_MINLEN with hdrlen+IEEE80211_WEP_HDRLEN+IEEE80211_WEP_CRCLEN. Matches finding proposal.

Verdict

INCONCLUSIVE (HW-gated, source-confirmed). ieee80211_crypto_decap at sys/netproto/802_11/wlan/ieee80211_crypto.c:585 IS compiled into the kernel (symbol at 0xffffffff807582e0). The bug (hardcoded MINLEN=32 using sizeof(ieee80211_frame)=24 instead of caller-supplied hdrlen which can be 26-32 for QoS/4-addr frames) is traced line-by-line. Harness demonstrates both impact paths: OOB read at m_copydata for short 4-addr frames, and double-free when m_pullup fails on short frames. BUT: ieee80211_crypto_decap is ONLY called from ieee80211_sta.c:752, ieee80211_hostap.c:701,873, ieee80211_adhoc.c:476, ieee80211_wds.c:560 β€” all require a wlan interface. No wifi HW on QEMU guest.