# DF-3264 VERDICT — not_reproduced on the QEMU guest (mechanism code-proven)

## Result
`status: not_reproduced`, `impact: none (demonstrated) / panic-to-UAF (by
construction)`, `confidence: likely` for the vulnerability mechanism,
which is certain from the code; what could not be demonstrated inside the
budget is the timing overlap on this guest.

## Mechanism (all path:line verified against sys/net/pf/pf.c @ 8faf1f65)
1. The packet path runs pf with pf_token held **shared**
   (pf_ioctl.c:3205/3224/3246/3262 `lwkt_gettoken_shared(&pf_token)`).
2. `pf_send_tcp()` **releases** the token around `ip_output()`
   — pf.c:2254-2256 (`eh == NULL` branch), 2274-2276 (route-to/reply-to
   branch), 2289-2291 (IPv6 branch) — then re-acquires it.
3. Callers on the state path keep using `struct pf_state` after the send:
   * SYNPROXY retransmit: pf.c:4918-4924 send, then FAIL →
     `lockmgr(&(*state)->lk, LK_RELEASE)` pf.c:5066-5067;
   * SYNPROXY completion: pf.c:4963-4973 two sends, then twelve+
     read/writes of `(*state)->src/dst` at pf.c:4974-4984;
   * create-state synproxy: pf.c:4294-4298 send → pf_test tail uses
     `s->state_flags`, `s->tag`, `s->nat_rule.ptr`, `s->packets[]`,
     `s->bytes[]` (pf.c:6712-6812);
   * bad-state RST: pf.c:4738-4746 — three writes to `src->*` AFTER the
     send;
   * `pf_unlink_state()` RST: pf.c:1489-1497 (purge-thread reachable).
4. `struct pf_state` has **no reference count** (pfvar.h:810-859); the
   ONLY things keeping it alive are (a) the shared pf_token and (b) its
   timeout. During the token-drop window neither holds: the purge thread
   (`pf_purge_thread` pf.c:1262-1333, once per second per cpu) can
   acquire the token exclusively, find
   `pf_state_expires(cur) <= time_second` and unlink+**free** the state
   in the same sweep (pf.c:1589-1597).
5. Result: the netisr thread resumes after `ip_output()` and writes
   read/write-freed ~250-byte kmalloc(M_PFSTATEPL) memory. Slab reuse
   with attacker-influenced content (new pf_states are being allocated
   continuously) makes this a corrupting UAF write primitive; at minimum
   it is a crash.

## Verification attempts (guest: DragonFly 6.5-DEVELOPMENT #0/#1, 6 vCPU)
* **Instrumented kernel**: built a detector kernel marking the state
  currently inside the pf_send_tcp token-drop window (per-cpu
  `pf_dbg_inflight[]`), checked from `pf_free_state()`; a hit proves
  free-during-window (the exact race precondition). Module verified
  loaded (`strings pf.ko | grep RACE-HIT`), state machinery verified
  live (512-state synproxy waves expiring at +120 s, state-table churn
  observed: inserts/removals counting up).
* Campaigns (all against the detector kernel):
  1. 6 race rounds × 512 synproxy states (create → wait 117 s → retransmit
     flood across the +120 s expiry sweep).
  2. + 4 parallel retransmit flooders (loopback egress).
  3. 12 continuous flooders — purge thread became token-starved (state
     churn stopped; removals frozen) → dropped to 2 flooders.
  4. 2 flooders with SYN|ACK egress via the virtio NIC (10.0.2.0/24
     spoofed sources) to widen the ip_output window.
  5. DIOCKILLSTATES tight-loop ioctl hammer (1.5M+ iterations,
     20M lifetime state removals) — accelerates *unlink* (pf_ioctl.c
     pfioctl DIOCKILLSTATES → pf_unlink_state) but the **free** stays
     deferred to the purge thread, so this did not close the chain.
* **0 RACE-HITs in ~1 h of steady-state racing** (~5-10 state frees/s
  continuously crossing thousands of in-flight send windows).

## Why it did not reproduce here (honest assessment)
The overlap probability per free is
`P ≈ (per-state marked time fraction) = retransmit_rate × ip_output_duration`.
On QEMU, loopback egress is ~3 µs and virtio ~20-30 µs, with a ~300 µs
per-send cycle → P ≈ 0.05-1 % per free; with ~5-10 frees/s the expected
hit time is minutes-to-tens-of-minutes *if* the purge wins tokens during
windows, but under continuous multi-sender load the purge thread itself
becomes token-starved (observed), collapsing the very regime that has
high window coverage. On real hardware (ms-scale tx stalls, ARP waits,
congested links — i.e. every production firewall), the window is 2-3
orders of magnitude larger and the race is correspondingly more real.

## Exploit chain (not executed)
synproxy/keep-state rule (default in most pf deployments) + SYN
retransmits timed at state expiry → purge frees in-flight state →
netisr's post-send writes corrupt a recycled pf_state slab object
(attacker can groom with new states) → state-key/rule-pointer corruption
→ potential code-execution class impact; at minimum remote-triggerable
panic.

## Fix
fix.diff in this pack (three hunks: try-lock-and-defer in
pf_free_state; state lock held across the create-state synproxy send;
state lock held across pf_test's SYNPROXY_DROP tail). Not validated on
guest (baseline not reproduced); recommended upstream direction: stop
dropping pf_token inside pf_send_tcp (queue generated packets instead).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
