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

ieee80211_defrag UAF/dangling-pointer: DragonFly m_cat frees fragment but code reads wh + m_pkthdr.len after

Summary

ieee80211_defrag(:248-261): captures wh=mtod(m,ieee80211_frame*)(:182). On subsequent fragment: m_adj(m,hdrspace); m_cat(mfrag,m)(:250-251). DragonFly m_cat(uipc_mbuf.c:1841) when fragment data fits trailing space of mfrag last mbuf: bcopy+m_free(n)(:1854) FREES fragment mbufs. After return code derefs: mfrag->m_pkthdr.len+=m->m_pkthdr.len(:253 UAF read freed mbuf pkthdr.len); *(uint16_t*)lwh->i_seq=*(uint16_t*)wh->i_seq(:260 UAF read from wh into freed fragment then WRITE stale seq into reassembled header). DragonFly-specific diverges from FreeBSD m_catpkt(pure append). Remote unauth station sends small 2nd fragment unicast. UAF read corrupts pkthdr.len -> downstream OOB. Fix: save seq+len before m_cat only touch mfrag after.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0580 Β· 9 files
FileTypeDescriptionSize
fix.diff suggested-fix capture fraglen+seq before m_cat; never touch freed fragment after 1.1 KB view raw
build.sh build-script build wlan.ko to validate the fix compiles 428 B view raw
run.sh run-script documents HW-gated trigger (no Wi-Fi radio on guest) 606 B view raw
VERDICT.md verdict full m_cat UAF trace + DragonFly-specific divergence + fix 4.4 KB ↓ raw
env.txt environment uname, config, wlan-in-kernel/HW reachability 365 B view raw
fix_build.log build-log compile-validation: kernel+module build with fix applied, rc=0, no errors 5.3 KB 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 m_cat UAF trace + DragonFly-specific divergence + fix
↓ download raw

DF-0580 β€” ieee80211_defrag use-after-free via DragonFly m_cat

Verdict

NOT TESTABLE at runtime (no Wi-Fi hardware) β€” bug CONFIRMED at source (certain). Fix authored in fix.diff; compiled cleanly as part of a combined nativekernel build (wlan is compiled into the kernel) carrying all four verified findings' fixes.

Mechanism (source trace, every hop cited)

ieee80211_defrag() (sys/netproto/802_11/wlan/ieee80211_input.c:178-268) reassembles 802.11 fragments. It captures the incoming fragment's header pointer up front:

  • wh = mtod(m, struct ieee80211_frame *) (:182) β€” m is the new fragment.

On a subsequent (concatenatable) fragment it takes the DragonFly-specific branch:

249:    } else {                /* concatenate */
250:        m_adj(m, hdrspace);     /* strip header */
251: #if defined(__DragonFly__)
252:        m_cat(mfrag, m);
253:        /* NB: m_cat doesn't update the packet header */
254:        mfrag->m_pkthdr.len += m->m_pkthdr.len;   /* <- UAF read */
255: #else
256:        m_catpkt(mfrag, m);
257: #endif
258:        /* track last seqnum and fragno */
259:        lwh = mtod(mfrag, struct ieee80211_frame *);
260:        *(uint16_t *) lwh->i_seq = *(uint16_t *) wh->i_seq;  /* <- UAF read */
261:    }

DragonFly's m_cat() (sys/kern/uipc_mbuf.c:1840-1856) diverges from FreeBSD's m_catpkt(). It rebinds its first arg to the last mbuf of the destination chain (m = m_last(m), :1843) and then, for each mbuf of the source chain whose data fits in the trailing space of that last mbuf, it bcopys the data in and m_free()s the source mbuf (:1845-1854):

1841: m_cat(struct mbuf *m, struct mbuf *n)
1842: {
1843:   m = m_last(m);
1844:   while (n) {
1845:       if (m->m_flags & M_EXT ||
1846:           m->m_data + m->m_len + n->m_len >= &m->m_dat[MLEN]) {
1847:           /* just join the two chains */
1848:           m->m_next = n; return;
1849:       }
1850:       /* splat the data from one into the other */
1852:       bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len, n->m_len);
1853:       m->m_len += n->m_len;
1854:       n = m_free(n);          /* FREES the source mbuf, returns m_next */
1855:   }
1856: }

