DF-2611 / verdict.json
{ "finding_id": "DF-2611", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "REPRODUCED live on the stock INVARIANTS guest: icmp6_redirect_output transmits the 8-byte-alignment pad of the ND_OPT_TARGET_LINKADDR option as uninitialized mbuf-cluster heap memory. With tap0's ifi_addrlen rewritten 6->8 (via /dev/kmem, offsets pre-validated against vtnet0/lo0/tap0 known values - a privileged stand-in for the EUI-64/fwip interface QEMU cannot emulate; the leaked code path is identical), every emitted redirect (35/35 across three phases) carried a 16-byte TLLA option of which bytes [10,16) were stale cluster contents: 20/20 redirects leaked the immediately-preceding UDP spray pattern (b1,b2,b3... incrementing per iteration - byte-perfect attribution to the freed cluster), and 10/10 no-spray redirects leaked '00' x6 (virgin cluster) then 'b5' x6 - the pattern of the EARLIER finished spray run still resident in the heap. Control with stock addrlen=6: option length 8, zero pad bytes, exactly as the finding states for Ethernet. Quantified: 6 pad bytes per redirect for addrlen=8 (1-7 for other non-6 addrlens), 100% emission rate, contents = whatever the cluster cache last held (attacker-influenced when on-path traffic grooms it), passively sniffable on-link.", "exploit_chain": "", "evidence": "run.log + leak_sample.txt: control 'TLLA bytes: 02 00 00 00 00 01' (5x, option len 8); spray phase 'TLLA bytes: 02 00 00 00 00 01 00 00 b1 b1 b1 b1 b1 b1' .. 'b4' wrapping over 20 iters (option 'type=2 len=16'); no-spray phase '00 00 00 00 00 00' x4 then 'b5 b5 b5 b5 b5 b5' x6 (stale pattern from the previous run - leak decoupled from the spray). kmem validation lines: vtnet0 type=6 addrlen=6 mtu=1500 / lo0 type=24 addrlen=0 mtu=16384 / tap0 type=6 addrlen=6->WROTE 8 (readback).", "kernel_refs": [ "sys/netinet6/icmp6.c:2496-2497", "sys/netinet6/icmp6.c:2506-2511", "sys/netinet6/icmp6.c:2516", "sys/netinet6/icmp6.c:2412", "sys/netinet6/icmp6.c:2611", "sys/netinet6/ip6_forward.c:246-265", "sys/netinet6/ip6_forward.c:358", "sys/netinet/icmp6.h:165" ], "poc_changes": "No seed. Toolkit written from scratch; material discoveries during bring-up: (1) /dev/tap is exclusive-open and destroy-on-close, so one process must own tap0 end-to-end (setup in-process); (2) the gateway route MUST carry a scoped link-local gateway ('fe80::42:1%tap0') or rt_llroute()'s rtlookup(rt_gateway) misses and the forward dies with EHOSTUNREACH (ICMPv6 unreach code 3) instead of emitting a redirect; (3) TLLA pad analysis must parse options at frame offset 94 (14 eth + 40 ip6 + 40 nd_redirect); (4) the if_addrlen mutation must happen while the harness holds tap0 (ifnet_array is rebuilt on interface create/destroy).", "attempts": 7, "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": 400, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o tap_rdr tap_rdr.c && cc -O2 -Wall -o kmem_addrlen kmem_addrlen.c", "run_cmd": "sh run.sh (as root; performs control addrlen=6, patch8+spray leak run, patch8 no-spray run)", "code_hash": "dd8e2b3ba3af504cc006faac0754e3f4d5c5e51df21632884dd2207866339114", "notes": "The only simulated element is the interface's address LENGTH (privileged kmem rewrite standing in for fwip/EUI-64 hardware QEMU cannot provide); everything after that - option rounding to 16, 2+addrlen written bytes, 6 transmitted uninitialized cluster bytes, checksum coverage - is stock kernel behavior. Production preconditions per the finding: IPv6 router role (defaults ip6.redirect=1) + non-Ethernet-addrlen outgoing interface + resolved neighbor. Guest reset to clean snapshot after the run (kmem was mutated mid-run).", "recommended_fix": "bzero(lladdr + ifp->if_addrlen, len - sizeof(*nd_opt) - ifp->if_addrlen) after the LLADDR bcopy in icmp6_redirect_output (see fix.diff); same class as DF-0329's verified redirected-header fix.", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff authored (5-line pad bzero at icmp6.c:2510) but single-fix kernel not built/rebooted: this is an info-leak finding, not memory corruption, so the mandatory rebuild rule does not apply, and the change is mechanically identical to DF-0329's already-verified fix for the sibling redirected-header-option site. Baseline leak is fully characterized above for later A/B if a maintainer wants it.", "fix_evidence": "fix.diff; VERDICT.md; DF-0329 pack (verified fix for the sibling site, findings/poc/DF-0329/)." } |