gre_mobile_input(): neither M_HASH nor M_LENCHECKED cleared, inner ip_len double-counts the outer IP header (ANY valid mobile-GRE packet panics INVARIANTS kernels), and a truncated datagram yields a negative-length bcopy β reproduced as an unbounded kernel-heap overwrite (mbuf-metadata smash, secondary panic in m_free)
Summary
gre_mobile_input() recomputes ip->ip_len as ntohs(ip_len)+sizeof(struct ip)-msiz although DragonFly delivers the FULL outer datagram length (the stale 'rip_input already decreased' premise), so every decapped packet claims 20 bytes more than the mbuf holds; it requeues without clearing M_HASH or M_LENCHECKED so ip_input trusts the lie (KASSERT 'mbuf too short' for any valid packet); and for a datagram shorter than outer-header+msiz the bcopy length goes negative and is passed to bcopy as (size_t)-8. VERIFIED: mobile-mode tunnel (ifconfig gre0 -link0 up - one flag away); a single WELL-FORMED 28-byte packet panics ('mbuf too short'); one 20-byte truncated packet executes an unbounded ascending kernel-heap copy - 'panic: overflowed mbuf' with a SECONDARY PANIC in m_free<-sosendtcp (the copy corrupted a neighbouring mbuf's metadata): a genuine unauthenticated remote heap-corruption primitive on production kernels, not merely DoS. Fix validated (presence guard + corrected ip_len arithmetic + clear both flags): both variants + 20 stress injections dropped, guest up.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3423 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| gre_poc.c | β | 9.9 KB | view raw | |
| setup.sh | β | 602 B | view raw | |
| run.sh | β | 241 B | view raw | |
| build.sh | β | 100 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.mobile.log | β | 215 B | view raw | |
| run.mobileuflow.log | β | 215 B | view raw | |
| run.patched.log | β | 3.4 KB | view raw | |
| panic.mobile.txt | β | 303 B | view raw | |
| panic.mobileuflow.txt | β | 620 B | view raw | |
| fix.diff | β | 2.1 KB | view raw | |
| env.txt | β | 290 B | view raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| verdict.json | β | 5.7 KB | view raw |
DF-3423 β VERDICT
Status: reproduced (impact: panic β including a demonstrated unbounded
kernel-heap bcopy overwrite effective on any kernel). Confidence: certain.
What was run
Same guest/kernel/module as DF-3422 (env.txt). Tunnel put in MOBILE mode with
ifconfig gre0 -link0 up (gre_ioctl SIOCSIFFLAGS β g_proto =
IPPROTO_MOBILE, if_gre.c:461-470; encap re-attached with
in_mobile_protosw, if_gre.c:563-566).
Baseline results
mobileβ outer ip_len=28 (20 IP + 8-byte mobile header, S-bit clear, checksum crafted to 0). This is a well-formed minimal mobile-GRE packet; the decap'sip->ip_len = ntohs(...)+sizeof(struct ip)-msiz(ip_gre.c:247) claims 40 bytes for a 20-byte mbuf (the "+20" double-counts the outer header; the comment's rip_input premise does not hold on DragonFly), and since neitherM_HASHnorM_LENCHECKEDis ever cleared (ip_gre.c:259-261),ip_inputtrusts it:panic: mbuf too shortβip_input+0xfaf(KASSERTip_input.c:576), backtraceip_input β netmsg_service_loop(panic.mobile.txt). Deterministic for ANY valid mobile-GRE packet β mobile tunnels cannot work on INVARIANTS kernels; on production kernels every decapped packet is processed with ip_len 20 bytes longer than the data (in-cluster OOB reads by transports).mobileuflowβ outer ip_len=20 (NO mobile header; the 8 "header" bytes are read from the frame padding past ip_len, checksum still valid). msiz=8 βbcopy(src, dst, m->m_len - msiz - (ip->ip_hl<<2))=bcopy(..., -8)(ip_gre.c:237-238) β(size_t)-8:panic: overflowed mbuf 0xfffff80117e0c400withSECONDARY PANIC ON CPU 0inm_free β sosendtcp β soo_write β kern_pwritev(panic.mobileuflow.txt): the unbounded copy had smashed neighbouring kernel heap objects β the ssh session's own mbuf came back with corrupted metadata. On a production kernel the m_free INVARIANTS check is absent: this is a silent, unbounded, attacker-initiated kernel-heap overwrite (source bytes partially attacker-controlled via the frame), i.e. a genuine memory-corruption primitive, not just a DoS.
Fix validation
fix.diff (shared with DF-3422, ip_gre.c only): guard
m->m_len >= (ip->ip_hl<<2) + msiz before the checksum/bcopy;
ip->ip_len corrected to ntohs(ip->ip_len) - msiz;
M_HASH|M_LENCHECKED cleared before netisr_queue.
Rebuilt if_gre.ko, re-ran both variants + 20 stress injections:
no panic, guest up, dmesg clean (run.patched.log). fix_status: fixed.
Escalation note
The mobileuflow primitive (huge ascending kernel copy whose source prefix
is attacker-controlled frame bytes) is the strongest of the batch; turning it
into a controlled uid=0 chain would require heap-layout steering of the copy
direction/extent β the bcopy copies upward through the direct map with a
fixed relationship between src and dst (dst = src - msiz), so every byte
between them is preserved while everything above the source range is
overwritten by higher memory β a destructive smear rather than a surgical
write. We characterize it and stop there; the fix removes the primitive
entirely.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedfix.diff applied to /usr/src in-guest, if_gre.ko rebuilt and installed. Both baseline-panicking variants re-run byte-identically plus 20 stress injections: no panic, no trap, guest up, dmesg clean (run.patched.log).
run.patched.log (patched module, all variants rc=0, STRESS_20_INJECTIONS_DONE, uptime healthy); panic.mobile.txt/panic.mobileuflow.txt (baseline); fixbuild.log; fix.diff
Confirmed kernel references
Detail
Exploit chain
tap frame (proto 55) -> ether_input -> outer validated, M_LENCHECKED+M_HASH set -> ip_input -> transport_processing_oncpu -> encap4_input on netisr0 (in_mobile_protosw, attached by gre_ioctl recompute when LINK0 is cleared) -> gre_lookup(IPPROTO_MOBILE) match -> gre_mobile_input: [mobileuflow] ip_len=20 -> mobile 'header' read from frame padding past the data end, checksum crafted valid from the same padding -> msiz=8 -> bcopy(dst=ip+20, src=ip+28, (size_t)(20-8-20)) = huge ascending kernel copy -> neighbouring kernel-heap objects overwritten -> ssh session's mbuf metadata corrupted -> secondary panic in m_free (INVARIANTS) / silent heap corruption on production kernels. [mobile] valid 28-byte packet -> bcopy len 0 -> ip_len := 28+20-8 = 40 with m_pkthdr.len 20 -> stale M_HASH/M_LENCHECKED -> ip_input skips characterization -> KASSERT(pkthdr.len >= ip_len) panic 'mbuf too short'.
Evidence (decisive lines)
["panic.mobile.txt: 'panic: mbuf too short' ip_input+0xfaf <- netmsg_service_loop (baseline, VALID mobile packet)", "panic.mobileuflow.txt: 'panic: overflowed mbuf 0xfffff80117e0c400' + SECONDARY PANIC in m_free<-sosendtcp<-soo_write<-kern_pwritev (baseline, negative bcopy)", 'run.patched.log: fixed if_gre.ko - both variants + 20 stress injections, no panic, guest up', 'fixbuild.log + fix.diff: guard m->m_len >= (ip->ip_hl<<2)+msiz, ip_len -= msiz only, clear M_HASH|M_LENCHECKED', 'run.mobile.log / run.mobileuflow.log: injection runs (ssh hang at tool timeout = guest frozen at ddb, see pack note)']
PoC changes
mobile hcrc must be stored native-endian (gre_in_cksum sums native u_short words); first attempt stored big-endian and the packet was checksum-rejected (visible in run.mobile.log's first attempt 'cksum(kernel-mirror)=0xa659'); the mobileuflow variant relies on the ethernet frame padding past ip_len carrying a checksum-valid mobile header -- both deterministic with tap injection.
Verified recommended fix
In gre_mobile_input(): require m->m_len >= (ip->ip_hl<<2)+msiz before checksum/bcopy, compute ip->ip_len = htons(ntohs(ip->ip_len) - msiz), and clear M_HASH|M_LENCHECKED before netisr_queue() (see fix.diff).
Verdict
REPRODUCED on the stock INVARIANTS guest, gre0 in MOBILE mode (ifconfig gre0 -link0 up). gre_mobile_input() (sys/netinet/ip_gre.c:204-263) has three stacked defects: (1) ip->ip_len is recomputed as ntohs(ip_len)+sizeof(struct ip)-msiz although DragonFly delivers the FULL outer datagram length (the comment's 'rip_input already decreased' premise is stale NetBSD behaviour), so the inner header claims 20 bytes more than the mbuf holds FOR EVERY well-formed mobile-GRE packet; (2) neither M_HASH nor M_LENCHECKED is ever cleared before netisr_queue (ip_gre.c:259-261), so ip_input trusts the lying length -- a single VALID minimal mobile packet panics the default kernel: 'panic: mbuf too short' at ip_input.c:576 (mobile). (3) the bcopy length 'm->m_len - msiz - (ip->ip_hl<<2)' goes negative for a datagram shorter than outer-header+msiz and is passed to bcopy as (size_t)-8 (ip_gre.c:237-238) -- 'mobileuflow' reproduced as 'panic: overflowed mbuf 0xfffff80117e0c400' with a SECONDARY PANIC in m_free<-sosendtcp<-soo_write: the unbounded copy had smashed neighbouring kernel-heap objects (the ssh session's mbuf came back corrupted). That is an unbounded attacker-initiated kernel-heap overwrite effective on production kernels too (the m_free INVARIANTS check merely makes it visible). Fix (header-presence guard, corrected ip_len arithmetic, M_HASH|M_LENCHECKED cleared) validated by rebuilding if_gre.ko in-guest: both variants + 20 stress injections harmlessly dropped, guest up.
No comments yet.