{
 "finding_id": "DF-3433",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "Use-after-free reproduced on the stock INVARIANTS guest (#0). kldunload if_stf (stfmodevent MOD_UNLOAD, if_stf.c:210-224) tears the softc down with no serialization against (a) the lock-free encaptab walk (ip_encap.c:174-215 calls stf_encapcheck on ep->arg) and (b) mbufs already decapsulated by in_stf_input carrying rcvif=&sc->sc_if into the async NETISR_IPV6 queue (if_stf.c:611,629). With stf0 actually configured (2002:0a63:0001::1/16 over a tap v4 anchor) and debug.use_weird_array=1 (kfree poisons freed chunks with 0xdeadc0de, kern_slaballoc.c:1566-1571) the teardown faulted deterministically: Fatal trap 9, cpuid 0, current process Idle (netisr), Stopped at nd6_purge+0xc9 cmpb $0x12,0x1(%rdx) - the if_detach/in6_ifdetach teardown walking memory freed by the concurrent unload/decap interleaving, ~4000 unload cycles / ~24M injected 6to4 frames / ~80s. The poison knob only makes the post-free access visible; on stock kernels the same interleave silently reads/writes freed memory (IFNET_STAT_INC/in6_ifstat_inc write through freed if_afdata). Honest negative included: the bare walk-instant variant (DF-3425s exact gif shape, 79.1M proto-41 floods + 1969 kldunload cycles) did NOT panic - the stf mutator is the ~120ms kldunload only, so the walk window is much narrower than gifs ioctl-speed reconfig churn; the async rcvif vector is the wide, reproducible one. Trigger is root-gated (kldunload); read side is ordinary peer traffic.",
 "exploit_chain": "not developed: root-gated kldunload + peer flood -> teardown walks freed/poisoned memory (panic demonstrated). Constrained write-after-free primitive exists via in6_ifstat_inc/IFNET_STAT_INC through the freed sc and freed if_afdata; slab-grooming the recycled 2-4KB softc chunk before the teardown walk is the plausible escalation path, not pursued here.",
 "evidence": [
  "panic.txt:45-59: Fatal trap 9: general protection fault, current process: Idle, Stopped at nd6_purge+0xc9: cmpb $0x12,0x1(%rdx) (non-canonical poison pointer, cpuid 0)",
  "stf_async_race.c: tap-injected matching 6to4 frames (in_stf_input decaps + netisr_queue with rcvif=softc) vs kldunload/kldload+reconfig bounce",
  "setup_async.sh: stf0 inet6 2002:0a63:0001::1/16 + tap0 10.99.0.1/24 + sysctl debug.use_weird_array=1",
  "run.log: baseline walk-instant attempt - 79,135,119 floods / 1,969 unload cycles / no panic (documented negative for the narrow sub-vector)",
  "run.patched.log: identical async harness on the fix.diff kernel - no panic"
 ],
 "kernel_refs": [
  "sys/net/stf/if_stf.c:210",
  "sys/net/stf/if_stf.c:214",
  "sys/net/stf/if_stf.c:222",
  "sys/net/stf/if_stf.c:250",
  "sys/net/stf/if_stf.c:611",
  "sys/net/stf/if_stf.c:626",
  "sys/net/stf/if_stf.c:629",
  "sys/netinet/ip_encap.c:174",
  "sys/netinet/ip_encap.c:180",
  "sys/netinet/ip_encap.c:427"
 ],
 "poc_changes": "No seed (pass-2 discovery). Three harness iterations: (1) bouncer state machine started out-of-sync with the pre-loaded module - made stateless; (2) tap MAC discovery via SIOCGIFADDR returned garbage/zeros (dgram socket AND tap fd) - switched the frame dst MAC to broadcast; (3) the async vector required stf0 to actually MATCH (encapcheck) - added tap0 v4 anchor + 2002:0a63:0001::1/16 per-cycle reconfig and debug.use_weird_array=1 to make the post-free deref fault deterministically.",
 "attempts": 4,
 "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": 560,
 "guest_dirty": 1,
 "build_cmd": "cc -O2 -pthread -o /tmp/stf_async_race /tmp/stf_async_race.c",
 "run_cmd": "sh /tmp/setup_async.sh && /tmp/stf_async_race 120",
 "code_hash": "4e214185b3146e2acbe6dbbcb3bbc11203c48eb95b8e3673ce2994da2e457dc4",
 "notes": "DF-3425 (gif twin) reproduced the same lock-free-walk defect and validated the mp_token fix; this run proves the stf instance and adds the wider async rcvif vector (queued NETISR_IPV6 mbufs outlive the softc with no refcount). Residual after fix.diff: a full softc refcount (if_ref-style) would be needed to protect queued packets past a down/up transition even with the EBUSY guard removed by an admin who first sets the interface down.",
 "recommended_fix": "Serialize encaptab mutators with mp_token (walkers already hold it) + refuse MOD_UNLOAD while stf0 is IFF_UP (EBUSY); see fix.diff.",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Sep 12 16:56:02 UTC 2026   root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "Two-kernel validation. Kernel #2 (16:27:37, stf-EBUSY + ip_encap mp_token + state fixes) and the final fix-v3 kernel #1 (16:56:02, same stf/encap parts) both ran the identical async harness: 48.1M injected 6to4 frames with 43,749 kldunload attempts in run 1 and 18.9M frames / 17,617 attempts in the 60s re-check - every unload refused with EBUSY (stf0 configured/UP), unload_cyc=0, no panic, guest stayed up; baseline stock kernel #0 panicked at ~4,000 successful unload cycles (nd6_purge, trap 9). The EBUSY-while-up guard structurally removes the unsynchronized teardown; the mp_token change covers the encap walk for any remaining unload path (interface down first).",
 "fix_evidence": [
  "run.patched.log",
  "kbuild.tail.log",
  "fix.diff"
 ]
}