DF-3316 / verdict.json
{ "finding_id": "DF-3316", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "The tcp_pcblist() marker-dance race exists by construction (source-proven): the sysctl user thread migrated per-cpu advances its marker with a non-atomic remove/relink pair (tcp_subr.c:1276-1277) with no synchronization against same-cpu netisr inpcb teardown (tcp_close -> in_pcbdetach -> LIST_REMOVE + kfree), and unlike DF-3315 (raw pcbinfo) BOTH sides here are fully unprivileged (world-readable sysctl + TCP connect/close churn on lo0). If a teardown preempts the walker inside the window, LIST_INSERT_AFTER relinks the marker after a freed inpcb (UAF write into freed M_PCB memory) and the walk then reads freed/recycled inpcbs and exports them to the unprivileged reader (heap info leak). Three stress runs on the stock INVARIANTS guest (best exposure: 3,684,422 unprivileged inpcb teardowns at 12,281/s intersecting 685,481 full pcblist walks, ~2x10^8 advance windows; cumulative ~3.7M teardowns / ~9.3M walks) produced no panic, no corruption, and no anomalous records - empirically matching DF-3315s finding that the lwkt scheduler does not preempt the walker inside the ~10 ns window; safety rests on scheduler behavior rather than any synchronization in the code. Filed as a hardening (Low/memcorrupt) with the same crit_enter()/crit_exit() treatment DF-3315 recommended for in_pcblist_range; fix.diff applies cleanly to sys/netinet/tcp_subr.c.", "exploit_chain": "(not demonstrated) unpriv sysctl net.inet.tcp.pcblist loop + unpriv TCP connect/close storm -> netisr preempts walker between LIST_REMOVE(marker) and LIST_INSERT_AFTER(inp, marker) -> teardown of inp (in_pcbdetach kfree) -> marker relinked into freed M_PCB chunk (UAF write) -> walk continues over freed/recycled inpcb (bcopy inp/inp_ppcb/sotoxsocket into xtcpcb exported to the unpriv reader = heap info leak; list corruption -> panic).", "evidence": [ "run.log (best-exposure run 3): churn 3,684,422 close cycles (12,281/s) as nobody; 2 readers 685k dumps; guest healthy, dmesg clean", "run.2.log (run 2): 4 readers + 128-conn churn, 44,659 teardowns, 2.05M walks, healthy", "tcp_pcb_race.c: unprivileged reader (-r) and unprivileged TCP churner (-c)", "VERDICT.md: structural proof + empirical scheduler analysis (mirrors DF-3315)", "fix.diff: crit_enter()/crit_exit() around advance + snapshot (same treatment as DF-3315)" ], "kernel_refs": [ "sys/netinet/tcp_subr.c:1261", "sys/netinet/tcp_subr.c:1265", "sys/netinet/tcp_subr.c:1269", "sys/netinet/tcp_subr.c:1271", "sys/netinet/tcp_subr.c:1276", "sys/netinet/tcp_subr.c:1277", "sys/netinet/tcp_subr.c:1285", "sys/netinet/tcp_subr.c:1288", "sys/netinet/in_pcb.c:265", "sys/netinet/in_pcb.c:1217", "sys/netinet/in_pcb.c:1225" ], "poc_changes": "adapted from DF-3315s pcb_race.c: reader switched to net.inet.tcp.pcblist; churner rewritten for unprivileged TCP churn (lo0 listener with nonblocking accept-drain + rotating connect/close, each accepted+closed conn being another netisr inpcb teardown). Initial churner revision stalled on a never-drained accept queue (run 2, 149 teardowns/s); fixed revision reached 12,281 teardowns/s.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1050, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o tcp_pcb_race tcp_pcb_race.c", "run_cmd": "./run.sh 300 (4+1 unpriv processes; best exposure: 2 readers + ./tcp_pcb_race -c 300 16)", "code_hash": "9236019071feab25ccde09c1824af981df79db65e14a288787f1fd6d2f6ccb5d", "notes": "Structural race is certain from source; practical triggerability is speculative - the lwkt scheduler did not preempt the walker mid-dance across ~2x10^8 advance windows with 12k unpriv teardowns/s (same empirical result as DF-3315 at 6x10^9 windows). Any future preemption change, or a pcb teardown path that runs in true interrupt context, re-opens the window. Distinct finding from DF-3315 (different file/function/sysctl/list; a fix to in_pcb.c does not fix tcp_subr.c) but same family/root-cause pattern; cross-referenced.", "recommended_fix": "wrap the tcp_pcblist marker advance + pcb/tcpcb snapshot in crit_enter()/crit_exit() (fix.diff), making the walk atomic against same-cpu netisr preemption instead of relying on scheduler behavior", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "Baseline never faulted (3 runs, best exposure 3.7M teardowns x 685k walks), so patched kernel no longer faults is vacuous - fix_status not_testable, mirroring DF-3315s fix-validation outcome. fix.diff is defense-in-depth making the advance atomic by construction; patch --dry-run -p0 verified it applies cleanly to sys/netinet/tcp_subr.c.", "fix_evidence": "fix.diff (applies cleanly; not boot-tested for the above reason)" } |