DF-3376 / verdict.json
{ "finding_id": "DF-3376", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "likely", "verdict": "REPRODUCED 2/2 on the stock INVARIANTS kernel: an unauthenticated on-link attacker drives an RA-accepting (ND6_IFF_ACCEPT_RTADV, !ip6_forwarding) DragonFly host to a fatal kernel panic in 2-5 minutes at ~1.3k pps: panic: rtrequest1_msghandler: rtrequest table req 1, failed on cpu1, error 55 (per-CPU route replication invariant broken after ND6 state corruption). Root-cause family: the nd6_ra_input tail (nd6_rtr.c:403-410) walks/mutates the nd_prefix list, nd_defrouter back-pointers (pfxrtr->router) and pr->ndpr_stateflags WITHOUT nd6_mtx while nd6_timer_dispatch (nd6.c:461-692, netisr0, holding nd6_mtx) kfree()s those same objects (nd6_rtr.c:576/739/845) \u2014 a use-after-free read/write race on M_IP6NDP objects, amplified by the attacker controlling both the expiry timing (RA lifetimes) and the walk rate (RA-with-SLLA). Attribution isolated by 2x2 controls: churn-only 10 min/6.1M RAs clean; walk-only 8 min/7.8M RAs clean; only the concurrent mix is fatal. A one-hunk fix serializing only the :410 pfxlist_onlink_check call was built (kernel #1) and FALSIFIED \u2014 the identical hammer still panicked in ~3.5 min \u2014 proving the lockless-discipline defect spans the wider RA tail (lockless nd6_cache_lladdr state machine + per-CPU RTM_RESOLVE route cloning vs the sweeps global route deletes) and needs the broader serialization pass. uid=0 escalation not completed: the write primitive (2-bit NDPRF set/clear at fixed offset in freed M_IP6NDP chunk + list-pointer chasing) is probabilistic across 6 CPUs and requires zone grooming against a self-driving race; documented as the blocker. Demonstrated, verified impact: remote-triggerable fatal kernel panic (DoS).", "exploit_chain": "on-link attacker -> crafted RAs on gif0/any ND interface (hlim=255, LL src, valid cksum) -> churner RAs create nd_defrouter (lifetime 1s) + nd_prefix w/ pfxrtr (vltime 2s) -> nd6_timer sweep on netisr0 kfrees dr/pfxrtr/pr under nd6_mtx -> concurrent RA tails on other netisr CPUs walk the same lists lockless (pfxlist_onlink_check; nd6_cache_lladdr adjacency) -> UAF reads + ndpr_stateflags bit-writes into freed-reused M_IP6NDP chunks -> ND/route state corruption -> per-CPU route table divergence -> intentional fatal panic (route.c:765). Ceiling (not completed): M_IP6NDP groom + steer the bit-write/LIST_REMOVE-through-corrupted-pointer into a victim object for uid=0.", "evidence": [ "panic.raw.txt + panic2.raw.txt: identical fatal panic signatures, runs 1-2", "fix_panic.txt: patched kernel (#1, 19:14:38) panics identically -> fix_failed", "run.log: 2x2 control matrix (churn-only clean 10min/6.1M; walk-only clean 8min/7.8M; mix fatal 2/2)", "VERDICT.md: full mechanism, DF-0417 window exclusion, fix falsification", "env.txt: guest/rig facts (6 CPUs, nd6_prune=1, gif0 ACCEPT_RTADV)" ], "kernel_refs": [ "sys/netinet6/nd6_rtr.c:410", "sys/netinet6/nd6_rtr.c:403", "sys/netinet6/nd6_rtr.c:1146", "sys/netinet6/nd6_rtr.c:1181", "sys/netinet6/nd6_rtr.c:1204", "sys/netinet6/nd6_rtr.c:576", "sys/netinet6/nd6_rtr.c:739", "sys/netinet6/nd6_rtr.c:845", "sys/netinet6/nd6_rtr.c:847", "sys/netinet6/nd6.c:461", "sys/netinet6/nd6.c:577", "sys/netinet6/nd6.c:670", "sys/netinet6/nd6_nbr.c:770", "sys/netinet6/in6.c:1328", "sys/net/route.c:765" ], "poc_changes": "Built from scratch on the DF-3331 gif self-tunnel injection rig (proto-41 raw socket to ::1). Two RA species: churner (fresh fe80::47xx src, router lifetime 1s, PIO 2001:db8:inst:P|epoch::/64 L=1 A=0, vltime 2s, epoch flip every 3s so half the population is swept mid-epoch) and walker (8 rotating fe80::46xx srcs, lifetime 0, SLLA -> every packet ends in the lockless RA tail). -W 0 / -C 0 switches implement the churn-only / walk-only controls. Root-only steps are rig setup only (gif + ACCEPT_RTADV + raw socket); the packets themselves are what any on-link peer emits.", "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": 4800, "guest_dirty": 0, "build_cmd": "cd /root/df3376 && sh build.sh (cc -O2 -Wall -o rarace rarace.c)", "run_cmd": "ifconfig gif0 create; ./rarace setup gif0; for i in 1 2 3 4 5 6; do ./rarace blast -i $i > r.$i.log 2>&1 & done", "code_hash": "675f43545ff5a9ce6ecbc1fead216778bee2869729f3e3c960e324a8c156ae8e", "notes": "Status vocabulary per persist_poc.py (reproduced). Fix experiment: single-site nd6_mtx wrap at nd6_rtr.c:410 (fix.diff, sha256 a02bd831...) compiled into kernel #1 and FALSIFIED by identical panic -> the correct fix must serialize the whole nd6_ra_input tail and audit the nd6_cache_lladdr/RTM_RESOLVE clone vs sweep-delete interplay; sibling lockless sites (nd6_nbr.c:770, in6.c:1328, nd6.c:786 nd6_purge, nd6_rtr.c:809-830 prelist_remove prologue) need the same pass. Guest returned via vm.sh reset with-src (clean snapshot, guest_dirty=0).", "recommended_fix": "Serialize the entire nd6_ra_input tail (nd6_cache_lladdr + pfxlist_onlink_check) under nd6_mtx and extend the same serialization to the sibling lockless call sites (nd6_nbr.c:770, in6.c:1328, nd6_purge, prelist_remove prologue); a one-site lockup is empirically insufficient.", "fix_status": "fix_failed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep 11 19:14:38 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 1, "fix_verdict": "fix_failed: with fix.diff applied (nd6_mtx around only the nd6_rtr.c:410 pfxlist_onlink_check call; kernel rebuilt as #1 and confirmed running), the identical 6-blaster hammer still produced the same fatal panic in ~3.5 min (fix_panic.txt). The corruption therefore also flows through the remaining lockless RA-tail machinery (nd6_cache_lladdr state machine / per-CPU RTM_RESOLVE clones racing the sweeps route deletes); broader serialization required.", "fix_evidence": [ "fix.diff (applied in-guest, compiled with -Werror, kbuild.log:7767)", "fix_panic.txt (patched-kernel fatal run, identical signature)", "VERDICT.md Fix validation section" ] } |