# DF-0604 — PoC: pfi_buffer cross-CPU race (pfctl churn + ifaddr churn)

Privileged local kernel-panic PoC. The global `pfi_buffer`/`pfi_buffer_cnt`/
`pfi_buffer_max` in `sys/net/pf/pf_if.c:74-76` are shared across all CPUs with
no lock. Two concurrent `pfi_table_update()` callers on different CPUs race
on the shared buffer, corrupting pf's dynamic-interface address tables and
causing a kernel panic.

## Key correction to the finding

The finding's original PoC (concurrent `ifconfig alias` on two interfaces)
**cannot trigger the race** because `SIOCAIFADDR` dispatches ALL work to
netisr0 (`sys/netinet/in.c:248`), so all `ifaddr_event` firings serialize on
netisr0's single thread.

The **actual race window** is between:
- **(A) pfioctl path** (DIOCADDRULE → `pfi_dynaddr_setup` → `pfi_table_update`)
  which runs on the **caller's CPU** (pfioctl does NOT dispatch to netisr0)
- **(B) ifaddr_event path** which runs on **netisr0**

These two paths share the global `pfi_buffer` with no lock and can overlap.

## Files

- `race_churn.c` — C harness for concurrent SIOCAIFADDR churn with CPU pinning
- `race_live.sh` — Shell driver: pfctl rule churn (path A) + ifaddr churn (path B)
- `race_proof.c` — Code-level proof: pthreads program replicating the race pattern
- `fix.diff` — Correct fix: dispatch `pfi_table_update` to netisr0
  (NOT the finding's lwkt_token proposal which **deadlocks**)
- `VERDICT.md` — Full analysis
- `panic.txt` — Panic signature from the baseline (instrumented #1 kernel)
- `fix_build.log` / `fix_run.log` — Build and validation logs for the fix

## Setup (as root on DragonFlyBSD guest)

```
kldload pf
cat > /tmp/pf_0604.conf << 'EOF'
pass quick on vtnet0 inet from (vtnet0) to any
pass quick on vtnet0 inet6 from (vtnet0) to any
block in quick from (vtnet0:network) to any
EOF
pfctl -f /tmp/pf_0604.conf
```

## Build

```
cc -O2 -o race_churn race_churn.c
cc -O2 -pthread -o race_proof race_proof.c
```

## Run

```
# Code-level proof (quick — shows the race pattern is fundamental)
./race_proof

# Live race trigger (requires root + pf loaded)
sh ./race_live.sh 30
```

## Expected outcome

**Unpatched kernel** (with race-detection instrumentation):
- `DF0604_RACE: concurrent pfi_table_update depth=2 cpu=0` messages
- `pfi_table_update: cannot set N new addresses into table vtnet0: N`
- **Fatal trap 9: general protection fault** at `rn_walktree_at+0xa8`
- Guest panics

**Fixed kernel** (dispatch-to-netisr0):
- 0 race detections, 0 panics, pf rules stay functional
- Guest stays up

## Fix

The fix (`fix.diff`) dispatches the entire `pfi_table_update` to netisr0 when
not already on CPU 0. Since netisr0 is single-threaded, all callers serialize
inherently. This **supersedes** the finding's lwkt_token proposal, which would
**deadlock** when the token holder blocks on `netisr_domsg` and netisr0 tries
to acquire the same token for an ifaddr_event handler.
