# DF-5749 — Unsynchronized `l2cap_rtx()` vs `l2cap_detach()`/`l2cap_close()` double-teardown: kernel panic (page fault / zone double-free), `sys/netbt/l2cap_upper.c`

`l2cap_detach()` (the close(2) path, running as a netmsg on the cpu0
netisr protocol port) and the `l2cap_rtx()` response-timeout callback
(running on the softclock thread) both tear down the same
`l2cap_channel` with **zero synchronization** between the two threads.
The evidence rig compiles the production
`l2cap_upper.c / l2cap_lower.c / l2cap_misc.c / l2cap_signal.c`
**verbatim** into a KLD and drives exactly those two production
contexts.

## Contents

* `harness.c`, `stub.c`, `l2uh.h`, `Makefile` — KLD harness
  * `hw.l2uh_setup=1` — rig facts (sizes/offsets/hz)
  * `hw.l2uh_once=1` — CONTROL: one unraced round, must be clean
  * `hw.l2uh_race3=N` — BASELINE TRIGGER (unfixed kernels): a cpu1
    kthread fires production `l2cap_rtx()` the instant the orchestrator
    fires production `l2cap_detach()` on a freshly armed channel
    (models "softclock callback already entered").
  * `hw.l2uh_race4=N` — production-callout-delivery stress (armer cpu0,
    reaper cpu1).  NOTE: its armer also races active-list ops that
    production serializes on the protocol port — rig artifact, see
    VERDICT.md.
  * `hw.l2uh_race5=N` — the pure production pair only: one worker does
    every port-serialized op; a cpu0 helper only re-arms the rtx
    callout; detach lands on the firing tick.
  * `hw.l2uh_state=1` — counters.
* `build.sh` — copies the verbatim production sources from
  `/usr/src/sys/netbt` (md5s recorded in `setup.log`), builds the KLD.
* `run.sh` — baseline run: load, control round, `race3=2000`.
* `fixbuild.sh` — applies `fix.diff` to the guest's `/usr/src` and
  rebuilds (never touches the host `sys/` tree).
* `fix.diff` — v6 fix (global `l2cap_lock`, exactly-once request free,
  single-execution close claim + `L2CAP_CLOSING` completion flag).
* Logs: `build.log`, `setup.log`, `run.log`, `panic.txt`,
  `console_baseline_final.txt`, `console_panic.txt` (baseline);
  `fix_build.log`, `fix_run.log` (patched kernel).

## Reproduce (baseline panic)

```sh
# guest, as root:
kldload /root/l2uh/l2uh_harness.ko
sysctl hw.l2uh_setup=1     # facts
sysctl hw.l2uh_once=1      # control: clean
sysctl hw.l2uh_race3=2000  # EXPECT: kernel panic within seconds
```

**Expected result (vulnerable kernel):** panic.  Observed signatures
across 4 baseline runs:

1. `Fatal trap 12: page fault while in kernel mode`,
   `fault virtual address = 0x70`, `supervisor write data`,
   `Stopped at l2cap_request_free+0x70: movq %rdx,0x70(%r13)` —
   `TAILQ_REMOVE(&req->lr_link->hl_reqs, ...)` with `lr_link == NULL`
   on a freed/re-initialized request.
2. `panic: zone: freeing free entry`, `zfree() <- l2cap_rtx()` —
   double free of the `l2cap_req`.
3. Hard freeze (no trap output) under sustained churn.

**Patched kernel (fix.diff v6):** the control round and the burst rig
pass, but the pure production-pair stress (`race5`) still reaches a
post-`zfree` `l2cap_rtx()` invocation (fault address `0x12342380` =
`ZENTRY_FREE+8`: `req->lr_chan` of a freed zone entry) after ~500–5000
rounds — the `callout_stop()` drain does not close the stop-vs-dispatch
window on a foreign cpu's softclock queue.  **fix_status:
fix_failed**; a complete fix needs deferred request reclamation or the
upstream NetBSD model (global l2cap mutex + `callout_halt`-with-lock);
see VERDICT.md.

## Environment

DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC, 6 vCPU, `cc 8.3
[DragonFly]`, netbt not compiled into GENERIC (KLD).
