# Handoff — sealing the gamepad SHM channels

Status: **implemented (Option A), 2026-07-03 — Windows CI + on-glass validation pending.** The design
below was implemented as proposed; the "Implementation notes" section records what was actually built
and the deltas. Remaining: build + sign + redeploy both pad drivers, then the hardware validation plan
(§Validation) — it needs a physical controller on the box.

This closes the one open residual left by the IDD-push sealed-channel work
(`design/idd-push-security.md`): frames were sealed; the gamepad input/output channel was not.

## Unsafe hygiene (2026-07-03 follow-up — the drivers' `unsafe` was confined)

After the seal landed, the pad drivers' `unsafe` footprint (raw `OpenFileMapping`/`MapViewOfFile`,
`read_unaligned`, the whole bootstrap state machine as bare-pointer arithmetic) was pulled into a new
audited crate **`pf-umdf-util`** (`packaging/windows/drivers/pf-umdf-util/`), so the drivers benefit
from Rust instead of being C-in-Rust:

- `section::MappedView` — a mapped section wrapped as bounds- + alignment-checked accessors
  (`load_u32`/`store_u32`/`read_bytes`/…). Callers never see the base pointer; an out-of-range offset
  asserts instead of corrupting. `ViewCell` holds the adopted view as a leaked `&'static` (the
  re-delivery-must-not-unmap rule, now type-enforced).
- `channel::ChannelClient` — the ENTIRE sealed-channel driver side (publish pid → adopt handle →
  validate magic+`pad_index`), as a **`#![forbid(unsafe_code)]`** module over `MappedView`. One
  implementation both pad drivers share (was hand-duplicated).
- `wdf::{Request, query_location_index, retrieve_next_request}` — the WDF request/memory/property FFI
  behind safe methods; a callback turns its raw `WDFREQUEST` into a `Request` token once (the only
  `unsafe` at the driver boundary), and completion consumes the token.

Result: `pf-xusb`/`pf-dualsense` business logic is **100 % safe Rust**; the only remaining `unsafe` in
them is the unavoidable WDF *setup* FFI in `DriverEntry`/`EvtDeviceAdd`/the timer, each with a
`// SAFETY:` proof. The display driver `pf-vdisplay` is inherently FFI-bound (D3D11 / IddCx DDIs /
cross-process textures) so it can't be unsafe-*free*, but it's now unsafe-*audited*: every `unsafe {}`
carries a proof. Both invariants are lint-gated across the whole drivers workspace
(`#![deny(unsafe_op_in_unsafe_fn)]` + `#![deny(clippy::undocumented_unsafe_blocks)]`) and enforced by
a new `cargo clippy -D warnings` step in `windows-drivers.yml`. Verified on the RTX box (.173): the
whole workspace builds + clippies + fmt-checks clean; both gamepad DLLs still produce.

## Implementation notes (what was built, 2026-07-03)

- **Contract** (`pf_driver_proto::gamepad`, `GAMEPAD_PROTO_VERSION = 2`): `PadBootstrap` (32 B —
  `magic "PFBT"`, `host_proto`, `driver_pid`, `driver_proto`, `data_handle: u64`, `handle_pid`,
  `handle_seq`) with `Pod` + `offset_of!` asserts; `xusb_boot_name`/`pad_boot_name`
  (`Global\pf…-boot-<index>`) REPLACE the old `*_shm_name` fns (the DATA-section name is gone);
  `XusbShm`/`PadShm` gained `pad_index` (carved from reserved space) so the DRIVER validates a
  delivery resolves to *its own* pad — the authentic-side answer to the "redirect the dup into a
  different pad's WUDFHost" hardening note (the section content is host-written and unreachable by a
  sibling LS, so the check can't be spoofed). Both pad drivers now path-dep `pf-driver-proto` (as
  pf-vdisplay does) instead of hand-synced literals.
