fix(windows-host): IDD-push resilience — driver-death recovery, reopenable control device, full interface discovery

Batch A of the audit's medium tier (M1+M2+M3):

- M1 driver-death detection: a dead WUDFHost stops publishing, which at the
  ring is indistinguishable from an idle desktop — SDR sessions streamed a
  frozen frame forever (next_frame's 20 s bail is unreachable once anything
  presented). The ChannelBroker's process handle now doubles as a liveness
  probe (SYNCHRONIZE at OpenProcess); while no fresh frame arrives,
  try_consume polls it (rate-limited) and fails the capturer, landing in the
  session's bounded in-place rebuild.
- M2 reopenable control device: the manager's OnceLock-cached handle is now
  a retire/reopen DeviceSlot — a gone-classified IOCTL failure (driver
  upgrade / WUDFHost restart; pinger, create, or REMOVE) retires the handle
  and the next use reopens + re-handshakes. Retired handles are deliberately
  kept alive forever: bare-HANDLE holders (pinger, ChannelBroker) rely on
  never-closed, and a retired handle only fails IOCTLs. CLEAR_ALL runs on
  the FIRST open only (a reopen races live-ish sessions); acquire retries
  the monitor create once after a reopen. The JOIN path now probes the
  active monitor's WUDFHost pid and preempts a DEAD monitor instead of
  handing the rebuilding session its stale target — without this the whole
  recovery chain starved to the rebuild budget.
- M3 interface discovery: enumerate ALL interface instances with an
  SPINT_ACTIVE filter (a Code-10 devnode at index 0 no longer shadows the
  live interface), HDEVINFO behind RAII (error paths leaked one per probe),
  the raw device handle wrapped before GET_INFO (leaked on handshake
  failure), and the detail-sizing result guarded before the cbSize write.
