DF-5738 / verdict.json
{ "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" } |