DF-3419 / verdict.json
{ "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" } |