- **Host** (`inject/windows/gamepad_raii.rs`): `Shm::create_unnamed` (DATA, `D:P(A;;GA;;;SY)`) +
  `Shm::create_named` (mailbox, SY+LS, **squat-checked** — `ERROR_ALREADY_EXISTS` on create is
  close+retry×5 then a hard error, so the handshake never runs through a pre-created object; this also
  turns the previously-silent two-hosts-same-index cross-wire into a loud failure). `PadChannel` owns
  both + the delivery state machine: poll `driver_pid` → `OpenProcess` →
  `verify_is_wudfhost` (now shared with the frame broker in `capture/windows/idd_push.rs`) →
  `DuplicateHandle` → publish `data_handle`/`handle_pid`, bump `handle_seq` last (Release). Pumped
  from each backend's existing service tick (≤4 ms) + a bounded **eager delivery** (1.5 s) at pad-open
  so the DS4's `device_type` is readable before hidclass asks for descriptors. Delivery attempts are
  **capped at 16 per pad** so a tampered flapping mailbox can't mint unbounded remote handles. Same
  pid never retried (failed verify can't be spun into a hot loop).
- **Drivers** (`pf-xusb`, `pf-dualsense`): per-tick `pump_bootstrap()` (the DS timer / every XUSB
  IOCTL + a bounded EvtDeviceAdd worker thread for XUSB's no-game-running case) opens the mailbox *by
  name each time* — the name existing doubles as host-liveness, replacing the old per-access section
  open; mailbox gone → detach (DS additionally resets the pended-read report to neutral instead of
  the old frozen-last-state behavior). The driver writes `driver_proto` always but publishes its pid
  **only when `host_proto` matches** (fail closed both ways: v1 host never creates a mailbox a v2
  driver polls; a v1 driver opens a name that no longer exists). A delivery is adopted once
  (CAS on `handle_seq`, reset when the mailbox disappears so a new host session's counter can't
  collide), mapped, and validated: `magic` AND `pad_index == SHM_INDEX` — else unmapped + ignored
  (the handle is deliberately NOT closed on validation failure: a tampered value could name an
  unrelated handle in the driver's own table). The adopted view is cached and never unmapped
  (re-delivery swaps + leaks the old 64/256 B mapping on purpose — a concurrent reader may hold it).
  Driver log line for validation step 3: `sealed pad channel mapped (index …)`.
- **Not built:** Option B (devnode custom properties). The residual named mailbox is documented and
  DoS-bounded; migrate later if it's ever deemed worth removing.

## The problem (why this exists)

Each virtual pad's host↔driver channel is a **named** shared-memory section:

- `Global\pfxusb-shm-<index>` (64 B, [`pf_driver_proto::gamepad::XusbShm`]) — virtual Xbox 360 / XInput.
- `Global\pfds-shm-<index>` (256 B, [`pf_driver_proto::gamepad::PadShm`]) — virtual DualSense / DualShock 4.

Both are created by the SYSTEM host with DACL `D:(A;;GA;;;SY)(A;;GA;;;LS)` (`inject/windows/gamepad_raii.rs`
`Shm::create`) so the driver's WUDFHost (LocalService) can open them by name. That means **a sibling
LocalService process can `OpenFileMapping` the section by name** and:

- **read** the victim's live controller input (buttons/sticks/gyro/touchpad — host→driver `input` region), and
- **inject/forge** gamepad input or rumble (write the `input` region → the driver feeds it to whatever game
  has focus; write the `output` region + bump `out_seq` → forge rumble/LED back to the client).

This is the *same* name-open vector we closed for frames, one module over. Severity is lower than desktop
capture (it's game-controller I/O, scoped to the focused app, and requires the attacker to already have
LocalService code execution), but it is real and it is inconsistent to leave named next to a sealed frame ring.

**Not a stopgap:** randomizing the section name is inadequate — the object namespace is enumerable with
`NtQueryDirectoryObject`, so a random name is discoverable. (Same reason it was rejected for frames.) The fix
is to remove the name.

## Why it isn't already sealed the frame way

The frame channel seals cleanly because pf-vdisplay has a **control device** (the IddCx device interface):
the host duplicates the unnamed handles into the driver's WUDFHost and delivers the values over
`IOCTL_SET_FRAME_CHANNEL`, and the driver reports its own pid in the `IOCTL_ADD` reply.

The pad drivers (`pf-dualsense`, `pf-xusb`) are **UMDF HID minidrivers with no control device** — hidclass
owns the device stack and blocks a freely-openable control interface. That is *why* they use a named section
in the first place. So there is no IOCTL to (a) hand the driver a duplicated handle or (b) learn the driver's
WUDFHost pid. Compounding it: `pszDeviceLocation` (the existing host→driver property) is fixed at
`SwDeviceCreate` time — **before** the WUDFHost process exists — so the host can't duplicate a handle into a
not-yet-created process and stamp its value there. A bidirectional, late-bound handshake is required.

## Current architecture (what to modify)

Host (`crates/punktfunk-host/src/inject/windows/`):
- `gamepad_raii.rs` — `Shm::create(name, size)` creates the **named** section (SY+LS SDDL) + maps it;
  `SwDevice` wraps the `SwDeviceCreate` devnode.
- `gamepad_windows.rs` (XUSB), `dualsense_windows.rs` (DualSense/DS4), `dualshock4_windows.rs` — each creates
  its `Shm`, then `create_swdevice(index)` / `create_swdevice(profile)` which stamps the pad **index** into
  `info.pszDeviceLocation` (a UTF-16 decimal string) and creates `pf_xusb_<index>` / `pf_pad_<index>`.

Driver (`packaging/windows/drivers/pf-{xusb,dualsense}/src/lib.rs`):
- `query_shm_index(device)` — `WdfDeviceAllocAndQueryProperty(DevicePropertyLocationInformation)` → parses the
  decimal → `SHM_INDEX` static.
- On first control activity it builds `format!("Global\\pf…-shm-{}", SHM_INDEX)`, `OpenFileMappingW` +
  `MapViewOfFile`. The dualsense driver also runs a ~125 Hz timer (writes `driver_heartbeat`) — an existing
  poll loop to piggyback a bootstrap-wait on.

Contract (`crates/pf-driver-proto/src/lib.rs` `mod gamepad`): owns `XusbShm`/`PadShm` layouts, the magics,
`xusb_shm_name`/`pad_shm_name`, `device_type`, `GAMEPAD_PROTO_VERSION`, and the driver_proto/heartbeat fields.

## Proposed design — a late-bound bootstrap handshake

Split each pad's channel into **(1) an unnamed DATA section** (the real `XusbShm`/`PadShm`, host↔driver) and
**(2) a tiny bootstrap mailbox** that carries only a magic + the driver's pid + a handle value. The handshake:

1. **Host**, per pad: create the DATA section **unnamed** (`CreateFileMappingW` with `PCWSTR::null()`, DACL
   `D:P(A;;GA;;;SY)` — SYSTEM-only, exactly as the sealed frame ring now uses; the driver reaches it by
   duplicated handle, which carries access, so no LS ACE is needed). Then create the devnode via
   `SwDeviceCreate`, stamping the pad index into `pszDeviceLocation` **as today** (the index still identifies
   *which* pad's bootstrap the driver should use).
2. **Driver** `EvtDeviceAdd`: read the index (unchanged `query_shm_index`). Write `std::process::id()` where
   the host can read it, then **poll** (piggyback the existing timer) for a delivered handle value; map the
   DATA section from it once non-zero.
3. **Host**: learn the driver's pid, `OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_LIMITED_INFORMATION)`,
   **verify it is the WUDFHost servicing this pad's devnode** (see hardening note), `DuplicateHandle` the
   DATA section into the WUDFHost, and deliver the resulting handle value back to the driver.

Two viable transports for steps 2–3's pid-out / handle-in (pick one):

- **Option A — named bootstrap mailbox** (`Global\pf…-boot-<index>`, ~32 B, SY+LS): host creates it; driver
  opens it by name (index from location), writes `driver_pid`, spins on `data_handle` != 0; host polls
  `driver_pid`, dups the DATA section in, writes `data_handle` + a ready seq. **Safe to leave named + SY+LS**
  because it carries *only* a pid (not sensitive) and a handle value (meaningless outside the target WUDFHost)
  — identical to the frame channel's "the bootstrap ACL is not load-bearing" argument. A sibling LS that reads
  it learns nothing exploitable; one that tampers it can at worst feed a bogus pid/handle → the driver maps a
  value that doesn't resolve in its own table → **DoS, not a breach** (the attacker cannot place a valid
  section handle in the WUDFHost, so it cannot make the driver map an attacker-controlled section). *Fastest to
  build — reuses the existing named-section + poll machinery.*
- **Option B — devnode custom properties** (no `Global\` object at all): driver writes its pid via
  `WdfDeviceAssignProperty(DEVPROPKEY_pf_pad_pid)`; host reads it via `CM_Get_DevNode_PropertyW` /
  `SetupDiGetDevicePropertyW`, dups in, writes a `DEVPROPKEY_pf_pad_handle` property; driver re-queries it in
  its timer. Tighter (property store isn't world-readable like the Global namespace) but more moving parts and
  UMDF-property-write ergonomics to prove out. *Cleaner end-state.*

Recommendation: **build Option A first** (small, mirrors the frame channel, gets the DATA section unnamed —
which is the actual isolation win, proven by #3 below), then optionally migrate the bootstrap to Option B if
the residual named mailbox is deemed worth removing.

## Reuse the frame-channel precedent

- **Ownership/adopt-on-success** discipline from `capture/windows/idd_push.rs` `ChannelBroker` — exactly one
  side ever closes a duplicated handle value; reap remote duplicates (`DUPLICATE_CLOSE_SOURCE`) on any failure.
- **`verify_is_wudfhost`** (`idd_push.rs`) — before duplicating into the driver-reported pid, confirm it's
  `%SystemRoot%\System32\WUDFHost.exe`. **Strengthen it here**: also confirm the pid is the host *servicing
  this pad's devnode* (walk devnode → process, e.g. via the driver writing a per-pad nonce it echoes, or a
  devnode/PID association) so a tampered bootstrap can't redirect the dup into a *different* pad's WUDFHost.
- **Contract in `pf_driver_proto::gamepad`** — add the bootstrap layout (`PadBootstrap { magic, driver_pid,
  data_handle: u64, seq }`) with `Pod` + `offset_of!` asserts, bump `GAMEPAD_PROTO_VERSION`, and (Option A)
  keep `pad_shm_name`/`xusb_shm_name` only for the bootstrap mailbox, dropping the data-section name.
- **SDDL** on the DATA section: `D:P(A;;GA;;;SY)` (SYSTEM-only) — validated safe for a duplicated-handle
  consumer on the frame ring (the driver's `OpenSharedResource`/`MapViewOfFile` on a handle does not re-check
  the object DACL).

## Security properties after the change

- The **DATA section is unnamed** and only ever handle-duplicated into the pad WUDFHost. Empirically
  (`design/idd-push-security.md`, RTX box 2026-07-03) a **LocalService token is DACL-denied `OpenProcess` on a
  UMDF WUDFHost for every access right incl. `QUERY_LIMITED`** — so a sibling LS cannot dup the handle out or
  read the WUDFHost's memory. Unnamed + unopenable-host ⇒ no sibling-LS path to the input/output data. This is
  the same guarantee the frame channel now has, and it rests on the same verified property.
- **Residual (Option A):** the bootstrap mailbox stays named + SY+LS, but carries only a pid + handle value →
  worst case a sibling LS causes a **gamepad DoS**, never a read or injection. Option B removes even that.
- **Unchanged inherent limits:** admin/SYSTEM = total; the game reading the pad sees the input by design.

## Validation plan (needs hardware)

The blocker for calling this done is that it **requires a physical controller on the box** — the memory notes
repeatedly flag the gamepad path as "needs a physical pad to live-verify," and neither the probe nor a
synthetic client exercises a real game reading the virtual pad.

1. Build + sign + redeploy `pf-dualsense` and `pf-xusb` (same loop as pf-vdisplay:
   `packaging/windows/drivers/deploy-dev.ps1` per driver, or `redeploy-*`; DriverVer must strictly increase).
   Bump `GAMEPAD_PROTO_VERSION` — a v_new host against a v_old pad driver (or vice-versa) must fail closed, so
   deploy host + both pad drivers together.
2. Connect a real client with a physical controller; confirm in a game that input works and rumble/LED return.
3. Driver log (`C:\Users\Public\pfds-driver.log` / `pfxusb-driver.log` in debug builds): confirm the driver
   reports its pid, receives a handle, and maps the DATA section (add a `dbglog!` "sealed pad channel mapped").
4. Re-run the **sibling-LS `OpenFileMapping` test**: from a LocalService scheduled task, attempt to open the
   old `Global\pf…-shm-<index>` name — it must now **fail (name gone)**, and attempting to open the bootstrap
   (Option A) must yield only pid+handle bytes. (Reuse the scheduled-task P/Invoke harness from the #3 frame
   test — see the session that produced `design/idd-push-security.md`.)
5. Multi-pad: two controllers → two devnodes, two unnamed DATA sections, two bootstraps by index; confirm no
   cross-talk and clean teardown (`SwDeviceClose` + host handle close; the WUDFHost dies with its devnode).

## Risks / gotchas

- **Regression risk to a working feature.** Gamepad input currently works on glass; this reroutes its
  bootstrap. Keep the change behind the `GAMEPAD_PROTO_VERSION` bump and be ready to revert both drivers.
- **Chicken-and-egg timing.** The driver loads and wants the handle before the host has dup'd it — the poll
  loop must tolerate a bounded wait (mirror the frame path's `wait_for_attach`, ~4 s) and the driver must not
  block `EvtDeviceAdd` on it (spin in the timer, not the add callback).
- **Handle value in shared memory is a `u64`.** A WUDFHost handle value is process-local; writing it to the
  bootstrap is safe (meaningless elsewhere), but the driver must treat it as untrusted (validate the mapped
  DATA section's magic before use — the existing `XusbShm`/`PadShm` magic already gives this).
- **Two drivers, one contract.** DualSense and DualShock 4 share `pf-dualsense`/`PadShm`; XUSB is separate.
  Factor the bootstrap into `pf_driver_proto::gamepad` so both drivers + the host use one definition (as the
  frame channel does).

## Effort

Medium — comparable to the frame sealed-channel change but across **two** drivers plus the host inject code,
and gated on **physical-controller validation** that can't be driven over SSH. Files: `pf_driver_proto`
(gamepad module), `inject/windows/{gamepad_raii,gamepad_windows,dualsense_windows,dualshock4_windows}.rs`,
`packaging/windows/drivers/pf-{xusb,dualsense}/src/lib.rs`. Reference implementation: the frame sealed channel
(`capture/windows/idd_push.rs` + `packaging/windows/drivers/pf-vdisplay/src/{control,monitor,frame_transport}.rs`
+ `pf_driver_proto` `control`/`frame`).
