# DF-5763 — `netbt_dispose` wired as `dom_dispose` is a dead-code booby trap: it destroys the global netbt vm_zones per socket flush, `sys/netbt/bt_proto.c`

`btdomain.dom_dispose = netbt_dispose` (bt_proto.c:163) misreads the
DragonFly `dom_dispose` contract: that hook is the **per-socket,
per-flush** rights-mbuf disposal callback, invoked from
`sys/kern/uipc_socket.c:1611` and `:1987` on every
`sorflush()`/receive-cleanup of a `PR_RIGHTS` socket — it is **not** a
domain-teardown hook. `netbt_dispose()` (bt_proto.c:128-134) ignores its
mbuf argument and unconditionally `zdestroy()`s the three **global**
`vm_zone`s (`l2cap_pdu_pool`, `l2cap_req_pool`, `rfcomm_credit_pool`).
`zdestroy()` frees the zone structure itself (vm_zone.c:543), so a
second flush of *any* bluetooth socket operates on freed memory, and any
later `zalloc` from those pools is a UAF.

**Currently dead code:** no netbt `protosw` sets `PR_RIGHTS`
(bt_proto.c:73,87,102,116), and both call sites gate on it — proven on
the guest: 8 socket+shutdown rounds with production flags never disturb
the pools.

**One bit away from a global UAF:** the rig flips exactly one flag bit
(`btsw[0].pr_flags |= PR_RIGHTS`, e.g. a future PR_RIGHTS-capable
protosw or flag refactor) and the very next socket shutdown runs
production `netbt_dispose` → deterministic `Fatal trap 12` in
`zdestroy+0x19` (3/3 runs, 2 clean boots).

## Contents

* `btdisp_rig.c`, `stub.c`, `Makefile` — KLD rig; `build.sh` compiles
  the **verbatim** production `/usr/src/sys/netbt/bt_proto.c` (md5
  `e68d6f9f555dc28d0d4ff6d9b26fe4ed`, matches the audit tree) into the
  rig and registers the production `btdomain`.
* `btdisp.c` — userland trigger: `socket(AF_BLUETOOTH, SOCK_RAW,
  BTPROTO_HCI)` + `shutdown(SHUT_RD)`.
* `control.sh` — control phase (production flags; must be clean).
* `armed.sh` — arms `PR_RIGHTS` on `btsw[0]` via `sysctl hw.btdisp.arm=1`,
  then fires 4 rounds.
* `run.sh` — both phases (armed phase expected to panic).
* `build.log`, `setup.log`, `env.txt` — verbatim-source proof, build
  output, guest environment.
* `run.control.log` — control evidence (pools stable, 8 rounds clean).
* `panic.txt`, `panic.2.txt`, `run.log` — three deterministic baseline
  panics at `zdestroy+0x19` from three separate boots.
* `fix.diff` — one-line fix: `.dom_dispose = NULL` (+contract comment).
* `fix_build.log`, `fix_run.log` — fix validation: patched rig survives
  the armed phase.

## Reproduce

```sh
# guest, as root:
sh build.sh
sh control.sh          # CONTROL: 8 rounds, no dispose, guest healthy
# (baseline) armed phase — EXPECT kernel panic:
sh armed.sh            # Fatal trap 12 ... Stopped at zdestroy+0x19
```

**Expected (vulnerable wiring, armed):** `btdisp: ARMING btsw[0]
pr_flags 0x00000003 -> 0x00000013` then

```
Fatal trap 12: page fault while in kernel mode
fault virtual address	= 0xfffff80118584068
fault code		= supervisor read data, page not present
current process		= Idle		(netisr protocol thread)
Stopped at      zdestroy+0x19:  testb   $0x40,0x4068(%rdi)
```

**Expected (fix.diff applied to /usr/src, rig rebuilt):** armed phase
completes all 4 rounds, `dom_dispose=0`, pools untouched, guest healthy.

## Environment

DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, Jul 2 2026), cc 8.3
[DragonFly], netbt not compiled into GENERIC (KLD), 6 vCPU
(`net.netisr.ncpus: 6`).
