⬢ DragonFlyBSD Kernel Audit
DF-3316 / verdict.json
← back to finding ↓ download raw
{
  "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)"
}