# DF-0724 — Unsynchronized timer-vs-caller double-close race in rfcomm_dlc_close

## Finding

**File:** `sys/netbt/rfcomm_dlc.c:156-183` (Low severity)
**Title:** Unsynchronized timer-vs-caller double-close race in `rfcomm_dlc_close` — no lock/atomic on CLOSED transition
**Summary:** `rfcomm_dlc_close` (lines 151-183) has no lock or atomic state guard. The `KKASSERT(rd_state != CLOSED)` at line 156 cannot catch the race: T1 (network input DISC/DM/UA from `rfcomm_session.c:784,819,876,911`) and T2 (softclock timer `rfcomm_dlc_timeout` at line 195) both read non-CLOSED before either writes CLOSED at line 168. T1's `callout_stop` (line 164) blocks while T2 completes the full close (LIST_REMOVE line 166, `rd_session=NULL` line 167, `state=CLOSED` line 168, disconnected callback line 170, session-expiry lines 177-183). T1 resumes: double LIST_REMOVE, double disconnected callback, double session-expiry scheduling. The `crit_enter` in `rfcomm_dlc_timeout` (line 199) only blocks IPIs/softints on T2's CPU; it does not serialize T1 on a different CPU. None of the input-path callers take `crit`. The current socket consumer `rfcomm_disconnected` (`rfcomm_socket.c:166-176`) is idempotent, so practical impact is nil today, but this is a genuine unsynchronized double-close with list-corruption risk for a non-idempotent upper layer.

## Build & Run

```sh
./build.sh    # cc -o rfcomm_race_test rfcomm_race_test.c
./run.sh      # requires: kldload netbt (root)
```

## Expected output

On a guest WITHOUT Bluetooth hardware:
```
socket(RFCOMM) = 3 OK
bind OK
connect FAIL errno=65 (No route to host)
=> no HCI unit / BT adapter; L2CAP link cannot be established; RFCOMM session unreachable
done
```

The race path in `rfcomm_dlc_close` is **not reachable** on this guest because:
1. `netbt.ko` is not loaded by default (requires root `kldload`)
2. No Bluetooth adapter / HCI unit exists (QEMU guest has no BT hardware)
3. Without an HCI unit, `connect()` fails with `EHOSTUNREACH` — no L2CAP link, no RFCOMM session, no DLC timer armed
4. Even with BT hardware, triggering the race requires a remote BT peer sending DISC at the exact timer-expiry window (20s ± race window)

## Fix

See `fix.diff` — adds an `atomic_cmpset_short` CAS loop at the top of `rfcomm_dlc_close` to atomically claim the CLOSED transition, ensuring exactly one caller performs the teardown.
