DF-5753 / verdict.json
{ "finding_id": "DF-5753", "status": "reproduced", "verdict": "Reproduced as a deterministic NULL-pointer kernel WRITE in netisr context: hci_intr()s scodone branch (hci_unit.c:387-406) walks unit->hci_links and calls hci_sco_complete(link,1) after releasing hci_devlock and with no reference on the matched link, while the unprivileged socket-close path sco_sabort() -> sco_disconnect(pcb,0) NULLs link->hl_sco (sco_upper.c:215) and runs hci_link_free() on the syscall thread, with the link staying on the list until hci_link.c:1019. hci_sco_complete (hci_link.c:884-892) unconditionally executes link->hl_sco->sp_pending--; the rig hit the window within the first rounds of 1500x[200 sends + disconnect], faulting at 0x1c (offsetof sco_pcb.sp_pending) with rax=NULL, in hci_sco_complete+0x7 on the netisr thread. The same window past the kfree (hci_link.c:1024) is a UAF write / wild sp_proto->complete call when the chunk is reused. Fix validated: devlock-guarded dispatch + devlock-guarded teardown (unit pointer saved before the free) -> 1500 rounds / ~28k completions with no panic.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "local unprivileged owner of an SCO socket sends sp_mtu-sized packets (in-flight TX completions pile up in hci_scodone via hci_complete_sco), then closes/aborts the socket: sco_disconnect NULLs hl_sco then spends ~40 lines in hci_link_free before the TAILQ_REMOVE; the netisr threads scodone membership walk matches the still-listed link in that window and writes *(NULL + 0x1c) -= 1 -> fatal page fault (kernel write). Extended window (past kfree): sp_pending-- on freed memory and a call through freed/reused hl_sco->sp_proto; with same-zone chunk reuse as a fresh hci_link the wrong link is credited (accounting corruption) or another NULL hl_sco is hit", "evidence": "panic.txt / run.log: Fatal trap 12 ... fault virtual address = 0x1c, supervisor write data ... current process = Idle ... Stopped at hci_sco_complete+0x7: subl $0x1,0x1c(%rax) after round 0; fix_run.log: post-fix all rounds done without a hit at round 1500 with ~28k completions (completes=28613) and guest up", "kernel_refs": [ "sys/netbt/hci_unit.c:390", "sys/netbt/hci_unit.c:395", "sys/netbt/hci_unit.c:397", "sys/netbt/hci_link.c:884", "sys/netbt/hci_link.c:1019", "sys/netbt/hci_link.c:1024", "sys/netbt/sco_upper.c:213", "sys/netbt/sco_upper.c:215", "sys/netbt/sco_socket.c:253" ], "poc_changes": "rig wires a production sco_attach() pcb to a production hci_link_alloc()d SCO link (sp_mtu=32) and uses production sco_send/sco_disconnect verbatim; the fake controllers output_sco calls production hci_complete_sco() instantly (what ubts USB completion interrupt does); 200 sends per round widen the netisr drain against the teardown", "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": 420, "guest_dirty": false, "build_cmd": "cd /root/hciuu && sh build.sh", "run_cmd": "kldload ./hci_unit_harness.ko; sysctl hw.hciu_setup=1; sysctl hw.hcius_fire=1500", "code_hash": "d6b73fd72e85527a98201eb9da96d1256e9d1bda0ddd017d55267f708cf018dc", "notes": "The completion-dispatch counterpart of DF-5737 (which is event-context teardown racing the syscall TX side); this is the netisr dispatch racing the syscall teardown side, different file (hci_unit.c) and sink (hci_sco_complete). The baseline run also logged one DF-5752 rig marker (that build had the DF-5752 re-inject enabled by default); it is unrelated to the SCO path. First fix iteration released the devlock through the already-freed link (lockmgr_release crash) - corrected by saving the unit pointer before hci_link_free; the shipped fix.diff is the corrected version.", "recommended_fix": "Run the scodone membership walk + hci_sco_complete() under hci_devlock, and perform sco_disconnect()s hl_sco=NULL + hci_link_free() teardown under the same lock (unit pointer saved before the free)", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "Applied fix.diff (devlock around the scodone dispatch in hci_intr + devlock around the sco_disconnect teardown with the unit pointer saved before hci_link_free), rebuilt the verbatim-source module, rebooted, re-ran hcius_fire=1500: all 1500 rounds completed with ~28k hci_sco_complete dispatches and no panic; guest stayed up.", "fix_evidence": "findings/poc/DF-5753/fix_run.log: rounds through 1475 with completes=28613 and final all rounds done without a hit; contrast panic.txt (crash at round <=25 pre-fix)" } |