DF-3373 / verdict.json
{ "finding_id": "DF-3373", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Unprivileged local kernel panic reproduced twice from clean snapshots (<=45s hammer, identical signature: panic: already linked in in_pcbunlink_flags via tcp_connect) caused by a check-then-act TOCTOU between soconnect/solisten socket-layer flag reads and handler-side state: with the default async connect path SS_ISCONNECTING is stored only in tcp_usr_preconnect after soconnects checks, so a concurrent listen(2) slips through and tcp_usr_listen/tcp_connect process both messages, linking the same inpcb into the wildcard AND connection hashes. On production (non-INVARIANTS) kernels this is memory corruption: in_pcb_rmhash removes only one membership, leaving a dangling connhash entry to the freed inpcb (UAF on attacker-chosen 4-tuple lookups) -- documented from source, not demoable on this INVARIANTS guest since every dual-membership path asserts first. Handler-side fix (fix.diff v3: TF_LISTEN early-out before reject-guard, state guards incl. mid-migration inp_pcbinfo==NULL, SO_ACCEPTCONN undo under so_rcv token, EOPNOTSUPP for connect-on-listener without soisdisconnected) built in-guest (make nativekernel) and validated: 377k iters/45s and 1.02M iters/120s of the identical hammer SURVIVED with ssh/TCP fully functional (vs <=45s-to-panic baseline).", "exploit_chain": "unpriv 2-thread connect/listen race (<=45s) -> inpcb linked into wildcard+conn hashes -> INVARIANTS: panic already linked (reproduced); production: in_pcb_rmhash one-sided removal at close -> dangling connhash bucket entry -> in_pcblookup_hash traverses freed inpcb on attacker-chosen 4-tuple -> UAF memory-corruption ceiling (uid=0 chain plausible but blocked on this INVARIANTS guest by the asserts; not pursued further)", "evidence": [ "panic.txt / boot_panic.log: panic: already linked backtrace in_pcbunlink_flags <- tcp_connect (both baseline runs)", "run_fixed.log: 377194 connect iters / 45s on fix-v3 kernel, 140024 EOPNOTSUPP rejections, SURVIVED", "run_fixed.2.log: 1024176 iters / 120s, SURVIVED", "fix.diff: 4 handler-side guards (listen v4/v6, connect v4/v6)", "env.txt: guest config (soconnect_async=1, 6 cpus, INVARIANTS)" ], "kernel_refs": [ "sys/netinet/tcp_usrreq.c:481-513", "sys/netinet/tcp_usrreq.c:1146-1236", "sys/netinet/tcp_usrreq.c:960-972", "sys/kern/uipc_socket.c:330-343", "sys/kern/uipc_socket.c:684-713", "sys/kern/uipc_msg.c:204-249", "sys/netinet/in_pcb.c:307-321", "sys/netinet/in_pcb.c:1885-1914", "sys/netinet/in_pcb.c:2283-2293", "sys/netinet/in_pcb.c:2367-2395", "sys/netinet/tcp_input.c:950" ], "poc_changes": "PoC authored for this run (no usable seed): two threads sharing an fd published via atomics; connector loops socket()+nonblocking connect(10.0.2.2:1) with 30us drain; listener spins listen(fd,1); fresh socket per iteration so each attempt gets a clean race; counters distinguish EOPNOTSUPP/EINPROGRESS/listen-ok.", "attempts": 4, "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": 255, "guest_dirty": 0, "build_cmd": "cc -O2 -pthread -o listen_connect_race listen_connect_race.c", "run_cmd": "./listen_connect_race 45 (baseline: panic <=45s; fix-v3: SURVIVED; longer: ./listen_connect_race 120)", "code_hash": "721d4f02e034991971d29d27993c12bc910b905afa1a4a32474d3c089e09ba2f", "notes": "Fix iteration was itself adversarial: fix-v1 (guards only, no SO_ACCEPTCONN undo) crashed in in_pcbdetach+0x15 via tcp_input.c:950 listener branch on a rejected-but-flagged socket; fix-v2 (undo before TF_LISTEN early-out) crashed because re-listen cleared SO_ACCEPTCONN on a healthy listener; v3 orders TF_LISTEN early-out first, exempts PRUL_RELINK re-entry, and avoids soisdisconnected on listeners (sonewconn children inherit parent so_state, uipc_socket2.c:383). Baseline reproduced 2x from clean with-src snapshots.", "recommended_fix": "Handler-side race-free guards in tcp_usr_listen/tcp6_usr_listen (reject non-TCPS_CLOSED/connecting/mid-migration and undo solistens SO_ACCEPTCONN) and tcp_connect/tcp6_connect (reject TF_LISTEN with EOPNOTSUPP) -- see fix.diff.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep 11 17:25:03 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff v3 applied to guest /usr/src, kernel rebuilt (make nativekernel) and installed: the identical hammer that panicked the stock kernel <=45s survived 377k iterations/45s and 1.02M iterations/120s with clean EOPNOTSUPP/EINVAL returns and functional TCP (ssh). Baseline panic reproduced twice before the patch.", "fix_evidence": "run_fixed.log, run_fixed.2.log (SURVIVED counters), panic.txt (baseline), fix.diff, VERDICT.md fix-validation section" } |