{
  "finding_id": "DF-3419",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Reproduced 2/2 on the stock INVARIANTS 6-cpu guest (panic in ~60-75s per run, identical stack): pfi_ifaddr_event -> pfi_kif_update walks kif->pfik_dynaddrs under crit_enter() only (no pf_token, pf_if.c:905-916/470-482) while a concurrent pfctl -f on another CPU frees every dynaddr of the old ruleset via pf_rm_rule -> pfi_dynaddr_remove (pf_ioctl.c:425-433 -> pf_if.c:681-686, under pf_token which the walker never takes). The walker dereferences the entry mid-teardown -> panic: pfi_dynaddr_update: bad argument (pf_if.c:491-492), or a freed-chunk UAF read if the kfree lands first. Primitive: with the freed pfi_dynaddr reclaimed by controlled data, the walk follows a controlled tqe_next and consumes controlled pfid_kt/pfid_kif pointers through pfi_table_update/pfr_set_addrs - but both racing operations require root, so the realistic ceiling is a root-triggered kernel panic (plus silent table corruption), not unpriv->root. Fix validated: pf_token added to the four event handlers, kernel rebuilt in-guest (#1 Sat Sep 12 10:51:52), the exact stress then ran clean 3x (8000x8000) + 1x (16000x16000) ~7min total vs ~70s-to-panic baseline.",
  "exploit_chain": "root: load ruleset with (tap0:network) dynaddr + root: address churn on tap0 (or in-kernel DHCPv6/RA address events) while concurrently reloading the ruleset -> pfi_kif_update walks dynaddr being freed -> panic (observed) / freed-chunk walk with reclaimed contents -> controlled pointer chain into pfr_set_addrs (characterized, not weaponized - no unpriv trigger exists since /dev/pf is 0600 and ifconfig is privileged)",
  "evidence": [
    "findings/poc/DF-3419/panic.txt — first baseline panic: full console trace",
    "findings/poc/DF-3419/panic.2.txt — second baseline panic after fresh vm.sh reset (fresh boot, same signature)",
    "findings/poc/DF-3419/patched_runs.log — patched kernel (#1) stress results: BOTH-DONE-NOPANIC x3 + 16K-STRESS-DONE-NOPANIC",
    "findings/poc/DF-3419/run.sh — exact reproduction script",
    "findings/poc/DF-3419/fix.diff — the 4-hunk pf_token patch applied to the guest /usr/src and validated",
    "sys/net/pf/pf_if.c:470-482 — unlocked TAILQ walk",
    "sys/net/pf/pf_if.c:673-689 — concurrent TAILQ_REMOVE + kfree under pf_token",
    "sys/net/pf/pf_ioctl.c:425-433 — pf_rm_rule invoking pfi_dynaddr_remove on ruleset change"
  ],
  "kernel_refs": [
    "sys/net/pf/pf_if.c:470-482",
    "sys/net/pf/pf_if.c:491-502",
    "sys/net/pf/pf_if.c:673-689",
    "sys/net/pf/pf_if.c:905-916",
    "sys/net/pf/pf_ioctl.c:425-433",
    "sys/netinet/in.c:634",
    "sys/netinet6/in6.c:803"
  ],
  "poc_changes": "Authored from scratch (no seed). Shell stress: address alias/-alias churn on tap0 concurrent with pfctl -f reload loop of a 41-line ruleset using (tap0:network) dynaddrs (pfctl collapses it to 2 rules/1-2 dynaddrs per reload - race hits anyway, twice).",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026\troot@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC\tx86_64",
  "runtime_sec": 1500,
  "guest_dirty": 0,
  "build_cmd": "cd /usr/src && patch -p1 < /tmp/fix3419.diff && make nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC",
  "run_cmd": "sh /tmp/stress.sh   # 8000x alias-churn vs 8000x pfctl -f; see run.sh",
  "code_hash": "",
  "notes": "Distinct from known DF-0604 (global pfi_buffer data race): this is the dynaddr list LIFETIME (walk-freed-object UAF) vs their table-contents race; same missing-pf_token root-cause family as their suggested fix. Distinct from DF-0605 (pfi_get_ifaces RB walk). Stress scripts/round logs preserved in pack; guest reset clean after validation.",
  "recommended_fix": "Take lwkt_gettoken(&pf_token) around pfi_update++/pfi_kif_update in the four event handlers (pfi_attach_ifnet/pfi_detach_ifnet/pfi_group_change/pfi_ifaddr_event), per fix.diff; longer term add dynaddr refcounting so walkers pin entries across blocking points",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Sep 12 10:51:52 UTC 2026\troot@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC\tx86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Baseline: 2/2 panics in ~60-75s on stock kernel #0. Patched kernel #1 (pf_token in the four event handlers, built and installed in-guest, RC=0): 4/4 clean runs of the exact PoC incl. one double-length 16000x16000 round (~7min total, ~6x the baseline time-to-panic). Bad behavior gone. Residual: lwkt tokens are surrendered at blocking points inside pfi_table_update, so a fully robust fix should also pin dynaddr entries (refcount) - noted for upstream.",
  "fix_evidence": "findings/poc/DF-3419/fix.diff; findings/poc/DF-3419/patched_runs.log"
}
