# DF-3315 — in_pcblist_range: unsynchronized pcblist walk of non-shared pcbinfo (non-atomic marker dance)

## What

`in_pcblist_range()` (sys/netinet/in_pcb.c:2408-2495) exports pcb lists via
sysctl (`net.inet.raw.pcblist`, `net.inet.udp.pcblist`).  For a pcbinfo
initialized **without** a shared infotoken — `ripcbinfo`
(raw_ip.c:115 `in_pcbinfo_init(&ripcbinfo, 0, FALSE)`) — `GET_PCBINFO_TOKEN()`
is a no-op (in_pcb.h:439-449), so the walk has **no synchronization at all**.

The walk advances its list marker with a non-atomic two-step dance:

```c
while ((inp = LIST_NEXT(marker, inp_list)) != NULL && i < n) {   /* read next */
        LIST_REMOVE(marker, inp_list);                           /* unlink    */
        LIST_INSERT_AFTER(inp, marker, inp_list);                /* relink    */
        ... inp->inp_flags / prison_xinpcb(inp) / bcopy(inp, ...) ...
```

(in_pcb.c:2458-2471).  The marker only keeps the walk coherent when
`SYSCTL_OUT()` blocks (that is what it was designed for); it does **not**
make the advance atomic against a same-cpu netisr that concurrently removes
and frees the pcb being stepped to (`rip_detach → in_pcbdetach →
in_pcbofflist + kfree(inp)` — the free happens in the same dispatch,
in_pcb.c:1210-1226).  The twin walker `tcp_pcblist()`
(tcp_subr.c:1226-1300) has the identical dance and admits the assumption in
its own comment: *"but otherwise depend on being on the correct cpu to avoid
races"* — correct-cpu placement is sufficient for code running *in* netisr
context, but the sysctl walker is a **user thread** merely migrated onto that
cpu (`lwkt_migratecpu`, in_pcb.c:2450).

If a pcb removal lands between the `LIST_NEXT` read and the
`LIST_INSERT_AFTER`, the marker is relinked relative to an **unlinked and
freed** inpcb: `LIST_INSERT_AFTER` writes the marker pointer into freed
M_PCB memory, leaves `succ->le_prev` pointing into the freed chunk, and the
walk then reads freed (possibly recycled) memory (`inp->inp_flags`,
`prison_xinpcb` derefs `inp->inp_socket->so_cred`, `bcopy(inp,...)` exports
recycled heap contents to the reader).

## Reachability

* Reader half: any **unprivileged** user — `sysctl net.inet.raw.pcblist`
  (CTLFLAG_RD, no privilege gate).
* Churn half: raw-socket create/close requires root
  (raw_ip.c:473 `SYSCAP_NONET_RAW`) — **or an unprivileged user via the
  setuid-root `/sbin/ping`** (`-r-sr-xr-x` on the stock guest): each
  invocation creates and closes an ICMP raw socket, so an unpriv user can
  drive ripcb churn at will.

## Reproduce

```
scp pcb_race.c build.sh run.sh dfbsd:/tmp/df3315/
ssh dfbsd  'cd /tmp/df3315 && sh build.sh'
ssh dfbsd  'cd /tmp/df3315 && ./pcb_race -c 300 96 &'      # root churner
ssh dfbsd-maxx '/tmp/df3315/pcb_race -r 280'               # unpriv reader
# (or sh run.sh 280 — runs both; see VERDICT.md for the multi-reader runs)
```

Expected on success: kernel panic (LIST corruption / wild deref in
`prison_xinpcb`/`sotoxsocket`/`in_pcbofflist` KASSERT) or garbage
(recycled-heap) records among the dumped xinpcb entries.

## Result on this guest

NOT reproduced under maximal stress: ~13.8M sysctl dumps (~6x10^9 marker
advances) against ~7M concurrent pcb teardowns across three rounds
(115 s single reader @45k dumps/s; 230 s x 8 concurrent readers @6.5k
dumps/s each; churn 13-55k cycles/s).  No panic, no corruption, guest
healthy.  See VERDICT.md for why: on this kernel the teardown work in
netisr0 executes at the walker's blocking/yield points (where the marker
protocol protects the walk), not by preempting the walker mid-dance.

## Fix

`fix.diff` — wrap the marker advance plus the pcb snapshot in
`crit_enter()/crit_exit()` so the sequence is atomic against same-cpu netisr
preemption regardless of pcbinfo sharing.  The same treatment is needed for
the `tcp_pcblist()` twin in tcp_subr.c (out of this diff's scope).
