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)
PoC verification
Evidence pack
findings/poc/DF-0580 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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) βmis 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_testablecompile 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.
No comments yet.