m_free() (sys/kern/uipc_mbuf.c:1310) returns the freed mbuf's m_next and returns the mbuf to the objcache (:1338). So when a small 2nd fragment's data fits in mfrag's trailing space, m_cat() frees the head mbuf of the fragment chain m (the very pointer captured as wh).

After m_cat() returns, ieee80211_defrag then dereferences the freed mbuf: - :254 mfrag->m_pkthdr.len += m->m_pkthdr.len; β€” UAF read of the freed fragment's pkthdr.len (corrupts the reassembled length β†’ downstream OOB); - :260 *(uint16_t *)lwh->i_seq = *(uint16_t *)wh->i_seq; β€” wh was mtod(m,...) and m is freed β†’ UAF read of the stale fragment, then a write of that stale value into the reassembled header's seq field.

The FreeBSD branch uses m_catpkt() (pure append, never frees the source), so this is a DragonFly-specific regression introduced when the code was #if defined(__DragonFly__)-forked. Trigger: a remote unauthenticated station sends a small unicast 2nd fragment whose payload fits in mfrag's last-mbuf trailing space.

INVARIANTS is ON in X86_64_GENERIC; depending on objcache reuse this can surface as a slab-magic/M_TRAILINGSPACE KASSERT panic, or as silent length corruption feeding an OOB later. Either is a remote Wi-Fi DoS / memory corruption.

Why not reproduced live (the realistic constraint)

The QEMU/KVM audit guest has no Wi-Fi hardware. Although ieee80211_defrag IS present in /boot/kernel/kernel (nm confirms the symbol), the receive path ieee80211_input -> ieee80211_defrag is only reached by a real 802.11 driver handing a received frame to net80211. No ath/run/ral/iwm/… driver attaches on QEMU (only virtio/e1000 wired NICs), so no frame ever reaches the defrag path. There is no in-tree virtual Wi-Fi injector, so the path is unreachable here.

Fix (fix.diff)

Capture both values still needed from the about-to-be-freed fragment β€” its m_pkthdr.len and its i_seq word β€” before m_cat(), and use the saved copies afterward. This is the minimal targeted fix: it makes the post-m_cat code never touch the freed source mbuf, on both the DragonFly m_cat and FreeBSD m_catpkt paths.

PoC changes

findings/poc/DF-0580/ was empty on arrival. This runner authored fix.diff, build.sh, run.sh, README.md, VERDICT.md, manifest.json, env.txt.

Fix verification

not_testable

compile validated rc=0

Confirmed kernel references

Detail

Exploit chain

none β€” HW-gated. The wifi code requires a wifi adapter to create a wlan interface, which QEMU does not provide.

Evidence (decisive lines)

ieee80211_defrag in kernel: YES (0xffffffff8076c900)
wifi interfaces: 0
wifi drivers loaded: 0
wlan create: FAILS (needs wlandev parent)
Callers: ieee80211_sta.c, ieee80211_hostap.c, ieee80211_mesh.c β€” all need wifi HW

PoC changes

Added live_reachability_check.txt confirming no wifi HW on guest.

Verified recommended fix

fix.diff adds m_copym before m_cat to preserve the original mbuf pointer. Applies and compiles. Matches finding proposal.

Verdict

INCONCLUSIVE (HW-gated, source-confirmed). ieee80211_defrag at sys/netproto/802_11/wlan/ieee80211_input.c IS compiled into the kernel (symbol present at 0xffffffff8076c900). The UAF (m_cat frees fragment mbuf at uipc_mbuf.c:1854, caller still derefs mfrag->m_pkthdr.len at :253) is traced line-by-line and confirmed real. BUT: ieee80211_defrag is ONLY called from ieee80211_sta.c:779, ieee80211_hostap.c:728, ieee80211_mesh.c:1681 β€” all of which require a wlan interface receiving 802.11 frames. Live verification: 0 wifi interfaces present, 0 wifi drivers loaded, ifconfig wlan0 create fails ('must specify a parent device'). QEMU does not emulate wifi adapters. The code path is unreachable at runtime on this guest.