⬢ DragonFlyBSD Kernel Audit
DF-5763 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5763",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "netbt_dispose (bt_proto.c:128-134) is wired as btdomain.dom_dispose (bt_proto.c:163), but DragonFlys dom_dispose is the per-socket PR_RIGHTS flush hook (uipc_socket.c:1611,1987), not a domain-teardown hook; it unconditionally zdestroy()s the three GLOBAL netbt vm_zones, and zdestroy frees the zone struct itself (vm_zone.c:543), so any second bt-socket flush or later zalloc is a UAF/double-free. Production never calls it because no netbt protosw sets PR_RIGHTS \u2014 proven on the guest: 8 socket+shutdown rounds with production flags left the pools untouched (control). Setting exactly one flag bit (btsw[0].pr_flags |= PR_RIGHTS) made the next shutdown run production sorflush->netbt_dispose->zdestroy and take a deterministic Fatal trap 12 at zdestroy+0x19, 3/3 runs across 2 clean boots; the modules real unload protection is MOD_UNLOAD returning EBUSY. Dead code today, one bit from a global bluetooth-state UAF, hence severity Info; fix.diff (.dom_dispose = NULL) validated: armed phase survives on the patched rig.",
  "exploit_chain": "precondition: any netbt protosw gains PR_RIGHTS (one bit; e.g. SCM_RIGHTS-capable socket refactor) -> userland socket(AF_BLUETOOTH,SOCK_RAW,BTPROTO_HCI) + shutdown(SHUT_RD) -> sorflush (uipc_socket.c:1986-1987) -> netbt_dispose -> zdestroy x3 (global pools, zone structs kfreed) -> second flush of any bt socket (trivially: close() of the same socket) -> zdestroy on freed zone -> UAF read/write + double-free; any subsequent l2cap request/pdu allocation zallocs from a destroyed pool",
  "evidence": "run.control.log (control: 8 rounds, pools bit-identical, dom_dispose=0xffffffff82600200 == nm netbt_dispose module+0x200); panic.txt / panic.2.txt / run.log (3x deterministic Fatal trap 12 ... Stopped at zdestroy+0x19: testb $0x40,0x4068(%rdi) right after the ARMING console line); setup.log+build.log (verbatim bt_proto.c md5 e68d6f9f...); fix_run.log (patched: dom_dispose=0, armed 4 rounds survive)",
  "kernel_refs": [
    "sys/netbt/bt_proto.c:128",
    "sys/netbt/bt_proto.c:131",
    "sys/netbt/bt_proto.c:133",
    "sys/netbt/bt_proto.c:163",
    "sys/kern/uipc_socket.c:1609",
    "sys/kern/uipc_socket.c:1611",
    "sys/kern/uipc_socket.c:1986",
    "sys/kern/uipc_socket.c:1987",
    "sys/vm/vm_zone.c:516",
    "sys/vm/vm_zone.c:543"
  ],
  "poc_changes": "built from scratch (no seed): KLD rig compiles the VERBATIM production bt_proto.c (md5-matched) with stubbed socket-layer symbols; userland trigger socket+shutdown(SHUT_RD); arm sysctl flips one bit (PR_RIGHTS) on btsw[0]",
  "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": 1500,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df5763 && sh build.sh   (copies verbatim /usr/src/sys/netbt/bt_proto.c, make -m /usr/src/share/mk SYSDIR=/usr/src/sys, cc trigger)",
  "run_cmd": "sh control.sh   # control: must be clean; then sh armed.sh   # arms PR_RIGHTS, expect panic at zdestroy+0x19",
  "code_hash": "91a70b3c46bcafc9e2bfee5510670cb6697909f3d091775de61608ff947437cc (sha256 of btdisp_rig.c+stub.c+btdisp.c; per-file hashes in code_hashes.txt)",
  "notes": "impact=panic refers to the armed-precondition demonstration (3/3 deterministic); production-config reachability is DISPROVEN by the control run \u2014 severity is Info (latent booby trap, certain deadness). netbt_dispose kept (netbt_init fail path bt_proto.c:154 calls it); only the dom_dispose wiring is wrong. Guest left clean (vm.sh reset with-src; rig unloaded).",
  "recommended_fix": "set btdomain.dom_dispose = NULL in sys/netbt/bt_proto.c (see fix.diff); domain teardown, if ever needed, belongs in MOD_UNLOAD after draining users",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to guest /usr/src/sys/netbt/bt_proto.c (patch hunk #1 succeeded at 160); rig rebuilt from the patched verbatim source (md5 78082bfb...); hw.btdisp.info reports dom_dispose=0; control clean AND armed phase (PR_RIGHTS set) completes 4/4 rounds with pools untouched and guest healthy \u2014 baseline panic 3/3 vs patched 0/1",
  "fix_evidence": "fix_build.log, fix_run.log (dom_dispose=0, round 4/4 ... still alive), setup.log patched md5s; baseline evidence in panic.txt/panic.2.txt/run.log",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64 (single-file fix validated via verbatim-source rig; netbt is not part of GENERIC)"
}