# Malicious HID Device Specification

The browser-side bug is only reachable when the host's overlapped `WriteFile`
to the gamepad's HID output-report pipe **does not complete within 100 ms**.
A cooperating physical (or emulated) USB/Bluetooth HID device is required.

## Identity

Pick one of the IDs Chrome routes through `HidWriterWin`:

| Path                          | VID:PID         | Notes |
|-------------------------------|-----------------|-------|
| `Dualshock4Controller` (USB)  | `054c:05c4`     | DS4 v1 |
| `Dualshock4Controller` (USB)  | `054c:09cc`     | DS4 v2 |
| `HidHapticGamepad`            | `0b43:0005`     | XSkills GC adapter (4-byte report id 0x00) |

The device must expose HID **Usage Page 0x01 (Generic Desktop)**, **Usage 0x05
(Game Pad)** so `RawInputDataFetcher::EnumerateDevices` accepts it, and an
**Output report** matching what the chosen controller class emits (DS4 USB:
report id `0x05`, 32 bytes).

## Behaviour required to trigger the timeout

1. **Enumerate normally** so Windows binds `hidclass.sys` and Chrome's
   RawInput fetcher opens the device with `FILE_FLAG_OVERLAPPED`.
2. **Send at least one input report** (any button) so the page's
   `gamepadconnected` fires and `vibrationActuator` becomes non-null.
3. **NAK every interrupt-OUT transaction indefinitely.** This keeps the host
   IRP for `WriteFile` pending; after 100 ms `WaitForSingleObject` times out
   and Chrome enters the `CancelIo` branch.
4. **Early-return path (pick one):**
   - *Path A — `CancelIo` returns FALSE:* `CancelIo` fails if the calling
     thread has no IRPs queued on the handle (e.g. the IRP was queued under a
     different thread context after a PnP/surprise-removal race) or the handle
     is being torn down. Hard to force deterministically; path B is easier.
   - *Path B — file handle pre-signalled:* Before NAK'ing, **complete one
     prior output report normally**. Completion of any overlapped I/O on a
     file handle signals the *file handle object itself*. With another I/O now
     pending, the handle's signal state is undefined and is commonly observed
     still-signalled, so `WaitForMultipleObjects(handles, /*bWaitAll=*/FALSE,
     INFINITE)` returns immediately on `handles[0]` (the file handle) without
     waiting for `overlapped.hEvent`.
5. After Chrome's `WriteOutputReport` has returned (stack frame reused), have
   the device **ACK / complete** the stalled transfer (or simply let the
   cancel finish propagating). The Windows I/O manager then writes the
   `IO_STATUS_BLOCK` (16 bytes on x64: `Internal` = NTSTATUS,
   `InternalHigh` = bytes transferred) into the stale stack slot and signals
   the now-closed (possibly recycled) event handle.

## Reference implementation

Any USB-device-capable MCU works (RP2040 / Pico with TinyUSB, Teensy,
GreatFET/Facedancer, ATmega32U4). Minimal logic:

```
on SET_CONFIGURATION:
    start sending periodic input reports (button A pressed)

on first OUT report on the interrupt-OUT endpoint:
    ACK it normally               # primes file-handle signal state (path B)

on every subsequent OUT report:
    NAK forever                   # forces 100 ms timeout in WriteOutputReport
    after host issues CLEAR_FEATURE(ENDPOINT_HALT) or ~N ms elapse:
        ACK once                  # device-timed IRP completion -> stack write
```

For Bluetooth DS4 (`054c:09cc` over L2CAP HID), the equivalent is to drop /
delay the HID Output (0xA2) packets on the control/interrupt channel.
