DF-3317 / verdict.json
{ "finding_id": "DF-3317", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "tcp6_ctlinput() dispatches ICMPv6 Packet-Too-Big to tcp_mtudisc() with no sequence-number validation and no RFC 8200 1280-octet minimum-MTU floor (tcp_subr.c:1582-1590, 1626-1630), so an off-path blind forger needs only the victim 4-tuple. A forged PTB with mtu=60 drove t_maxseg/t_maxopd to 0 (verified via net.inet.tcp.pcblist; with timestamp options the negative mss is clamped to garbage t_maxseg=419430), and the next close of that connection executed tcp_subr.c:1013s i = (i + tp->t_maxseg/2)/tp->t_maxseg with t_maxseg==0, producing Fatal trap 18: integer divide fault ... Stopped at tcp_close+0x66e: divq (system-down panic, current process=Idle/netisr; twice reproduced on the stock INVARIANTS kernel; two further runs where the fault hit the closing syscall thread delivered signal 11 to the process). fix.diff adds the RFC-mandated if (arg < IPV6_MMTU) goto out; and was validated by in-guest nativekernel rebuild: patched kernel leaves t_maxseg unchanged (14336) on the identical PoB, close returns cleanly, and legitimate PTB mtu=1300 still shrinks MSS to 1240.", "exploit_chain": "remote off-path IPv6 spoofer -> forged ICMPv6 PTB (type 2, mtu=60, garbage th_seq never validated) quoting victim 4-tuple -> icmp6_notify_error -> tcp6_ctlinput:1575-1590 (raw icmp6_mtu) -> in6_pcbnotify -> tcp_mtudisc:1820-1871 -> maxopd=0, t_maxseg=0 (u_int) -> victim closes conn with t_rttupdated>=16 and ssthresh-save block entered -> tcp_close:1013 integer division by t_maxseg==0 -> Fatal trap 18 (#DE) -> kernel panic (system down). No memory-write primitive: impact ceiling is remote unauthenticated kernel panic / persistent per-connection MSS corruption (blind), incl. tiny-MSS amplification for mtu in (60,1279] and DF-0338-class garbage t_maxseg for mtu in [41,59].", "evidence": [ "run.log: after-PTB : t_state=5 t_maxseg=0 t_maxopd=0 on stock kernel #0 (and fix_run.log shows it unchanged on patched #1)", "panic.txt / panic_console_full.txt: 12x tcpcb ...: MSS (0) too small to hold options! + Fatal trap 18: integer divide fault while in kernel mode + Stopped at tcp_close+0x66e: divq %r8,%eax + current process = Idle (two fatal reproductions; two signal-11 process-kill variants logged at 17:16:16 and 17:17:53)", "fix.diff: the IPV6_MMTU floor in tcp6_ctlinputs PRC_MSGSIZE branch", "fix_inspect.log/fix_run.log: patched kernel - t_maxseg 14336 before AND after PTB, close returns rc=0, dmesg MSS(0) count 0", "fix_sanity.log: patched kernel still honors legitimate PTB mtu=1300 (t_maxseg 14336->1240)", "VERDICT.md: full narrative incl. the raw-socket-is-injection-only privilege-model note" ], "kernel_refs": [ "sys/netinet/tcp_subr.c:1575", "sys/netinet/tcp_subr.c:1582", "sys/netinet/tcp_subr.c:1584", "sys/netinet/tcp_subr.c:1626", "sys/netinet/tcp_subr.c:1628", "sys/netinet/tcp_subr.c:1820", "sys/netinet/tcp_subr.c:1852", "sys/netinet/tcp_subr.c:1868", "sys/netinet/tcp_subr.c:1871", "sys/netinet/tcp_subr.c:1013", "sys/netinet/tcp_subr.c:945", "sys/netinet/tcp_subr.c:1509", "sys/netinet6/icmp6.c:1039", "sys/netinet6/icmp6.c:1078", "sys/netinet/tcp_var.h:223" ], "poc_changes": "seed PoC assumptions fixed during verification: (1) AF_INET6 SOCK_RAW never transmits a caller-built IPv6 header (kernel prepends its own; raw_ip6.c:392-418 also computes the checksum) - the first build double-headered the packet and died as unknown icmp6 type (96); (2) TCP_NOOPT on the victim socket is required for t_maxseg==0 exactly (with timestamps, mss=0-12=-12 and the unsigned clamp at tcp_subr.c:1868 yields garbage 419430); (3) the ssthresh-save block must be entered at close - seeded via route change -inet6 -host ::1 -ssthresh 1024, modeling the organic cached state of any previously congested connection; (4) SO_LINGER{1,0} close (abort -> tcp_drop -> tcp_close) for a deterministic synchronous teardown; (5) pcblist parsing needs #define _KERNEL_STRUCTURES + sys/socketvar.h + -I/usr/src/sys.", "attempts": 8, "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": 4500, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -I/usr/src/sys -o ptb6 ptb6.c", "run_cmd": "route change -inet6 -host ::1 -ssthresh 1024 && ./ptb6 -p 49161 (./ptb6 -x ... for inspect-only)", "code_hash": "3ee6c719232761066c8ab4c6818cdbdd293c7debf22b6b7a48b2c667056a0b80", "notes": "Root is needed only for local raw-ICMPv6 injection as a stand-in for the off-path forger; the packet is otherwise identical and no local information beyond the 4-tuple is used (th_seq is garbage, proving the missing check). Impact ceiling is remote panic/DoS - t_maxseg corruption is protocol state, not a memory-write primitive, so uid0 escalation is not applicable. Intermediate mtu values give persistent blind MSS corruption of any IPv6 TCP connection. Recommended follow-up hardening beyond fix.diff: copy the quoted th_seq and apply the v4-style [snd_una,snd_max) check (RFC 5927) as tcp_ctlinput:1509-1511 does.", "recommended_fix": "tcp6_ctlinput PRC_MSGSIZE: discard Packet-Too-Big with next-hop MTU < IPV6_MMTU (RFC 8200 5.2 MUST); follow up with v4-style quoted-sequence validation", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Thu Sep 10 17:27:19 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (IPV6_MMTU floor) applied to /usr/src in-guest, make nativekernel + installkernel, reboot into #1. Identical PoC on patched kernel: after-PTB t_maxseg unchanged (14336/16324 vs 0 on baseline), zero MSS (0) dmesg entries, aborting close returns rc=0, guest healthy; legitimate PTB mtu=1300 still shrinks MSS to 1240 (PMTUD preserved). Baseline panic reproduced twice on stock #0 prior.", "fix_evidence": "fix_inspect.log, fix_run.log, fix_sanity.log, fix_build.log.tail, fix.diff" } |