⬢ DragonFlyBSD Kernel Audit
DF-3423 / verdict.json
← back to finding ↓ download raw
{
 "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"
}