DF-5740 / verdict.json
{ "finding_id": "DF-5740", "status": "reproduced", "reproduced": true, "verdict": "hci_mtap() appends to so_rcv via sbappendaddr (hci_socket.c:1004) and hci_drop() mutates so_snd via sbdroprecord/sowwakeup (hci_socket.c:437-438) from the NETISR port of the queueing CPU, while socket ops serialize on netisr_cpuport(0) and user threads hold only ssb_lock/ssb_token - none of which excludes the netisr mutator. Demonstrated: panic receive 1 (uipc_socket.c:1348 KASSERT, sockbuf ssb_mb/ssb_cc desync) in 24s under open/send/close + completion-flood on a 6-cpu INVARIANTS kernel, plus accumulating non-EWOULDBLOCK send failures (race5) and hard cpu wedges (race2/4/5) from the same family. The hci_drop context pointer (M_SETCTX, hci_socket.c:523) additionally holds no socket reference and hci_cmdwait_flush (hci_socket.c:446-465) cannot see mbufs already dequeued by the completion path (window widened by hci_mtap, hci_unit.c:494), so hci_drop can execute sbdroprecord+m_freem on a socket freed by hci_sdetachs sofree (hci_socket.c:574) - post-detach UAF write / double-free / cross-socket record theft, mechanism verified line-by-line (silently manifests on non-INVARIANTS kernels). fix.diff (sockbuf tokens + ctx-owned soreference/sofree + devlock-guarded single-consumer ctx consume) validated by rebuilding the verbatim rig against the patched tree and re-running the identical race: 425+ iterations clean vs baseline panic at ~100-150.", "impact": "panic", "confidence": "certain", "exploit_chain": "unpriv local user with HCI unit present: open promiscuous tap sockets (full filters) + raw-socket command traffic -> hci_mtap sbappendaddr on netisr races user soreceive -> sockbuf record-list/accounting desync (panic on INVARIANTS; on production: lost records + live ssb_cc => permanent sosend/soreceive stall, stale sb_lastrecord write into freed mbuf, mbuf double-free) ; concurrently close command sockets -> hci_cmdwait_flush misses in-flight dequeued commands -> hci_drop(freed socket) writes freed/reallocated socket slot and frees its records (cross-object corruption). uid=0 not completed: needs objcache grooming of the recycled socket slot.", "evidence": "panic.txt (panic: receive 1, soreceive+0xd1f trace, cpu4 vs cpu3 injector); fix_run.log (patched: 425+ iters clean, 0 failures); race5.log (96 non-EWOULDBLOCK send failures); VERDICT.md (full mechanism + confound analysis)", "kernel_refs": [ "sys/netbt/hci_socket.c:1003", "sys/netbt/hci_socket.c:433", "sys/netbt/hci_socket.c:446", "sys/netbt/hci_socket.c:522", "sys/netbt/hci_socket.c:567", "sys/netbt/hci_unit.c:494", "sys/netbt/hci_event.c:353", "sys/kern/uipc_socket.c:259", "sys/kern/uipc_socket.c:1348", "sys/kern/uipc_sockbuf.c:510" ], "poc_changes": "seed had no rig; built production-code KLD rig compiling hci_socket.c+engine+bt_proto.c verbatim (real AF_BLUETOOTH domain registration so userland socket(2) reaches production code), fake hci_attach unit with num_cmd_pkts=0, CPU-pinned completion injector; taps drain in-loop so DF-5742s unbounded tap does not drown the race (earlier non-draining runs crashed via mbuf-exhaustion NULL-m path - documented as confound in panic.txt)", "attempts": 6, "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": 1500, "guest_dirty": 0, "build_cmd": "sh build.sh (guest, root): cp /usr/src/sys/netbt/{hci_socket,hci_event,hci_link,hci_misc,hci_unit,bt_input,bt_proto}.c verbatim + make KLD + cc trigger", "run_cmd": "kldload ./hci_sock_harness.ko; sysctl hw.hsh_setup=1; sysctl debug.trace_on_panic=1 debug.debuger_on_panic=0; (sysctl hw.hsh_fire=99999 &) ; DF5740_FEEDER=1 ./trigger 8000 6 200", "code_hash": "36cce5de61f3bd682124f10bfdffa81bca1bde066ce3d6ada32c8bee906b1133", "notes": "race1/2 page-fault (hci_input_event+0x42 NULL m) was exhaustion-mediated (rig taps not draining + DF-5742 unbounded append + KKASSERT no-op in kmod + _m_copyback2(NULL) success) - NOT claimed as this findings proof; decisive proof is race6 panic: receive 1. cpu0 wedge evidence: send_ipiq 3->0 tgt not draining (panic.txt). Guest reset with-src after run; /usr/src restored+verified md5 before reset.", "recommended_fix": "hold so_rcv/ssb_token around hci_mtaps sbappendaddr/sorwakeup and so_snd token around hci_sends sbappendrecord and hci_drops sbdroprecord/sowwakeup; make the M_SETCTX completion context own a socket reference (soreference at hci_send, sofree in hci_drop and in hci_cmdwait_flush); consume the ctx under unit->hci_devlock exactly once (hci_output_cmd NULLs it under devlock, flush too). See fix.diff (validated).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to guest /usr/src/sys/netbt (hci_socket.c+hci_unit.c), rig rebuilt verbatim, identical race re-run: 425+ open/send/close iterations (85k+ commands), 0 send failures, no panic, no wedge vs unfixed panic at ~100-150 iterations; stopped manually. Bad behavior gone.", "fix_evidence": "fix_run.log (clean iterations tail); fix.diff (the validated patch)" } |