⬢ DragonFlyBSD Kernel Audit
DF-5738 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5738",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "hci_link_free() only drops L2CAP-channel references before demanding hl_refcnt==0; the SCO->ACL anchor reference taken by production hci_acl_open() (from hci_sco_newconn(), hci_link.c:810, or sco_upper.c:160) is never released, so a Disconnect_Complete for an ACL link with a live SCO anchor leaves hl_refcnt==1. On INVARIANTS kernels this is an immediate KKASSERT(hl_refcnt==0) panic (hci_link.c:952); on production GENERIC kernels KKASSERT is compiled out (systm.h:98-118) and the link is silently kfreed while the SCO link and its pcb still reference it. Verified on the guest with the verbatim-production KLD rig: SCO-first ordering (the order hci_cmd_reset assumes) is clean; ACL-first ordering deterministically leaves the SCO->hl_link dangling and the subsequent production SCO teardown crashed the kernel with a supervisor-write fault inside hci_link_free (Fatal trap 12, Stopped at hci_link_free+0x2cb: movq %rdx,0x78(%rax)). fix.diff (SCO-anchor release pass mirroring hci_cmd_reset) validated: identical sequence completes cleanly, disconnected callback fires exactly once, 0/256 spray-reclaim canaries touched, guest alive.",
  "exploit_chain": "hostile/quirky controller emits Disconnection_Complete(ACL) while a SCO link is anchored (nothing lets the host rely on controller ordering; a hostile transport chooses it at will; hci_cmd_resets SCO-first comment proves the dependency is real) -> hci_link_free(ACL) with hl_refcnt==1 -> [GENERIC] silent kfree, SCO->hl_link dangles -> next SCO activity (sco_disconnect / discon_compl(SCO) -> hci_link_free(SCO) -> hci_acl_close(freed)) writes into freed/reused 168-byte M_BLUETOOTH memory (demonstrated: fatal trap) -> with heap grooming of the bluetooth slab (connection churn is attacker-paced) this is a UAF-write primitive; not pushed to uid=0 (no Bluetooth hardware; KLD rig demonstrates the primitive). [INVARIANTS] immediate DoS panic.",
  "evidence": "panic.txt: full serial console - control (SCO-first) clean, then 2) ACL#5 freed ... DANGLING, then Fatal trap 12 supervisor WRITE in hci_link_free+0x2cb. run.log: complete transcript with refcnt accounting (anchor=1). fix_run.log: patched module, same sequence, links=0, sco_disconnected=2, 0/256 spray canaries, no trap.",
  "kernel_refs": [
    "sys/netbt/hci_link.c:810",
    "sys/netbt/hci_link.c:943",
    "sys/netbt/hci_link.c:952",
    "sys/netbt/hci_link.c:979",
    "sys/netbt/hci_event.c:629",
    "sys/netbt/hci_event.c:1046",
    "sys/netbt/sco_upper.c:160",
    "sys/sys/systm.h:98"
  ],
  "poc_changes": "Extended the DF-5733/5734 KLD rig pattern to the SCO paths: hci_stub.c gains a listening sco_pcb + btproto whose newconn op returns a stub pcb so the FULL production hci_sco_newconn() -> hci_acl_open() path runs verbatim; harness adds hlh_sco_anchor (con_req(SCO) event), hlh_fire_discon (discon_compl events, v=2 SCO-first control), hlh_uaf (anchor -> kill ACL -> 256x sizeof(hci_link) 0xA5 spray reclaim -> SCO teardown -> canary scan for the --freed->hl_refcnt write). Fake device_t changed from a raw 0xcafe0000 constant to a mapped blob with a valid nameunit string because production kprintf paths (hci_acl_recv:473 etc.) dereference it.",
  "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": 960,
  "guest_dirty": 0,
  "build_cmd": "cd /root/hcih && sh build.sh   (copies verbatim /usr/src/sys/netbt sources, md5s recorded, builds hci_link_harness.ko)",
  "run_cmd": "kldload /root/hcih/hci_link_harness.ko; sysctl hw.hlh_setup=1; sysctl hw.hlh_sco_anchor=1; sysctl hw.hlh_fire_discon=2; sysctl hw.hlh_uaf=1",
  "code_hash": "4080aafc01c62ecac3a037fa10265fafa88d1bfaef8f19b8f1a2b11eca666754",
  "notes": "KKASSERT is INVARIANTS-only on DragonFly (systm.h:98-118) - the guest GENERIC kernel compiles it out, which is why the production manifestation is a silent UAF rather than the panic the source suggests. The control run (SCO-first) proves the ordering sensitivity and matches hci_cmd_resets SCO-first comment (hci_event.c:1046-1060). Impact classified panic (deterministic crash; memory-corruption class with escalation potential documented).",
  "recommended_fix": "Release SCO links anchored via hl_link at the top of hci_link_free() before the refcount assertion (mirror hci_cmd_resets SCO-first pass) - see fix.diff.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff adds a SCO-anchor release pass inside hci_link_free() (plus, shared with DF-5739, an exactly-once atomic teardown guard and callout_drain/terminate). Applied to the guests /usr/src copy, module rebuilt, identical trigger re-run: SCO anchor released inside the ACL teardown (sco_disconnected=2), both links freed (links=0), no trap, and the spray-reclaim canary scan found 0/256 modifications - the freed-memory write is gone. Baseline crashed inside this exact sequence.",
  "fix_evidence": "fix_run.log (patched-module transcript); fix.diff; build.log"
}