DF-3315 / verdict.json
{ "finding_id": "DF-3315", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "speculative", "verdict": "Structural race confirmed in source: in_pcblist_range (sys/netinet/in_pcb.c:2456-2476) walks non-shared pcbinfo lists (ripcbinfo, infotoken==NULL) with zero synchronization and advances its marker via a non-atomic read/unlink/relink dance; a same-cpu netisr teardown (rip_detach -> in_pcbdetach: in_pcbofflist + kfree, in_pcb.c:1210-1226) landing inside the window would relink the marker after a freed inpcb (UAF write of a pointer into freed M_PCB memory) and read/export freed-recycled memory (UAF read). Reader half is fully unprivileged (sysctl net.inet.raw.pcblist); churn half is root-gated raw sockets but unpriv-drivable via setuid /sbin/ping. NOT reproduced under maximal stress: ~13.8M sysctl dumps (~6e9 marker advances) vs ~7M concurrent teardowns across 3 rounds (115s single reader @45k dumps/s, 230s x 8 concurrent readers) - no panic, no corruption, guest healthy. On this kernel the netisr0 teardown work executes at the walkers blocking points where the marker protocol works as designed; safety rests on scheduler serialization, not on synchronization in the code. Filed as Low/speculative hardening; fix.diff makes the advance+snapshot atomic via crit_enter/crit_exit. Twin non-atomic dance in tcp_subr.c tcp_pcblist (fully unpriv churn) needs the same fix.", "exploit_chain": "if the window were entered: unpriv sysctl net.inet.raw.pcblist loop + unpriv setuid-ping churn -> LIST_INSERT_AFTER on freed inpcb -> pointer write into freed M_PCB chunk + succ->le_prev into freed chunk -> list corruption -> panic; freed-recycled inpcb contents bcopyd to user (info leak). Not demonstrated.", "evidence": "VERDICT.md (full analysis + attempts table); run.log (3 rounds, ~13.8M dumps / ~7M teardowns / 0 panics); pcb_race.c (reader+churn harness); fix.diff (crit_enter protection, patch -p1 --dry-run verified)", "kernel_refs": [ "sys/netinet/in_pcb.c:2456", "sys/netinet/in_pcb.c:2458", "sys/netinet/in_pcb.c:2459", "sys/netinet/in_pcb.c:2460", "sys/netinet/in_pcb.c:2469", "sys/netinet/in_pcb.c:1210", "sys/netinet/in_pcb.c:1217", "sys/netinet/in_pcb.c:1225", "sys/netinet/in_pcb.c:255", "sys/netinet/raw_ip.c:115", "sys/netinet/tcp_subr.c:1226" ], "poc_changes": "Seed sketch (none existed - new finding): wrote pcb_race.c with -r reader (unpriv sysctl hammer, dynamic sizing+data passes) and -c churner (root, holds 96 raw ICMP sockets, close+recreate round-robin); run.sh wrapper plus multi-reader stress.sh variant used in round 3.", "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": 700, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o pcb_race pcb_race.c", "run_cmd": "./pcb_race -c 300 96 & ; su maxx -c /tmp/df3315/pcb_race -r 280 (and 8x concurrent readers via stress.sh)", "code_hash": "3c2c982b8e53b241c47c2608497b6ae0966e24a1ca5742ffd05f90f76d02cd7d", "notes": "Known findings DF-0333/0334/0335 re-checked during this pass: all UNFIXED in tree (in_pcb.c:2469 raw bcopy intact; :428/:885 div0 intact; :2532-2555 portrange intact); DB status new. sotoxsocket (uipc_socket2.c:827) also exports xso_so/so_pcb raw pointers - same sink/fix as DF-0333. Killed candidates documented in VERDICT.md/negative notes: NULL inp_socket deref in in_pcbbind_laddrport_check (only assignment is in_pcb.c:286); cross-cpu 4-tuple duplication (udp/tcp connect migrate to netisr_hashport before final check: udp_usrreq.c:1620-1638, tcp_usrreq.c:1205+); in_pcbpurgeif0 imo UAF (in_delmulti never blocks - igmp_leavegroup/igmp_sendpkt NOWAIT path; membership add/del netisr0-only via ASSERT_NETISR0); in_pcbsrcaddr_check KKASSERT(sin_len) (kernel-set at udp_usrreq.c:971); localgroup sort bounds (il_inpcnt<il_inpsiz invariant, in_pcb.c:2200).", "recommended_fix": "Wrap the in_pcblist_range marker advance + pcb snapshot in crit_enter()/crit_exit() (see fix.diff); apply the same to tcp_pcblist() in tcp_subr.c." } |