DF-2610 / verdict.json
{ "finding_id": "DF-2610", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Source-level defect CONFIRMED verbatim in the current tree (guest /usr/src md5-identical to audit tree): icmp6.c:2160 takes ip6=mtod(m) BEFORE IP6_EXTHDR_CHECK at 2187, and the non-PULLDOWN branch compiles (no PULLDOWN_TEST anywhere in sys/), so nd_rd (2188), redtgt6/reddst6 (2196-2197) and ip6->ip6_hlim (2211) would dangle if the check's m_pullup reallocated the mbuf - violating the file's own invariant at icmp6.c:412. LIVE MANIFESTATION NOT REPRODUCIBLE from userspace on this kernel: (1) the dispatch copy at icmp6.c:787 strips M_LOOP (M_COPYFLAGS, mbuf.h:281-284), so the copy always takes the macro's non-pullup branches (ip6.h:285-297); (2) the M_LOOP original reaches icmp6_redirect_input only if that m_copym fails (cluster exhaustion); (3) the reallocating m_pullup path (uipc_mbuf.c:2125-2135) requires an M_EXT-or-no-room FIRST mbuf with off+icmp6len<=MHLEN, but sosend always sizes the first mbuf to the full send via m_getl (uipc_socket.c:866-887) and rip6_output's M_PREPEND (raw_ip6.c:326) splits off an internal header mbuf - so loopback chains are always [internal-hdr][cluster...] and take the in-place pullup (uipc_mbuf.c:2119-2124) that keeps ip6 valid; want>MCLBYTES makes m_pullup NULL and the macro frees+returns before line 2188. Live evidence: 70+ type-137 redirects of every shape (40..8000 bytes, multi-iov, bursts) traversed the full validation block (dmesg rejection log from icmp6.c:2241-2246; icp6s_inhist[137] 1->71) with zero panics/corruption. Defect is a latent class violation as filed (confidence: speculative); the one-line re-derive fix is warranted.", "exploit_chain": "", "evidence": "run.log: dmesg line 'ICMP6 redirect rejected; not equal to gw-for-src=::0001 (must be same): (src=fe80:0002::0001 dst=::0001 tgt=fe80:0002::)' (log at icmp6.c:2241-2246, i.e. execution passed the would-be-stale reads at 2196-2197); ic6stat inhist[137] 1->71 across all sends; 'ALIVE_AFTER_SENDS' + uptime after chain/burst shapes; raw137listen confirming wire src=fe80::1 scope=2 hlim=255.", "kernel_refs": [ "sys/netinet6/icmp6.c:2160", "sys/netinet6/icmp6.c:2186-2188", "sys/netinet6/icmp6.c:2196-2197", "sys/netinet6/icmp6.c:2211", "sys/netinet6/icmp6.c:412", "sys/netinet6/icmp6.c:781-793", "sys/netinet/ip6.h:277-306", "sys/kern/uipc_mbuf.c:2103-2153", "sys/sys/mbuf.h:281-284", "sys/kern/uipc_socket.c:866-887", "sys/netinet6/raw_ip6.c:326", "sys/net/if_loop.c:274" ], "poc_changes": "Standalone toolkit written from scratch (redir6 sender, ic6stat binary-stats decoder, raw137listen observer). Two material fixes found during verification: (1) sender originally used ICMPv6 type 139 (NI query) instead of 137 (ND_REDIRECT) - corrected after ic6stat inhist and the nodeinfo responder exposed the mix-up; (2) nd_rd_dst pointed at an unroutable address so rtpurelookup returned NULL silently - changed to the queried destination (local host route) to make the gateway-validation log fire as the traversal oracle.", "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": 300, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o redir6 redir6.c && cc -O2 -Wall -o ic6stat ic6stat.c && cc -O2 -Wall -o raw137listen raw137listen.c", "run_cmd": "sh run.sh (as root; net.inet6.icmp6.nd6_debug=1 enables the nd6log traversal oracle)", "code_hash": "23905d062baeeb618dee2b847199cdbb15521b3454b8febd5b5ed8a3b4e953c1", "notes": "Honest bottom line: the finding's CODE claim is true and verified line-by-line; its own 'not presently reproducible end-to-end' assessment is confirmed and extended with a complete reachability trace of why no userspace producer can build the required [M_EXT short first] chain into icmp6_redirect_input. Any future producer change (LRO/driver loopback chains) plus mbuf pressure would arm it; the 5-line fix removes the class.", "recommended_fix": "Re-derive ip6 = mtod(m, struct ip6_hdr *) immediately after IP6_EXTHDR_CHECK in icmp6_redirect_input, mirroring icmp6_input/icmp6_notify_error/icmp6_error (see fix.diff).", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "Fix kernel not built: the defect never manifests at runtime on this kernel (unreachable mbuf shape), so there is no observable behavioral delta to validate a patched kernel against. fix.diff is a mechanical pointer re-derivation identical in shape to the already-correct sibling functions (icmp6.c:420, 861, 978, 340); compile-correctness follows the same pattern.", "fix_evidence": "VERDICT.md reachability trace (uipc_socket.c:866 m_getl, raw_ip6.c:326 M_PREPEND, uipc_mbuf.c:2119-2135 in-place-vs-realloc conditions, mbuf.h:281-284 M_COPYFLAGS) plus run.log survival of all chain shapes." } |