DF-3423 / verdict.json
{ "finding_id": "DF-3423", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "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 comments 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 sessions 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.", "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 sessions 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": [ "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)" ], "kernel_refs": [ "sys/netinet/ip_gre.c:223-240", "sys/netinet/ip_gre.c:242-250", "sys/netinet/ip_gre.c:259-262", "sys/net/gre/if_gre.c:461-470", "sys/net/gre/if_gre.h:134-148", "sys/netinet/ip_input.c:576" ], "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.logs 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.", "attempts": 5, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1800, "guest_dirty": 1, "build_cmd": "cd /root/df3422 && cc -O -o gre_poc gre_poc.c", "run_cmd": "sh /root/df3422/setup.sh mobile && /root/df3422/gre_poc mobile # then reboot + rerun with mobileuflow", "code_hash": "24c2621193dca48ee526d3d0df1d12997e3e4ad8fc2883749468883e32415955", "notes": "MOBILE mode (IPPROTO_MOBILE, RFC 2004 minimal encapsulation) is a non-default opt-in (ifconfig gre0 -link0); the demonstrated unbounded bcopy primitive is the strongest of the batch (works on production kernels), but it is a destructive upward smear (dst = src - msiz, everything above the source range overwritten by higher memory) rather than a surgical write, so a uid=0 chain was not pursued; the fix removes the primitive entirely. panic: overflowed mbuf is the INVARIANTS m_free check catching the corruption the bcopy caused one hop away -- proof of cross-object kernel heap damage from one packet.", "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).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (same kernel; patched if_gre.ko module rebuilt from the same tree, kldstat-confirmed)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.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).", "fix_evidence": "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" } |