# DF-3310 — wg_socket_init() port-roam corrupts a live lock and swaps sockets unsynchronized

## What this is
Evidence pack for the pass-2 finding in `sys/net/wg/if_wg.c`:
`wg_socket_init()` (called from `wg_ioctl_set()` when the listen port changes
on a **running** interface, if_wg.c:2557-2560) does, with old sockets still
live and the send fast path `wg_send()` (if_wg.c:1145-1160) concurrently
holding `sc_socket.so_lock` SHARED and reading `so_so4`/`so_so6`:

1. `lockinit(&so->so_lock, ...)` at if_wg.c:974 — a **second initialization
   of a live lock**. DragonFly's `lockinit()` (sys/kern/kern_lock.c:1300)
   unconditionally zeroes `lk_count` and `lk_lockholder`. Any in-flight
   shared holder's release then subtracts `LKC_SCOUNT` from 0, leaving a
   ghost "shared holder" (0xFFFFFFFF00000000) in `lk_count` forever —
   subsequent exclusive acquirers (`wg_socket_set_cookie` if_wg.c:1114,
   `wg_socket_uninit` if_wg.c:1063) starve permanently.
2. `soclose()` of the old sockets + pointer install at if_wg.c:976-982
   **without taking so_lock at all** (only `sc_lock`, which `wg_send()`
   never takes) — the lock exists precisely to serialize this against
   `wg_send()`, as `wg_socket_uninit()` (if_wg.c:1063-1074) demonstrates.

## verdict summary
- **status: not_reproduced** (dynamic race not hit; the code defect is
  certain — see VERDICT.md for the line-by-line proof).
- ~8,900 successful port changes raced against sustained encrypted traffic
  (`ping -f`, incl. 1400-byte floods, and `net.inet.udp.sosend_async=0`
  which maximizes the in-lock hold time) with live `so_lock`-EXCLUSIVE
  probes (`wgtool cookie`, SIOCSWG w/ WG_INTERFACE_HAS_COOKIE) every
  iteration — no wedge, no panic in the allotted budget.

## Contents
- `README.md`    — this file
- `VERDICT.md`   — full narrative + code proof + run log
- `setupB.sh`    — bring up wg0<->wg1 with RFC 7748 keys over lo0
- `raceB.sh`     — port-change churn loop (plain)
- `raceB2.sh`    — port churn + concurrent so_lock-EXCLUSIVE probe (SIOCSWG
                   cookie with `timeout`), the live wedge detector
- `wgtool.c`     — SIOCSWG/SIOCGWG driver (also in DF-3311/), subcommands
                   `set`, `setport`, `peer`, `cookie`, `show`
- `build.sh`, `run.sh`
- `run.log`, `run.2.log`, `run.3.log` — the three race campaigns
- `env.txt`      — guest uname / kernel
- `fix.diff`     — the verified-by-inspection fix (lock once at softc
                   creation; take so_lock EXCLUSIVE around close/swap)
- `manifest.json`, `verdict.json`

## Reproduce
```
# on the DragonFly guest (root):
kldload if_wg
cc -O2 -o wgtool wgtool.c
./setupB.sh                 # prints TUNNEL_UP
ping -f 10.7.0.2 >/dev/null 2>&1 &
sysctl net.inet.udp.sosend_async=0   # maximize wg_send() hold time
sh raceB2.sh 300 0          # churn + live EXCLUSIVE probes
# WEDGE_DETECTED on stdout (or a hang of `ifconfig wgX destroy`) is a
# positive; repeated CLEAN runs were observed in this campaign (race is
# sub-1% overlap per iteration; see VERDICT.md).
```