- pf-driver-proto: SetFrameChannelRequest doc now states the real
  adopt-on-success contract (the old wording invited a driver-side
  close-on-error — a cross-process double-close against the host's reap).
- install: pf_vdisplay_present() passes /connected so a phantom devnode
  can't suppress creating a live ROOT node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 17:04:19 +00:00
parent 0da9d8ec10
commit 89455032a0
5 changed files with 370 additions and 113 deletions
@@ -23,7 +23,8 @@ use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use anyhow::Result;
use windows::Win32::Foundation::{HANDLE, LUID};
use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID, WAIT_OBJECT_0};
use windows::Win32::System::Threading::{OpenProcess, WaitForSingleObject, PROCESS_SYNCHRONIZE};
use super::{Mode, VirtualOutput};
use crate::win_display::{
@@ -54,13 +55,15 @@ pub(crate) struct AddedMonitor {
/// `&'static` singleton reached from the pinger + linger threads.
pub(crate) trait VdisplayDriver: Send + Sync {
fn name(&self) -> &'static str;
/// Find + open the control device, validate it (version handshake), read the watchdog timeout, and
/// reap monitors orphaned by a crashed previous host (`CLEAR_ALL`). Returns the owned handle +
/// watchdog seconds.
/// Find + open the control device, validate it (version handshake), and read the watchdog
/// timeout. `reap_orphans` (the FIRST open of the process only) additionally `CLEAR_ALL`s
/// monitors orphaned by a crashed previous host — a REOPEN (after a dead handle was retired)
/// must NOT, since sessions this process still considers live may be racing it. Returns the
/// owned handle + watchdog seconds.
///
/// # Safety
/// Issues setup-API + `DeviceIoControl` calls; runs in the caller's apartment.
unsafe fn open(&self) -> Result<(OwnedHandle, u32)>;
unsafe fn open(&self, reap_orphans: bool) -> Result<(OwnedHandle, u32)>;
/// ADD a virtual monitor at `mode`, pinning the IDD render GPU to `render_luid` first if `Some`, and
/// requesting `preferred_monitor_id` (the host's per-client stable id; `0` = auto). Returns the REMOVE
/// key + target id + the adapter LUID the driver actually used.
@@ -125,12 +128,31 @@ enum MgrState {
Lingering { mon: Monitor, until: Instant },
}
/// The manager's control-device cache. Reopenable: a driver upgrade / WUDFHost restart kills the
/// cached handle (every IOCTL fails with a gone-class code forever), so such a failure RETIRES it and
/// the next [`VirtualDisplayManager::ensure_device`] reopens the (new) device interface, re-running
/// the version handshake. Retired handles are deliberately kept alive — never closed — for the
/// process lifetime: the pinger/linger threads and every capturer's `ChannelBroker` hold BARE
/// `HANDLE` copies whose soundness contract is "never closed"; a retired handle only ever FAILS
/// IOCTLs, which every holder already tolerates. Reopens are rare (a driver restart), so the retained
/// list is bounded in practice.
#[derive(Default)]
struct DeviceSlot {
current: Option<Arc<OwnedHandle>>,
/// Never dropped — see the type doc (bare-`HANDLE` holders rely on no-close).
retired: Vec<Arc<OwnedHandle>>,
/// `CLEAR_ALL` (crashed-host orphan reap) runs only on the FIRST open of the process; a reopen
/// races sessions this process still considers live and must not raze them.
opened_once: bool,
}
/// The host-lifetime virtual-display manager: the single owner of the monitor lifecycle.
pub(crate) struct VirtualDisplayManager {
driver: Box<dyn VdisplayDriver>,
/// Control device, opened once on first acquire. Typed + `Send+Sync`, so the pinger/linger threads
/// share it via the `&'static` singleton with no raw-handle smuggling.
device: OnceLock<Arc<OwnedHandle>>,
/// Control device, opened on first acquire and REOPENED after a gone-classified failure retired
/// it (see [`DeviceSlot`]). Typed + `Send+Sync`, so the pinger/linger threads share it via the
/// `&'static` singleton with no raw-handle smuggling.
device: Mutex<DeviceSlot>,
watchdog_s: AtomicU32,
/// Monotonic lease-generation counter (was the `MON_GEN` global).
gen: AtomicU64,
@@ -155,7 +177,7 @@ static VDM: OnceLock<VirtualDisplayManager> = OnceLock::new();
pub(crate) fn init(driver: Box<dyn VdisplayDriver>) -> &'static VirtualDisplayManager {
VDM.get_or_init(|| VirtualDisplayManager {
driver,
device: OnceLock::new(),
device: Mutex::new(DeviceSlot::default()),
watchdog_s: AtomicU32::new(3),
gen: AtomicU64::new(1),
state: Mutex::new(MgrState::Idle),
@@ -173,39 +195,109 @@ pub(crate) fn vdm() -> &'static VirtualDisplayManager {
}
/// The live pf-vdisplay control-device handle, for the IDD-push capturer's sealed-channel delivery
/// (`IOCTL_SET_FRAME_CHANNEL`). Safe to hand out as a bare `HANDLE`: the device lives in a `OnceLock`
/// that is never cleared or closed for the process lifetime. `None` before the first backend open —
/// impossible for a capturer, which only exists on a monitor the manager created.
/// (`IOCTL_SET_FRAME_CHANNEL`). Safe to hand out as a bare `HANDLE`: cached handles are never closed
/// for the process lifetime — a dead one is RETIRED (kept alive, see [`DeviceSlot`]), so a stale copy
/// can only fail IOCTLs, never dangle. `None` before the first backend open — impossible for a
/// capturer, which only exists on a monitor the manager created.
pub(crate) fn control_device_handle() -> Option<HANDLE> {
VDM.get().and_then(VirtualDisplayManager::device_handle)
}
/// True when an IOCTL failure means the CONTROL DEVICE itself is gone (driver upgrade, WUDFHost
/// restart, device disable) — the cached handle can only keep failing and must be retired so the
/// next use reopens. The root `windows` error survives anyhow `.context` chains via `downcast_ref`.
/// NOTE: 0x80070490 (ERROR_NOT_FOUND, the ADD slot-exhaustion wedge) is deliberately NOT here — it
/// has its own reap-and-retry handling and the device is alive when it fires.
/// Best-effort "is this WUDFHost pid still alive?" — the monitor-liveness probe for the JOIN path.
/// `OpenProcess` failing (pid reaped) or the process being signaled ⇒ dead. Pid reuse could
/// theoretically alias a fresh process and read "alive"; the joining session then just retries into
/// its rebuild budget — acceptable for a sub-second reuse window that realistically never hits.
fn wudf_alive(pid: u32) -> bool {
if pid == 0 {
return true; // pre-v2 driver reports no pid — never preempt on the probe's account
}
// SAFETY: plain FFI probe; the opened handle (checked) is closed exactly once below, and the
// 0 ms wait only reads its signaled state.
unsafe {
let Ok(h) = OpenProcess(PROCESS_SYNCHRONIZE, false, pid) else {
return false;
};
let alive = WaitForSingleObject(h, 0) != WAIT_OBJECT_0;
let _ = CloseHandle(h);
alive
}
}
fn is_device_gone(e: &anyhow::Error) -> bool {
let Some(w) = e.downcast_ref::<windows::core::Error>() else {
return false;
};
// Win32 codes as HRESULTs: FILE_NOT_FOUND(2), INVALID_HANDLE(6), BAD_COMMAND(22),
// GEN_FAILURE(31), DEV_NOT_EXIST(55), OPERATION_ABORTED(995), DEVICE_NOT_CONNECTED(1167 =
// 0x48F — one below the 0x490 wedge), DEVICE_REMOVED(1617).
const GONE: [i32; 8] = [
0x8007_0002u32 as i32,
0x8007_0006u32 as i32,
0x8007_0016u32 as i32,
0x8007_001Fu32 as i32,
0x8007_0037u32 as i32,
0x8007_03E3u32 as i32,
0x8007_048Fu32 as i32,
0x8007_0651u32 as i32,
];
GONE.contains(&w.code().0)
}
impl VirtualDisplayManager {
pub(crate) fn backend_name(&self) -> &'static str {
self.driver.name()
}
/// Open + cache the control device (once). Called under the `state` lock so two racing acquires can't
/// double-open.
/// Open + cache the control device; REOPEN when a gone-classified failure retired the cached one
/// (driver upgrade / WUDFHost restart). The `device` mutex serializes racing opens.
fn ensure_device(&self) -> Result<HANDLE> {
if let Some(d) = self.device.get() {
let mut slot = self.device.lock().unwrap();
if let Some(d) = &slot.current {
return Ok(HANDLE(d.as_raw_handle()));
}
let reap = !slot.opened_once;
// SAFETY: `VdisplayDriver::open` is `unsafe` only because it issues SetupAPI + `DeviceIoControl`
// FFI in the caller's apartment; `ensure_device` runs that on the acquiring thread under the
// `state` lock (callers hold it), so there is no concurrent open. `open` has no handle
// precondition to uphold, and the `OwnedHandle` it returns is the sole owner of the device.
let (handle, watchdog_s) = unsafe { self.driver.open()? };
// FFI in the caller's apartment; the `device` mutex (held here) serializes it, so there is no
// concurrent open. `open` has no handle precondition to uphold, and the `OwnedHandle` it
// returns is the sole owner of the device.
let (handle, watchdog_s) = unsafe { self.driver.open(reap)? };
slot.opened_once = true;
self.watchdog_s.store(watchdog_s, Ordering::Relaxed);
let raw = HANDLE(handle.as_raw_handle());
let _ = self.device.set(Arc::new(handle));
slot.current = Some(Arc::new(handle));
if !reap {
tracing::info!("virtual-display control device reopened (retired handle replaced)");
}
Ok(raw)
}
/// The live control handle for the pinger/linger threads (lock-free: the device never changes once
/// opened). `None` only before the first acquire opened it.
/// The live control handle for the pinger/linger threads. `None` before the first acquire opened
/// it, or between a retire and the next reopen.
fn device_handle(&self) -> Option<HANDLE> {
self.device.get().map(|d| HANDLE(d.as_raw_handle()))
self.device
.lock()
.unwrap()
.current
.as_ref()
.map(|d| HANDLE(d.as_raw_handle()))
}
/// Retire the cached control handle after a gone-classified IOCTL failure. The handle is retained
/// un-closed (see [`DeviceSlot`]); the next [`ensure_device`](Self::ensure_device) reopens the
/// (new) device interface and re-runs the version handshake.
fn invalidate_device(&self, why: &anyhow::Error) {
let mut slot = self.device.lock().unwrap();
if let Some(cur) = slot.current.take() {
tracing::warn!(
"virtual-display control device retired — reopening on next use (cause: {why:#})"
);
slot.retired.push(cur);
}
}
/// Open + initialise the backend (validates the driver is present). Mirrors the old
@@ -247,9 +339,9 @@ impl VirtualDisplayManager {
old_target = mon.target_id,
"IDD-push reconnect — preempting the lingering monitor, recreating a fresh one"
);
// SAFETY: `teardown` requires `dev` to be the live control handle; `dev` is the value
// `ensure_device()` returned above (the device is cached in the `OnceLock` and never
// closed for the manager's lifetime). `mon` was moved out of the prior `Lingering`
// SAFETY: `teardown` requires `dev` to be a valid control handle; `dev` is the value
// `ensure_device()` returned above (cached handles are never closed — a dead one is
// retired, kept alive; see `DeviceSlot`). `mon` was moved out of the prior `Lingering`
// state by `mem::replace`, so it is exclusively owned here — no aliasing.
unsafe { self.teardown(dev, mon) };
// Let the OS finish the ASYNC monitor departure before the next ADD; a back-to-back
@@ -258,6 +350,30 @@ impl VirtualDisplayManager {
}
}
// An ACTIVE monitor whose WUDFHost has EXITED is dead driver-side (driver crash / upgrade):
// the capturer's driver-death watch failed its session, and that session's in-place rebuild
// re-acquires here while its old lease is STILL held — so the state is Active. Joining would
// hand the rebuild the dead monitor's target (stale wudf_pid) and starve it to the rebuild
// budget. Preempt instead: best-effort teardown (REMOVE fails harmlessly on a dead/retired
// device) and fall through to a fresh create on the auto-restarted device. Held leases are
// gen-stamped, so their eventual release is a no-op.
if matches!(&*state, MgrState::Active { mon, .. } if !wudf_alive(mon.wudf_pid)) {
if let MgrState::Active { mon, .. } = std::mem::replace(&mut *state, MgrState::Idle) {
tracing::warn!(
old_target = mon.target_id,
wudf_pid = mon.wudf_pid,
"virtual monitor's WUDFHost is gone — preempting the dead monitor, recreating"
);
// SAFETY: `teardown` requires a valid control handle; `dev` is the value
// `ensure_device()` returned above (cached handles are never closed — a dead one is
// retired, kept alive; see `DeviceSlot`). `mon` was moved out of the replaced state,
// so it is exclusively owned here — no aliasing.
unsafe { self.teardown(dev, mon) };
// Same async-departure settle as the reconnect preempt above.
thread::sleep(Duration::from_millis(400));
}
}
// A live monitor already exists — join it (refcount++). Covers concurrent sessions AND the
// build-then-drop overlap of a mid-stream Reconfigure (the new lease is taken while the old is
// still held). Reconfigure the shared monitor if the requested mode differs.
@@ -292,10 +408,26 @@ impl VirtualDisplayManager {
}
mon
}
// SAFETY: `create_monitor` requires `dev` to be the live control handle; `dev` is the
// handle `ensure_device()` returned above (cached in the `OnceLock`, never closed for the
// manager's lifetime), and we hold the `state` lock.
MgrState::Idle => unsafe { self.create_monitor(dev, mode, client_fp)? },
// SAFETY: `create_monitor` requires `dev` to be a valid control handle; `dev` is the
// handle `ensure_device()` returned above (cached handles are never closed — a dead one
// is retired, kept alive; see `DeviceSlot`), and we hold the `state` lock.
MgrState::Idle => match unsafe { self.create_monitor(dev, mode, client_fp) } {
// The cached device died under us (driver upgrade / WUDFHost restart, detected only
// now — e.g. the host sat idle past the pinger-less window). Retire it, reopen, and
// retry ONCE so the reconnect-after-driver-restart succeeds first try instead of
// burning one failed session per restart.
Err(e) if is_device_gone(&e) => {
self.invalidate_device(&e);
let dev = self.ensure_device()?;
tracing::info!(
"virtual-display control device reopened — retrying the monitor create"
);
// SAFETY: as above — `dev` is the handle the reopening `ensure_device` just
// returned, and the `state` lock is still held.
unsafe { self.create_monitor(dev, mode, client_fp)? }
}
r => r?,
},
MgrState::Active { .. } => unreachable!("handled above"),
};
let out = self.output_for(&mon);
@@ -353,13 +485,20 @@ impl VirtualDisplayManager {
let mut warned = false;
while !stop_t.load(Ordering::Relaxed) {
if let Some(h) = vdm().device_handle() {
// SAFETY: `ping` requires `dev` to be the live control handle. `h` is from
// `device_handle()` (the `Some` branch) — the `OnceLock<Arc<OwnedHandle>>` that,
// once set, is never cleared or closed for the process lifetime, so the handle is
// live for this call. The pinger thread only spins while the `&'static` manager
// singleton (and thus the device) lives.
// SAFETY: `ping` requires `dev` to be a valid control handle. `h` is from
// `device_handle()` (the `Some` branch) — cached handles are NEVER closed for the
// process lifetime (a dead one is retired, kept alive; see `DeviceSlot`), so the
// handle stays valid for this call even if it was retired concurrently — at worst
// the IOCTL fails. The pinger thread only spins while the `&'static` manager
// singleton lives.
match unsafe { vdm().driver.ping(h) } {
Ok(()) => warned = false,
Err(e) if is_device_gone(&e) => {
// The device itself is gone (driver upgrade / WUDFHost restart) — pings
// can only keep failing on this handle. Retire it so the next session's
// `ensure_device` reopens; this monitor is already dead driver-side.
vdm().invalidate_device(&e);
}
Err(e) => {
if !warned {
tracing::warn!("virtual-display keepalive PING failed (control handle lost?): {e:#}");
@@ -501,6 +640,11 @@ impl VirtualDisplayManager {
// `remove_monitor` requires exactly that. `&mon.key` borrows the `MonitorKey` inside the
// still-owned `mon`, alive for this synchronous IOCTL, so the pointer the driver reads stays valid.
if let Err(e) = unsafe { self.driver.remove_monitor(dev, &mon.key) } {
// A gone-classified failure means the device died under this monitor (driver upgrade /
// WUDFHost restart) — retire the handle so the NEXT session reopens instead of failing.
if is_device_gone(&e) {
self.invalidate_device(&e);
}
tracing::warn!("virtual-display REMOVE failed: {e:#}");
} else {
tracing::info!(
@@ -19,14 +19,14 @@
use std::ffi::c_void;
use std::mem::size_of;
use std::os::windows::io::{FromRawHandle, OwnedHandle};
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::sync::atomic::{AtomicU64, Ordering};
use anyhow::{Context, Result};
use windows::core::{GUID, PCWSTR};
use windows::Win32::Devices::DeviceAndDriverInstallation::{
SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInterfaces, SetupDiGetClassDevsW,
SetupDiGetDeviceInterfaceDetailW, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT,
SetupDiGetDeviceInterfaceDetailW, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, HDEVINFO, SPINT_ACTIVE,
SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_W,
};
use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID};
@@ -137,11 +137,9 @@ fn is_slot_exhaustion_wedge(e: &anyhow::Error) -> bool {
/// Pin the pf-vdisplay IddCx's RENDER GPU to `luid` (the analogue of Apollo's `SetRenderAdapter`). No
/// output buffer. Issued on the driver handle BEFORE `IOCTL_ADD` to steer which GPU the new target
/// renders on — on a multi-adapter box this stops DXGI from reparenting the virtual output onto a
/// different adapter than the one we duplicate/encode on (the ACCESS_LOST storm).
///
/// NOTE: the pf-vdisplay driver currently returns `STATUS_NOT_IMPLEMENTED` for this IOCTL (a STEP-4
/// stub), so this call WILL fail today. Callers tolerate the `Err` (warn + continue) — exactly as the
/// SudoVDA backend tolerated the driver IGNORING the pin.
/// different adapter than the one we duplicate/encode on (the ACCESS_LOST storm). The driver
/// implements it (`control.rs` → `adapter::set_render_adapter`); callers still tolerate an `Err`
/// (warn + continue) since the driver reports its real render LUID in the shared header either way.
unsafe fn set_render_adapter(h: HANDLE, luid: LUID) -> Result<()> {
let req = control::SetRenderAdapterRequest {
luid_low: luid.LowPart,
@@ -185,42 +183,102 @@ pub(crate) unsafe fn send_frame_channel(
.context("pf-vdisplay SET_FRAME_CHANNEL")
}
/// RAII over a SetupAPI device-info list: every exit path of [`open_device`] destroys it (the error
/// paths used to leak one `HDEVINFO` per failed open — and a driverless / mid-upgrade box probes
/// repeatedly).
struct DevInfoList(HDEVINFO);
impl Drop for DevInfoList {
fn drop(&mut self) {
// SAFETY: `self.0` is the live device-info list this wrapper solely owns; destroyed exactly
// once here.
unsafe {
let _ = SetupDiDestroyDeviceInfoList(self.0);
}
}
}
unsafe fn open_device() -> Result<HANDLE> {
let hdev = SetupDiGetClassDevsW(
Some(&PF_VDISPLAY_INTERFACE),
PCWSTR::null(),
None,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT,
)
.context("SetupDiGetClassDevsW(pf-vdisplay) — is the pf-vdisplay driver installed?")?;
// SAFETY: plain SetupAPI enumeration call; the returned list is solely owned by the RAII wrapper.
let hdev = DevInfoList(
unsafe {
SetupDiGetClassDevsW(
Some(&PF_VDISPLAY_INTERFACE),
PCWSTR::null(),
None,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT,
)
}
.context("SetupDiGetClassDevsW(pf-vdisplay) — is the pf-vdisplay driver installed?")?,
);
let mut idata = SP_DEVICE_INTERFACE_DATA {
cbSize: size_of::<SP_DEVICE_INTERFACE_DATA>() as u32,
..Default::default()
};
SetupDiEnumDeviceInterfaces(hdev, None, &PF_VDISPLAY_INTERFACE, 0, &mut idata)
.context("SetupDiEnumDeviceInterfaces(pf-vdisplay)")?;
let mut required = 0u32;
let _ = SetupDiGetDeviceInterfaceDetailW(hdev, &idata, None, 0, Some(&mut required), None);
let mut buf = vec![0u8; required as usize];
let detail = buf.as_mut_ptr() as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_W;
(*detail).cbSize = size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>() as u32;
SetupDiGetDeviceInterfaceDetailW(hdev, &idata, Some(detail), required, None, None)
.context("SetupDiGetDeviceInterfaceDetailW(pf-vdisplay)")?;
let handle = CreateFileW(
PCWSTR((*detail).DevicePath.as_ptr()),
0xC000_0000, // GENERIC_READ | GENERIC_WRITE
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(0),
None,
)
.context("CreateFileW(pf-vdisplay device)")?;
let _ = SetupDiDestroyDeviceInfoList(hdev);
Ok(handle)
// Enumerate EVERY interface instance, not just index 0: after a driver upgrade a present-but-
// failed devnode (Code 10) can hold index 0 while the LIVE node's interface sits at a later
// index — the old single-index read then failed every session with "driver not installed"
// even though a working interface existed. `SPINT_ACTIVE` filters dead interfaces (an interface
// is active only while its owning device is started); the first active + openable one wins.
let mut inactive = 0u32;
let mut last_err: Option<anyhow::Error> = None;
for index in 0..64u32 {
let mut idata = SP_DEVICE_INTERFACE_DATA {
cbSize: size_of::<SP_DEVICE_INTERFACE_DATA>() as u32,
..Default::default()
};
// SAFETY: `hdev.0` is the live list; `idata` is a valid, size-stamped out-param.
if unsafe {
SetupDiEnumDeviceInterfaces(hdev.0, None, &PF_VDISPLAY_INTERFACE, index, &mut idata)
}
.is_err()
{
break; // ERROR_NO_MORE_ITEMS — no further candidates
}
if idata.Flags & SPINT_ACTIVE == 0 {
inactive += 1;
continue;
}
let mut required = 0u32;
// SAFETY: sizing call — null buffer plus a valid `required` out-param; the expected
// ERROR_INSUFFICIENT_BUFFER "failure" is ignored and only `required` is consumed.
let _ = unsafe {
SetupDiGetDeviceInterfaceDetailW(hdev.0, &idata, None, 0, Some(&mut required), None)
};
if (required as usize) < size_of::<u32>() {
continue; // sizing failed — never stamp a cbSize through an under-sized buffer
}
let mut buf = vec![0u8; required as usize];
let detail = buf.as_mut_ptr() as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_W;
// SAFETY: `buf` is `required` bytes (>= 4, checked above), so stamping `cbSize` and letting
// the API fill up to `required` bytes stays in bounds; `detail` aliases `buf` only within
// this iteration, and the `DevicePath` pointer is read before `buf` is dropped.
let opened = unsafe {
(*detail).cbSize = size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>() as u32;
SetupDiGetDeviceInterfaceDetailW(hdev.0, &idata, Some(detail), required, None, None)
.context("SetupDiGetDeviceInterfaceDetailW(pf-vdisplay)")
.and_then(|()| {
CreateFileW(
PCWSTR((*detail).DevicePath.as_ptr()),
0xC000_0000, // GENERIC_READ | GENERIC_WRITE
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(0),
None,
)
.context("CreateFileW(pf-vdisplay device)")
})
};
match opened {
Ok(h) => return Ok(h),
// A raced-away or wedged device — remember the error, try the next interface.
Err(e) => last_err = Some(e),
}
}
Err(last_err.unwrap_or_else(|| {
anyhow::anyhow!(
"no ACTIVE pf-vdisplay device interface found ({inactive} inactive) — is the \
pf-vdisplay driver installed and its device started?"
)
}))
}
/// The pf-vdisplay IOCTL surface behind the shared [`VirtualDisplayManager`](super::manager::VirtualDisplayManager)
@@ -232,30 +290,30 @@ impl VdisplayDriver for PfVdisplayDriver {
"pf-vdisplay"
}
unsafe fn open(&self) -> Result<(OwnedHandle, u32)> {
unsafe fn open(&self, reap_orphans: bool) -> Result<(OwnedHandle, u32)> {
// SAFETY: `open_device` is `unsafe` only because it issues SetupAPI enumeration + `CreateFileW`
// FFI; it takes no arguments and returns an owned raw `HANDLE` (or `Err`). Called here on the
// backend-init thread, with no precondition beyond a valid thread context.
let device = unsafe { open_device()? };
// Wrap IMMEDIATELY: every `?` below must close the device exactly once — the old
// wrap-on-success-only shape leaked the raw handle whenever GET_INFO itself failed.
// SAFETY: `device` is the valid handle `open_device` just returned; ownership transfers into
// the `OwnedHandle` (single owner, `CloseHandle` on drop).
let device = unsafe { OwnedHandle::from_raw_handle(device.0 as _) };
let raw = HANDLE(device.as_raw_handle());
// HARD protocol-version check (unlike SudoVDA's best-effort log): a mismatched host/driver pair
// fails loudly here rather than corrupting the IOCTL stream.
let mut info_buf = [0u8; size_of::<control::InfoReply>()];
// SAFETY: `ioctl` requires `h` to be a valid device handle and its slices to be valid for the
// call. `device` is the live handle just returned by `open_device`. `IOCTL_GET_INFO` takes no
// input (`&[]`) and writes into `info_buf`, a stack `[u8; size_of::<InfoReply>()]` whose length
// is passed as the output size — so `DeviceIoControl` can't write OOB — and which outlives this
// synchronous call.
unsafe { ioctl(device, control::IOCTL_GET_INFO, &[], &mut info_buf) }
// call. `raw` borrows the live `OwnedHandle` above for this synchronous call. `IOCTL_GET_INFO`
// takes no input (`&[]`) and writes into `info_buf`, a stack `[u8; size_of::<InfoReply>()]`
// whose length is passed as the output size — so `DeviceIoControl` can't write OOB — and which
// outlives this synchronous call.
unsafe { ioctl(raw, control::IOCTL_GET_INFO, &[], &mut info_buf) }
.context("pf-vdisplay IOCTL_GET_INFO (version handshake)")?;
let info: control::InfoReply =
bytemuck::pod_read_unaligned(&info_buf[..size_of::<control::InfoReply>()]);
if info.protocol_version != pf_driver_proto::PROTOCOL_VERSION {
// SAFETY: `device` is the valid raw handle from `open_device` and has NOT yet been wrapped
// in an `OwnedHandle` (that happens only on the success path below), so this error path is
// the sole owner closing it exactly once — no double-close.
unsafe {
let _ = CloseHandle(device);
}
anyhow::bail!(
"pf-vdisplay protocol mismatch: host expects {}, driver reports {} — install matching \
host + driver",
@@ -269,12 +327,19 @@ impl VdisplayDriver for PfVdisplayDriver {
info.protocol_version,
watchdog_s
);
// Reap monitors orphaned by a crashed previous host — a FIRST-CLASS op (driver returns SUCCESS).
// Reap monitors orphaned by a crashed previous host — a FIRST-CLASS op (driver returns
// SUCCESS). FIRST open of the process only: a REOPEN (the manager retired a dead handle after
// a driver upgrade / WUDFHost restart) can race sessions that still believe they are live, and
// an unconditional CLEAR_ALL there would raze them.
if !reap_orphans {
reap_ghost_monitors();
return Ok((device, watchdog_s));
}
let mut none: [u8; 0] = [];
// SAFETY: `device` is the live handle from `open_device` (still owned here, before it is wrapped
// below). `IOCTL_CLEAR_ALL` has no input and no output: `&[]` and the empty `none` slice pass
// zero-length buffers, so nothing is read or written through them.
if unsafe { ioctl(device, control::IOCTL_CLEAR_ALL, &[], &mut none) }.is_ok() {
// SAFETY: `raw` borrows the live `OwnedHandle` above. `IOCTL_CLEAR_ALL` has no input and no
// output: `&[]` and the empty `none` slice pass zero-length buffers, so nothing is read or
// written through them.
if unsafe { ioctl(raw, control::IOCTL_CLEAR_ALL, &[], &mut none) }.is_ok() {
tracing::info!("cleared orphaned virtual monitors on host startup");
} else {
tracing::warn!("pf-vdisplay IOCTL_CLEAR_ALL failed on startup (continuing)");
@@ -285,14 +350,7 @@ impl VdisplayDriver for PfVdisplayDriver {
// monitor-slot budget — prevents the 0x80070490 slot-exhaustion wedge from carrying across
// restarts (the reason a restart's CLEAR_ALL alone never recovered it before).
reap_ghost_monitors();
Ok((
// SAFETY: `device` is the valid handle from `open_device`, still owned here and NOT closed
// on this success path (the error paths above close it and return). `from_raw_handle`'s
// contract — caller owns a valid handle — holds, so ownership transfers cleanly into the
// `OwnedHandle`: exactly one owner, which `CloseHandle`s it on drop.
unsafe { OwnedHandle::from_raw_handle(device.0 as _) },
watchdog_s,
))
Ok((device, watchdog_s))
}
unsafe fn add_monitor(