# DF-3311 — wg interface teardown races the in-flight receive upcall: tasks/queues/softc used after cancel/free

## What this is
Evidence pack for the pass-2 finding in `sys/net/wg/if_wg.c`:
`wg_down()`/`wg_clone_destroy()` cancel and drain the handshake and
encrypt/decrypt tasks and purge their queues **while the UDP sockets are
still open**, so a receive upcall already inside `wg_input()` can enqueue a
handshake packet and re-enqueue `sc_handshake_task` (or a decrypt dispatch)
*after* the cancel/drain/purge — leaving a task that later fires on a
softc whose task arrays were kfree'd (if_wg.c:2883-2884), whose queue
locks were lockuninit'd (if_wg.c:2885-2887), and which is ultimately
kfree'd (if_wg.c:2917).

## verdict summary
- **status: inconclusive.**
- One **hard system wedge** (guest dead, ssh gone, no panic text, no crash
  dump, QEMU process alive at DDB/hang; required force-kill + snapshot
  revert) occurred ~45 s into the *first* race run (root churn + unpriv
  spam), i.e. exactly under the trigger conditions.
- 3 clean controls (spam only; churn only; spam + single destroy) and 4
  subsequent race reruns (~8000 churn iterations total incl. a widened
  12-peer variant) did **not** re-wedge within budget.
- See VERDICT.md for the interleaving analysis and why the wedge signature
  (silent hang, no dump) matches a lock/queue-lifetime corruption rather
  than an unrelated flake.

## Contents
- `README.md` — this file
- `VERDICT.md` — full narrative
- `wgtool.c` — SIOCSWG/SIOCGWG driver (no wg(8) on the guest)
- `spam.c` — unprivileged "network peer": exact-length WG_PKT type-1/2/3
  packets (no valid crypto needed — the enqueue path only checks the type
  word and length, if_wg.c:114-125, 2163-2172)
- `raceA.sh` — root driver: create/config(4 peers,pka=1)/up/destroy loop
- `raceA12.sh` — widened variant (12 peers, longer teardown per destroy)
- `build.sh`, `run.sh`
- `run.log` — the wedge run (run #1) campaign transcript
- `run.2.log`, `run.3.log` — reruns (clean)
- `env.txt` — guest uname
- `fix.diff` — close the sockets *before* cancelling/draining in wg_down()
- `manifest.json`, `verdict.json`

## Reproduce
```
# on the DragonFly guest:
# terminal 1 (root):    build + churn loop
kldload if_wg
cc -O2 -o wgtool wgtool.c && cc -O2 -o spam spam.c
sh raceA.sh 3000
# terminal 2 (unprivileged):
su -m nobody -c './spam 127.0.0.1 51820 0 tag'
# success criterion: guest dies (ssh stops answering, vm.sh status =>
# down, no reboot in serial log); expect low probability per run,
# repeat the whole cycle.
```
