fix(inject): heap the SwDeviceCreate callback context; stop latching a pad slot on a failed create
Two medium findings from the round-1 sweep, each applied to both siblings. - create_swdevice stack-allocated the SwCreateCtx that the async PnP completion callback writes through (result + up to 127 u16 of instance id) and then SetEvents. The wait is bounded at 10s, so on a wedged-PnP timeout the callback can still be PENDING: the frame is popped, the input thread reuses that stack, and a late callback corrupts it and SetEvents an already-closed (possibly recycled) handle. The context is now heap-allocated and reclaimed only where the callback provably ran; on the timeout path the box is deliberately leaked and the event left open, so a late write always targets live memory. Costs a one-off ~264 B + one HANDLE on that rare path. Applied to the DualSense path and its XUSB sibling in gamepad_windows.rs. - Ds4WinPad::open swallowed a create_swdevice failure into a WARN and returned Ok with no devnode. PadSlots::ensure then stored Some(pad) AND called gate.on_success(), so the slot short-circuited on is_some() forever and the capped-backoff retry that exists precisely to self-heal a transient PnP failure never ran — the game saw no controller for the rest of the session unless the client unplugged the pad. Now propagates, matching the XUSB sibling. Same fix applied to steam_deck_windows.rs. Windows .173: pf-inject 53/0. Linux .21: pf-inject 74/0 (8 ignored). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -82,12 +82,16 @@ fn create_swdevice(index: u8) -> Result<(HSWDEVICE, Option<String>)> {
|
||||
let event = unsafe { CreateEventW(None, true, false, PCWSTR::null())? };
|
||||
// `result` starts as E_FAIL, NOT S_OK: if the wait below times out, a zero-initialised HRESULT
|
||||
// would read as success and mask the failure (found by the 2026-07 driver-health audit).
|
||||
let mut ctx = SwCreateCtx {
|
||||
// HEAP-allocated for the same reason as the DualSense sibling: the callback writes through this
|
||||
// pointer and SetEvents, and the wait below is bounded — a stack context would be popped while a
|
||||
// late callback still holds it. On the timeout path the box is deliberately leaked and the event
|
||||
// left open so a late write/SetEvent always targets live memory/handle.
|
||||
let ctx = Box::into_raw(Box::new(SwCreateCtx {
|
||||
event,
|
||||
result: E_FAIL,
|
||||
instance_id: [0; 128],
|
||||
};
|
||||
// SAFETY: info + buffers + ctx outlive the call (we wait on the event before returning).
|
||||
}));
|
||||
// SAFETY: info + buffers outlive the call; `ctx` is a live heap allocation outliving every path.
|
||||
let hsw = match unsafe {
|
||||
SwDeviceCreate(
|
||||
w!("punktfunk"),
|
||||
@@ -95,13 +99,14 @@ fn create_swdevice(index: u8) -> Result<(HSWDEVICE, Option<String>)> {
|
||||
&info,
|
||||
None,
|
||||
Some(sw_create_cb),
|
||||
Some(&mut ctx as *mut SwCreateCtx as *const c_void),
|
||||
Some(ctx as *const c_void),
|
||||
)
|
||||
} {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
// SAFETY: event is valid.
|
||||
// SAFETY: the call failed, so no callback is pending and `ctx` is ours to reclaim.
|
||||
unsafe {
|
||||
drop(Box::from_raw(ctx));
|
||||
let _ = CloseHandle(event);
|
||||
}
|
||||
return Err(anyhow!("SwDeviceCreate(pf_xusb) failed: {e}"));
|
||||
@@ -109,17 +114,20 @@ fn create_swdevice(index: u8) -> Result<(HSWDEVICE, Option<String>)> {
|
||||
};
|
||||
// SAFETY: event valid; block until PnP finishes enumerating, then check the callback result.
|
||||
let wait = unsafe { WaitForSingleObject(event, 10_000) };
|
||||
// SAFETY: event is valid.
|
||||
unsafe {
|
||||
let _ = CloseHandle(event);
|
||||
}
|
||||
if wait != WAIT_OBJECT_0 {
|
||||
// Timed out — intentionally leak `ctx` and leave `event` open (see above).
|
||||
// SAFETY: hsw is the handle SwDeviceCreate returned.
|
||||
unsafe { SwDeviceClose(hsw) };
|
||||
return Err(anyhow!(
|
||||
"SwDeviceCreate(pf_xusb) enumeration callback never fired (10s) — PnP may be wedged"
|
||||
));
|
||||
}
|
||||
// The callback ran (it signalled the event), so nothing else will touch `ctx`/`event`.
|
||||
// SAFETY: `ctx` came from `Box::into_raw` and is reclaimed exactly once here.
|
||||
let ctx = unsafe {
|
||||
let _ = CloseHandle(event);
|
||||
Box::from_raw(ctx)
|
||||
};
|
||||
if ctx.result.is_err() {
|
||||
// SAFETY: hsw is the handle SwDeviceCreate returned.
|
||||
unsafe { SwDeviceClose(hsw) };
|
||||
|
||||
Reference in New Issue
Block a user