# DF-3264 — pf_state use-after-free: pf_send_tcp drops pf_token mid-state-operation

## Files
* `synproxy.conf` — rule set: `pass quick inet proto tcp from any to 127.0.0.2 port 8080 synproxy state` + `pass all`
* `synflood_uaf.c` — creates N=512 synproxy states, waits to just before
  their PFTM_TCP_FIRST_PACKET (120 s) expiry, then retransmits all SYNs at
  high rate so the pf_purge_thread sweep at +120 s collides with in-flight
  `pf_send_tcp()` ip_output token-drop windows.
* `race.sh` — multi-round driver (6 rounds).

## Mechanism (sys/net/pf/pf.c)
* Packet path: `pf_check_in/out()` hold pf_token **shared** (pf_ioctl.c:3205/3224).
* `pf_test_state_tcp()` SYNPROXY retransmit path (pf.c:4913-4924) calls
  `pf_send_tcp()`, which **releases pf_token** around `ip_output()`
  (pf.c:2254-2256, 2274-2276, 2289-2291).
* In that window `pf_purge_thread` (blocked on the token) acquires it
  exclusively, finds the just-expired state (`pf_state_expires() <=
  time_second`), and unlinks+frees it (pf.c:1589-1597).
* `ip_output()` returns → netisr keeps using the freed state:
  `FAIL(PF_SYNPROXY_DROP)` → `lockmgr(&(*state)->lk, LK_RELEASE)`
  (pf.c:5066-5067), then pf_test's tail: `s->state_flags`, `s->tag`,
  `s->nat_rule.ptr`, `s->packets[]`, `s->bytes[]` (pf.c:6712-6812).
  Same exposure in the create-state synproxy send (pf.c:4294 → pf_test tail)
  and the bad-state RST path (pf.c:4738-4746: three writes to `src->*`
  *after* the send).
* `struct pf_state` has **no reference count** (pfvar.h:810-859); lifetime
  is governed solely by the pf_token + purge timeouts.

## Run (stock kernel)
`sh race.sh` — 6 rounds × 512 states; state churn verified (state count
dropped back each round exactly when the +120 s purge freed the expired
batch mid-flood). No visible crash on the stock kernel (a hit does not
guarantee an observable fault: the freed 256-byte slab chunk is typically
recycled as another pf_state and the stale writes land in valid-looking
fields — silent corruption).

## Run (instrumented kernel)
`apply_detector.py` marks the state currently inside the pf_send_tcp
token-drop window (per-cpu `pf_dbg_inflight[]`, checked from
`pf_free_state()`); a `RACE-HIT: freeing in-flight state ...` message
proves the purge freed a state the packet path is still going to touch
after ip_output() returns. See run.log / dmesg.txt in this pack.
