# DF-3316 VERDICT — not_reproduced (structural race confirmed by source; practical trigger not achieved, mirroring DF-3315)

**Status:** not_reproduced. **Impact observed:** none (guest survived all
runs). **Confidence:** likely (the structural defect is certain from
source; triggerability depends on lwkt scheduler preemption behavior).

## Source-level proof of the missing synchronization

1. `tcp_pcblist()` runs on a **user sysctl thread**, migrated per-cpu
   (`lwkt_migratecpu`, tcp_subr.c:1265). Its own comment (1253-1259)
   says it "depend[s] on being on the correct cpu to avoid races" —
   sound only for code running inside the owning netisr, not for a
   preemptible user thread parked on it.
2. The marker advance is a non-atomic three-step sequence with no
   crit section: read-next (1271), unlink (1276), relink-after (1277).
3. The mutator runs in the owning netisr: `tcp_close()` →
   `in_pcbdetach()` (in_pcb.c:1210-1226) → `in_pcbremlists()`
   (`LIST_REMOVE(inp, inp_list)`, in_pcb.c:265) → `kfree(inp, M_PCB)`
   (in_pcb.c:1225). For TCP this is driven by **fully unprivileged**
   churn: every connect/close on lo0 (or any peer) tears an inpcb out
   of the walked list.
4. The reader itself is unprivileged: `net.inet.tcp.pcblist` has no
   privilege gate (only `prison_xinpcb` filtering), confirmed by the
   PoC readers running as `nobody`.
5. If a teardown preempts the walker between 1276 and 1277 and removes
   + frees `inp`, `LIST_INSERT_AFTER` writes the marker pointer into
   freed M_PCB memory; `succ->le_prev` points into the freed chunk; the
   walk then reads freed/recycled inpcbs (`inp_flags` 1279,
   `prison_xinpcb` 1281 → derefs `inp_socket->so_cred`, `bcopy(inp)`,
   `bcopy(inp_ppcb)`, `sotoxsocket` 1285-1292) and exports the
   recycled contents to the unprivileged reader.

This is the same defect pattern already filed as DF-3315 for
`in_pcblist_range()` on ripcbinfo; it is a **distinct instance**
(different file, function, sysctl OID, pcbinfo; a fix in in_pcb.c does
not fix tcp_subr.c), with strictly better reachability (no raw sockets
needed for churn — TCP connect/close suffices, at higher rates).

## Attempts (stock INVARIANTS kernel #0, DragonFly 6.5-DEVELOPMENT, 6 cpus)

| # | setup | duration | exposure | result |
|---|-------|----------|----------|--------|
| 1 | 4 unpriv readers + churner (broken rev: accept queue not drained) | 300 s | 7.47M dumps (~2.5x10^8 advances), ~260 teardowns | no fault |
| 2 | 4 unpriv readers + churner (128 conns) | 300 s | 2.05M dumps (~7x10^7 advances), 44,659 teardowns (149/s) | no fault |
| 3 | **2 unpriv readers + churner (16 conns, accept-drain)** | 300 s | 685,481 dumps (~2x10^8 advances incl. per-record steps), **3,684,422 teardowns (12,281/s)** | no fault |

Cumulative: ~9.3M full pcblist walks, ~3.7M inpcb teardowns, all
unprivileged. Zero panics, zero dmesg anomalies, all dumped records
well-formed, guests healthy after every run.

## Why it does not fire (analysis, same as DF-3315)

The teardown work executes in the owning netisr *thread* and is
processed while the walker is blocked or yielding — `SYSCTL_OUT`
copyout (2.1 TB moved across the runs) provides constant blocking
points, and at those points the marker is *linked* and the protocol
works exactly as designed (removal of the element before the marker
correctly re-links the marker). The lwkt scheduler did not preempt the
walker user thread inside the ~10 ns advance window often enough (or
at all) to intersect one of the 12,281/s removals of the exact element
being stepped to. Safety therefore rests on scheduler serialization
behavior, not on any synchronization in the code — a fragility (any
future preemption change, or a pcb teardown path that runs in true
interrupt context, re-opens the window), which is why this is filed as
a hardening (Low/memcorrupt) finding rather than a demonstrable crash,
exactly like DF-3315.

## Exploit chain (if it ever fired)

unpriv `sysctl net.inet.tcp.pcblist` loop + unpriv TCP connect/close
storm → marker relinked after freed inpcb → UAF write (marker pointer
into freed M_PCB chunk) + UAF read (recycled heap contents exported to
user via xt_inp/xt_tp) → list corruption → panic; recycled-chunk
contents could include other pcbs' data (info leak). Not demonstrated.

## Fix validation

not_testable — the baseline never reproduced the fault, so "patched
kernel no longer faults" is vacuous (same as DF-3315's fix
validation). `fix.diff` (crit_enter/crit_exit around advance +
snapshot; applies cleanly, `patch --dry-run -p0` verified) makes the
walk atomic by construction instead of by scheduler behavior.
Recommend fixing both instances (in_pcb.c via DF-3315's diff,
tcp_subr.c via this one).

## Known-findings fix status checked during this pass (tcp_subr.c)

* DF-0336 (tcp6_getcred live so_cred pointer to blocking copyout):
  **unfixed** — tcp_subr.c:1387 still `SYSCTL_OUT(req,
  inp->inp_socket->so_cred, ...)`.
* DF-0337 (tcp_pcblist raw inpcb/tcpcb copyout): **unfixed** —
  bcopy at 1285/1288 intact.
* DF-0338 (tcp_mtudisc forged-MTU → corrupted t_maxseg): **unfixed** —
  no lower bound at 1802-1871; note the v6 blind path (DF-3317) makes
  it trivially reachable without a seq guess, and mtu=60+TCP_NOOPT
  yields t_maxseg==0 → the tcp_close:1013 divide-by-zero panic.
* DF-0339 (tcp_new_isn signed last_offset wrap): **unfixed** —
  tcp_subr.c:1674/1707/1742 intact.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
