{
 "finding_id": "DF-3422",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "REPRODUCED on the stock INVARIANTS guest: gre_input2() (sys/netinet/ip_gre.c:178-193) computes the decap offset hlen purely from the attacker-controlled GRE flag bits (CP|KP|SP -> +12 bytes) with no presence check, so hlen can exceed the packet length and m->m_len -= hlen / m->m_pkthdr.len -= hlen go NEGATIVE (int underflow, ip_gre.c:181-182), and it clears only M_HASH (ip_gre.c:192), keeping the outer packets M_LENCHECKED so ip_hashfn() (ip_demux.c:295) skips ip_lengthcheck() and ip_input() trusts the inner header that lies past the data end (in the attacker-controlled ethernet frame padding inside the mbuf cluster). One unauthenticated GRE frame from the tunnel peer (or anyone spoofing the two fixed tunnel addresses) panics the default kernel twice over: fake inner IHL=15 -> panic complete IP header not in one mbuf at ip_input.c:539 (gre40); hlen>ip_len -> m_len=-12 flows to the same KASSERT (greuflow) -- note ip_input.c:463s always-on m_len<sizeof(ip) guard does a signed/unsigned comparison and does NOT catch the negative length, so on production kernels the negative-length/lying-length mbuf continues into ip_inputs body (options walk, ip_reass m_len -= hlen, transport demux by attacker-chosen ip_p, negative TCP tlen per DF-3293). DF-3321s landed fix covered gif+mpls+ip_mroute but left gres decap untouched. Fix (bound hlen by m_pkthdr.len + clear M_HASH|M_LENCHECKED in gre_input2) validated by rebuilding only if_gre.ko in-guest: both PoCs plus 20 stress injections dropped (ips_badhlen 0->1), guest stays up.",
 "exploit_chain": "tap frame -> ether_input -> ip_lengthcheck validates OUTER header, sets M_LENCHECKED -> ip_input -> transport_processing_oncpu -> encap4_input (proto 47, in_proto.c:219) on netisr0 -> gre_lookup match (iff src==g_dst, dst==g_src, IFF_UP) -> gre_input2: hlen=off+4+12 from flags, m_data += hlen (past data end into frame padding), m_len/m_pkthdr.len -= hlen (negative), clears M_HASH only -> netisr_queue(NETISR_IP) -> ip_hashfn sees stale M_LENCHECKED, skips ip_lengthcheck, hashes the fake inner header -> ip_input: version from fake byte 0x4F OK, hlen 60, KASSERT(m_len >= hlen) panic (INVARIANTS). Production-kernel continuation (traced, consistent with DF-3321/DF-3293 documentation): negative m_len passes the signed/unsigned guard at ip_input.c:463, lying ip_len/ip_hl/ip_p flow into ip_dooptions (option-area walk past the data), ip_reass (m_len -= hlen underflow) and tcp_input (negative tlen).",
 "evidence": [
  "panic.gre40.txt: panic: complete IP header not in one mbuf ip_input+0xd61 <- netmsg_service_loop (baseline, gre40)",
  "panic.greuflow.txt: same KASSERT via the m_len=-12 underflow variant (baseline, greuflow)",
  "run.patched.log: fixed if_gre.ko - both variants + 20 stress injections, no panic, guest up, STRESS_20_INJECTIONS_DONE, with header length < data size 0->1",
  "fixbuild.log: in-guest module rebuild of if_gre.ko with fix.diff (make/make install, sys/net/gre)",
  "fix.diff: hlen bounded by m_pkthdr.len + M_HASH|M_LENCHECKED cleared in gre_input2 (and the DF-3423 mobile hunks)",
  "run.gre40.log / run.greuflow.log: injection runs (ssh hang at tool timeout = guest frozen at ddb, see pack note)"
 ],
 "kernel_refs": [
  "sys/netinet/ip_gre.c:148-182",
  "sys/netinet/ip_gre.c:191-193",
  "sys/netinet/ip_demux.c:295-298",
  "sys/netinet/ip_input.c:463-468",
  "sys/netinet/ip_input.c:536-576",
  "sys/netinet/in_proto.c:219-222",
  "sys/netinet/ip_encap.c:130-254"
 ],
 "poc_changes": "gre_poc.c written fresh for gre (DF-3321s gif harness adapted to proto-47 GRE encapsulation with CP|KP|SP flag arithmetic); greuflow variant added to demonstrate the int underflow; mobile variants (see DF-3423) share the file. gre40 layout revised after source-trace: ip_input.c:463s m_len<20 gate requires m_len>=20 for the clean KASSERT-539 path, so outer ip_len=76 / fake IHL=15 (m_len 40 < hlen 60) instead of the initial 40/5 layout; mobile hcrc must be stored native-endian because gre_in_cksum() sums native u_short words (first attempt stored big-endian and was checksum-rejected).",
 "attempts": 3,
 "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": 3000,
 "guest_dirty": 1,
 "build_cmd": "cd /root/df3422 && cc -O -o gre_poc gre_poc.c",
 "run_cmd": "sh /root/df3422/setup.sh gre && /root/df3422/gre_poc gre40  # then reboot + rerun with greuflow",
 "code_hash": "24c2621193dca48ee526d3d0df1d12997e3e4ad8fc2883749468883e32415955",
 "notes": "gre(4) is the module if_gre.ko (not in X86_64_GENERIC); the audit file sys/net/gre/if_gre.c owns output/ioctl/clone, the defect is in its input twin sys/netinet/ip_gre.c compiled into the same module. GRE mode (IPPROTO_GRE) is the clone default (if_gre.c:205-206). Fix validation rebuilt only the module (defect is module-local); same kernel uname for baseline and patched runs, module identity confirmed via kldstat. The underflow variants production-kernel continuation (negative m_len into ip_input body) is traced but not executed on a production kernel; DF-3321 documented the equivalent continuation for gif.",
 "recommended_fix": "In gre_input2(): reject hlen > m->m_pkthdr.len before adjusting the mbuf, and clear M_LENCHECKED alongside M_HASH 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 (make; make install in sys/net/gre, fixbuild.log). Both baseline panicking variants re-run byte-identically plus 20 stress injections: no panic, guest up, inner packet rejected by ip_lengthcheck (netstat -s -p ip with header length < data size 0->1), dmesg free of the ip_input atomicy splat.",
 "fix_evidence": "run.patched.log (patched module, all variants rc=0, guest up, counter delta); fixbuild.log; panic.gre40.txt/panic.greuflow.txt (baseline panics); fix.diff"
}