{
  "finding_id": "DF-5780",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Unsynchronized hci_memos lifecycle in sys/netbt/hci_misc.c: hci_memo_find() expiry-frees memos with no lock while reachable concurrently from unprivileged user context (L2CAP/SCO connect -> hci_route_lookup), netisr-0 (hci_event -> hci_memo_new, hci_devlock dropped at hci_unit.c:340) and softclock (hci_link_free -> hci_memo_new). Two fresh-boot PoC runs panicked in the slab allocator: kuhp < 0 usage-counter assert (double free) with the hci_memo_find (expiring) print on the panic line itself, and a chunk_mark_free zone-metadata assert with timer-context walker frames. Unprivileged uid-1001 racer, ~5-35s to panic. Fix (hci_devlock serialization) validated: 0 panics across 6 burst cycles + 12.4M-connect debug-off stress, dup=0 stuck=0.",
  "exploit_chain": "unprivileged user: socket(AF_BLUETOOTH,SOCK_SEQPACKET,BTPROTO_L2CAP)+connect loop (each connect walks+expiry-frees the whole memo list) racing the netisr inquiry-result walker over an all-expired list -> both walkers free the same struct hci_memo (48B, global size-class zone) -> double kfree + stale LIST_REMOVE + 16B microtime write-after-free at hci_misc.c:171 (clobbers slab freelist c_Next) + aliased double-insert self-links the list (multi-second walk stalls observed). uid=0 chain blocked on this kernel: kfrees always-on slab asserts (chunk_mark_free zone/bitmap checks, kmemusage counter) convert every duplicate free into an immediate panic before the freelist dup can be allocated through; the remaining UAF-write offers only wall-clock-valued data (no chosen value/address).",
  "evidence": "panic_run2.txt + boot_run2_panic.log last lines: hci_memo_find: memo 0xfffff801188e77e8 too old (expiring) immediately followed by panic assertion \"kuhp < 0\" failed ... kern_slaballoc.c; panic.txt: chunk_mark_free zone assert (run 1, timer-context trace); fix_run.log tail: patched run watchdog totals: dup=0 stuck=0, guest up",
  "kernel_refs": [
    "sys/netbt/hci_misc.c:113-138",
    "sys/netbt/hci_misc.c:145-173",
    "sys/netbt/hci_misc.c:175-181",
    "sys/netbt/l2cap_socket.c:351-367",
    "sys/netbt/l2cap_upper.c:140-172",
    "sys/netbt/hci_event.c:463",
    "sys/netbt/hci_event.c:500",
    "sys/netbt/hci_link.c:99-104",
    "sys/netbt/hci_link.c:1016",
    "sys/netbt/hci_unit.c:340-349",
    "sys/netbt/hci_unit.c:247-248"
  ],
  "poc_changes": "rewrote the seeded PoC sketch into (a) a full-production-stack KLD rig (all sys/netbt/*.c compiled verbatim, real PF_BLUETOOTH domain so the real unprivileged connect() path is exercised; fake controller only plays the hardware role; fabricated struct bsd_device so BLUETOOTH_DEBUG DPRINTFs are dereference-safe -- the first crash turned out to be that artifact and was fixed), (b) burst-mode race scheduling (freeze list, expire all, then release 6 user threads + netisr burst simultaneously -- steady-state churn keeps only 1-2 expired-unreaped memos alive and does not collide), (c) repeated-bdaddr flood to drive the found/update path, (d) a live watchdog kthread (walk-latency probe + canaryd 48B batch dup detection) after post-hoc checks missed transient dups",
  "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": 2400,
  "guest_dirty": 1,
  "build_cmd": "cd /root/hcir && sh build.sh",
  "run_cmd": "cd /root/hcir && sh run3.sh 6 30",
  "code_hash": "6c168c9afb371380a96fe1d45f15e645b22c8cf821127d44eb5e7cbea8480d89",
  "notes": "netbt not in guest GENERIC -> rig compiles the production stack verbatim into a KLD (same evidence class as DF-5734/5735/5736); module cannot kldunload (domain registration is permanent) -> each build requires vm.sh reset. Baseline reproduced 2/2 (two different slab asserts). Steady-state run.sh (17.6M connects, no collision) is kept as negative evidence that the race needs the burst pattern; watchdog stuck=1 in baseline run 2 = circular-list stall before the fatal double free. Residual post-fix window (memo field writes by callers after lock drop) documented in VERDICT.md.",
  "recommended_fix": "serialize all hci_memos list operations on unit->hci_devlock (locked/unlocked function split; see fix.diff)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.diff applied to guest /usr/src (patch -p1), module rebuilt from fixed production sources, exact PoC re-run: baseline 2/2 panics -> patched 0 panics over 6 burst cycles + 12.4M-connect debug-off stress; watchdog dup=0 stuck=0; guest stayed up (fix_run.log)",
  "fix_evidence": "findings/poc/DF-5780/fix_run.log (patched-run totals), fix.diff, VERDICT.md Fix validation"
}