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