95a08e99c3
Frame ring (pf-vdisplay) and both gamepad SHM channels move off named Global\ objects (openable by any sibling LocalService) to UNNAMED sections/events whose handles the host DuplicateHandles into the driver's verified WUDFHost with least access — frame delivery over the SYSTEM+admins-only IOCTL_SET_FRAME_CHANNEL, pads over a 32-byte named bootstrap mailbox (pid + handle value only, DoS-bounded; HID minidrivers have no control device). Driver-validated pad_index kills cross-pad redirects; v1↔v2 mixes fail closed with diagnosis logs on both sides. Sibling-LocalService denial proven empirically (design/idd-push-security.md, design/gamepad-channel-sealing.md). Driver-side raw ops now live behind pf-umdf-util (checked shm accessors, the forbid(unsafe_code) ChannelClient state machine, WDF request tokens) — the pad drivers' logic is 100% safe Rust; whole drivers workspace clippy-gated in CI. driver install --gamepad now sweeps SWD\punktfunk phantom devnodes: a re-created SwDevice REVIVES the old devnode with its previously-bound driver (never re-ranks), so an upgrade otherwise leaves the old driver serving — or, across the v1→v2 fence, a dead pad (found live on the RTX box). On-glass validated on the RTX 4090 box: frame path 7007 frames p50 2.06 ms cross-machine; DualSense + XUSB "sealed pad channel mapped"/proto=2 attach via both the test harness and a real streaming session; phantom-sweep repro. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
3.4 KiB
Rust
76 lines
3.4 KiB
Rust
//! Minimal driver logger. `OutputDebugStringA` always (ETW/DebugView); the optional world-writable file
|
|
//! (`C:\Users\Public\pfvd-driver.log`, readable over SSH) is now OPT-IN — debug builds, or the
|
|
//! `PFVD_DEBUG_LOG` env var, only — so a RELEASE build never writes it (audit §4.4: it was an
|
|
//! info-leak/DoS surface). Best-effort; ignores all errors. Production driver-state visibility is the
|
|
//! SharedHeader `driver_status` channel, not this file.
|
|
|
|
unsafe extern "system" {
|
|
fn OutputDebugStringA(s: *const u8);
|
|
}
|
|
|
|
/// Whether the world-writable bring-up file log is enabled (resolved once). Off in release builds unless
|
|
/// `PFVD_DEBUG_LOG` is set.
|
|
fn file_log_enabled() -> bool {
|
|
use std::sync::OnceLock;
|
|
static ON: OnceLock<bool> = OnceLock::new();
|
|
*ON.get_or_init(|| cfg!(debug_assertions) || std::env::var_os("PFVD_DEBUG_LOG").is_some())
|
|
}
|
|
|
|
/// Process-lifetime append handle to the bring-up log, opened ONCE (by whichever thread logs first) and
|
|
/// shared via a `Mutex` — so the swap-chain WORKER thread's writes land too. Per-call open/append raced
|
|
/// the control thread and/or could fail under the worker's restricted token, hiding exactly the
|
|
/// swap-chain-processor lines a game-break repro needs (game-capture bug S3). `flush` after each line so a
|
|
/// crash/stall doesn't lose the tail.
|
|
fn file_appender() -> Option<&'static std::sync::Mutex<std::fs::File>> {
|
|
use std::sync::OnceLock;
|
|
static APPENDER: OnceLock<Option<std::sync::Mutex<std::fs::File>>> = OnceLock::new();
|
|
APPENDER
|
|
.get_or_init(|| {
|
|
if !file_log_enabled() {
|
|
return None;
|
|
}
|
|
std::fs::OpenOptions::new()
|
|
.create(true)
|
|
.append(true)
|
|
.open("C:\\Users\\Public\\pfvd-driver.log")
|
|
.ok()
|
|
.map(std::sync::Mutex::new)
|
|
})
|
|
.as_ref()
|
|
}
|
|
|
|
pub fn log(s: &str) {
|
|
if let Ok(c) = std::ffi::CString::new(s) {
|
|
// SAFETY: `c` is a valid NUL-terminated string for the duration of the call.
|
|
unsafe { OutputDebugStringA(c.as_ptr().cast()) };
|
|
}
|
|
use std::io::Write;
|
|
if let Some(m) = file_appender()
|
|
&& let Ok(mut f) = m.lock()
|
|
{
|
|
let _ = writeln!(f, "{s}");
|
|
let _ = f.flush();
|
|
}
|
|
}
|
|
|
|
macro_rules! dbglog {
|
|
($($a:tt)*) => { $crate::log::log(&::std::format!($($a)*)) };
|
|
}
|
|
|
|
/// Zero-initialise a C POD struct (windows-rs / WDK / IddCx). These are `#[repr(C)]` framework structs
|
|
/// whose all-zero bit pattern is a valid zero-initialised value; the caller stamps the required
|
|
/// `.Size`/etc fields immediately after. Centralises the `unsafe { core::mem::zeroed() }` the IddCx/WDF
|
|
/// bring-up needs — pass the type EXPLICITLY (`pod_init!(T)`) so it works without a binding annotation.
|
|
/// Made crate-visible by the same `#[macro_use] mod log;` in `lib.rs` that exports `dbglog!`.
|
|
macro_rules! pod_init {
|
|
($t:ty) => {{
|
|
// SAFETY: $t is a C POD (windows-rs/WDK/IddCx struct); its all-zero bit pattern is a valid
|
|
// zero-initialised value and the caller sets the required .Size/etc fields immediately after.
|
|
// `unused_unsafe`: pod_init! is also expanded at call sites already inside an `unsafe` block
|
|
// (where this `unsafe` is redundant), but it IS required at the non-unsafe sites — so allow it.
|
|
#[allow(unused_unsafe)]
|
|
let zeroed = unsafe { ::core::mem::zeroed::<$t>() };
|
|
zeroed
|
|
}};
|
|
}
|