# DF-3376 — nd6_rtr.c: lockless `pfxlist_onlink_check()` from `nd6_ra_input` races `nd6_timer` expiry-sweep frees → use-after-free, fatal kernel panic

## What

`nd6_ra_input()` (sys/netinet6/nd6_rtr.c:410) ends every accepted Router
Advertisement with a call to `pfxlist_onlink_check()` **without taking
`nd6_mtx`**. That function walks the global `nd_prefix` list
(nd6_rtr.c:1181/1192/1233), dereferences `pfxrtr->router->rtaddr/.ifp`
through each prefix's advertising-router list (nd6_rtr.c:1146-1152),
**writes** `pr->ndpr_stateflags` (nd6_rtr.c:1204-1209), and mutates the
route tables via `nd6_prefix_onlink/offlink`.

Meanwhile `nd6_timer_dispatch()` (nd6.c:461-692, netisr CPU0, **holding
`nd6_mtx`**) frees exactly those objects when router/prefix lifetimes
expire: `defrtrlist_del()` → `pfxrtr_del()`/`kfree(pfxrtr)` (nd6_rtr.c:739),
`kfree(dr)` (nd6_rtr.c:576), and `prelist_remove()` → `kfree(pr)`
(nd6_rtr.c:845). The mutex does not protect the RA-side walker because the
walker never takes it. RA input runs on any netisr CPU, so the two sides
execute truly in parallel.

Result: use-after-free reads AND writes (`ndpr_stateflags` bit-set/clear at
a fixed offset inside a freed, reusable `M_IP6NDP` chunk), with the freed
chunks re-allocated from the same zone by the walker's own RA traffic
(`nd_defrouter`, `nd_pfxrouter`, `nd_prefix` all share `M_IP6NDP`).

Demonstrated manifestation (2/2 runs): the corruption cascades into the
per-CPU routing tables and the kernel takes the intentional fatal
replication check:

```
panic: rtrequest1_msghandler: rtrequest table req 1, failed on cpu1, error 55
```

(sys/net/route.c:765 — an RTM_ADD that succeeded on cpu0 fails ENOBUFS on
cpu1; per-CPU route tables must be identical, so this is fatal by design.)

## Threat model

* Unauthenticated **on-link attacker** (same L2 segment / VM bridge), or a
  compromised peer — identical to DF-0417/DF-0418.
* Victim: any host with `ND6_IFF_ACCEPT_RTADV` on an interface (the normal
  IPv6 RA-managed configuration), not acting as IPv6 router.
* Packet constraints: hlim=255, link-local source, valid ICMPv6 checksum —
  all trivially met by the attacker's own stack. No auth, no state needed.
* Attacker controls both sides of the race: router/prefix lifetimes
  (expiry frees) and the RA-with-SLLA walk rate.

## Reproduce

```
# guest (root): rig + 6 blasters, ~1-2k pps aggregate
ifconfig gif0 create
./rarace setup gif0          # ::1<->::1 tunnel + ACCEPT_RTADV + up
for i in 1 2 3 4 5 6; do ./rarace blast -i $i > r2.blast.$i.log 2>&1 & done
# expect panic within 2-5 minutes; console shows the rtrequest1_msghandler panic
```

Controls (both clean, proving the race needs BOTH sides):

```
./rarace blast -i $i -W 0 ...   # churn-only: 10 min, 6.1M RAs, NO crash
./rarace blast -i $i -C 0 ...   # walk-only:   8 min, 7.8M RAs, NO crash
```

## Files

* `rarace.c` — injector (gif self-tunnel rig from DF-3331): "churner" RAs
  (fresh link-local src, router lifetime 1s, PIO /64 L=1, vltime 2s; epoch
  flip every 3s so half the population stops being refreshed and is swept)
  and "walker" RAs (lifetime 0, SLLA → nd6_ra_input:410 walk).
* `build.sh` / `run.sh` — exact commands.
* `panic.raw.txt`, `panic2.raw.txt` — serial console of the two fatal runs.
* `fix.diff` — first-attempt fix: `mtx_lock(&nd6_mtx)` around the :410 call
  (same discipline `prelist_update` already uses at nd6_rtr.c:916→732/1120).
  **FALSIFIED by experiment**: the patched kernel (#1) still panics
  identically (fix_panic.txt) — the lockless defect spans the whole RA tail;
  see VERDICT.md.
* `VERDICT.md`, `manifest.json`, `verdict.json` — full analysis + verdicts.

## Root privilege?

The write primitive is narrow: a 2-bit set/clear (`NDPRF_DETACHED`=0x2,
`NDPRF_ONLINK`=0x1) at the `ndpr_stateflags` offset inside a freed
`M_IP6NDP` chunk, racing-driven and probabilistic, plus LIST-walk reads
through freed `nd_prefix::ndpr_next` / `nd_pfxrouter::router`. Converting
that to controlled corruption requires precise zone grooming against a
6-CPU race; not developed here (see VERDICT.md "blocker"). Demonstrated
and reproduced impact: remote-triggerable fatal kernel panic (DoS).
