Files
punktfunk/packaging/windows/drivers/pf-umdf-util/src/section.rs
T
enricobuehler 8b47be668f feat(host/windows): seal the host↔driver channels (frame + gamepad, proto v2)
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>
2026-07-03 12:08:56 +00:00

242 lines
11 KiB
Rust

//! Safe access to Win32 shared-memory sections: [`MappedView`] wraps a mapped view of a known
//! length and exposes bounds- and alignment-checked accessors, so callers never touch the raw base
//! pointer. Cross-process sync fields (seqs, pids, handle values) go through real atomics; bulk
//! report regions use plain unaligned copies, guarded by the channel protocol's seq fields — the
//! same access discipline the host side uses (`inject/windows/gamepad_raii.rs`).
use core::ffi::c_void;
use core::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, Ordering};
const FILE_MAP_RW: u32 = 0x0002 | 0x0004; // FILE_MAP_WRITE | FILE_MAP_READ
// kernel32 file-mapping APIs (resolved via std's kernel32 import; UMDF permits file mapping).
unsafe extern "system" {
fn OpenFileMappingW(access: u32, inherit: i32, name: *const u16) -> *mut c_void;
fn MapViewOfFile(h: *mut c_void, access: u32, hi: u32, lo: u32, len: usize) -> *mut c_void;
fn UnmapViewOfFile(addr: *const c_void) -> i32;
fn CloseHandle(h: *mut c_void) -> i32;
}
/// A read/write view over a mapped shared section of exactly `len` bytes. Every accessor
/// bounds-checks (and, for the atomic ones, alignment-checks) its offset, so no caller can read or
/// write outside the mapping — the offsets are `offset_of!` constants from `pf_driver_proto`, making
/// a failed check a compile-shaped logic bug (it aborts the WUDFHost rather than corrupting).
///
/// Concurrency: the peer process writes the section concurrently. Fields used for cross-process
/// synchronization must be accessed through the `load_*`/`store_*` atomic accessors; the bulk
/// byte/scalar accessors are plain unaligned accesses whose consistency is guarded by the channel
/// protocol (seq-fenced publishes), exactly as on the host side.
pub struct MappedView {
base: *mut u8,
len: usize,
}
// SAFETY: `MappedView` is a pointer + length over an OS mapping that stays valid until
// `UnmapViewOfFile` in `Drop` (or forever, once leaked into a `ViewCell`). All access goes through
// the checked accessors — atomics for shared sync fields, unaligned reads/writes for bulk data —
// none of which require a single-thread owner, so sharing/sending the view across the driver's
// callback threads is sound.
unsafe impl Send for MappedView {}
// SAFETY: as above — `&MappedView` only exposes accessors that are safe under concurrent use.
unsafe impl Sync for MappedView {}
impl MappedView {
/// Open the named section `name` and map its first `len` bytes read/write. `None` if the name
/// does not exist (e.g. the host is gone) or the mapping fails. The section handle is closed
/// immediately — the view keeps the section alive.
pub fn open_named(name: &str, len: usize) -> Option<MappedView> {
let wide: Vec<u16> = name.encode_utf16().chain(std::iter::once(0)).collect();
// SAFETY: `wide` is a valid NUL-terminated UTF-16 string for the duration of the call.
let h = unsafe { OpenFileMappingW(FILE_MAP_RW, 0, wide.as_ptr()) };
if h.is_null() {
return None;
}
// SAFETY: `h` is the valid mapping handle just opened; map `len` bytes read/write. The view
// keeps the section alive, so the handle can be closed right away.
let base = unsafe { MapViewOfFile(h, FILE_MAP_RW, 0, 0, len) } as *mut u8;
// SAFETY: `h` is the valid handle from `OpenFileMappingW`, owned solely by this function.
unsafe { CloseHandle(h) };
if base.is_null() {
return None;
}
Some(MappedView { base, len })
}
/// Map `len` bytes of a section from a raw handle VALUE (the sealed channel's delivery — a
/// handle the host duplicated into this process). `None` if the value does not resolve to a
/// mappable section. The handle itself is NOT consumed — the caller decides after validating
/// the mapped content (see [`close_handle_value`]).
pub fn from_handle_value(value: u64, len: usize) -> Option<MappedView> {
if value == 0 {
return None;
}
// SAFETY: `MapViewOfFile` on an arbitrary handle value is safe — it fails (returns null)
// unless the value resolves to a section handle in this process's table with RW access.
let base = unsafe { MapViewOfFile(value as usize as *mut c_void, FILE_MAP_RW, 0, 0, len) }
as *mut u8;
if base.is_null() {
return None;
}
Some(MappedView { base, len })
}
/// Assert `off..off+n` is inside the view and, for atomics, `align`-aligned. The view base is
/// page-aligned (`MapViewOfFile`), so field alignment reduces to offset alignment.
#[inline]
fn check(&self, off: usize, n: usize, align: usize) {
assert!(
off.is_multiple_of(align) && off.checked_add(n).is_some_and(|end| end <= self.len),
"MappedView access out of bounds/alignment (off={off}, n={n}, len={})",
self.len
);
}
/// Atomic `u32` load at `off` (must be 4-aligned) — the cross-process sync accessor.
#[inline]
pub fn load_u32(&self, off: usize, order: Ordering) -> u32 {
self.check(off, 4, 4);
// SAFETY: `off` is in-bounds + 4-aligned per `check`, and the page-aligned mapping stays
// valid while `&self` lives; an `AtomicU32` view over shared memory is the defined way to
// race the peer process.
unsafe { (*(self.base.add(off) as *const AtomicU32)).load(order) }
}
/// Atomic `u32` store at `off` (must be 4-aligned).
#[inline]
pub fn store_u32(&self, off: usize, v: u32, order: Ordering) {
self.check(off, 4, 4);
// SAFETY: as `load_u32` — in-bounds, aligned, valid for `&self`'s lifetime.
unsafe { (*(self.base.add(off) as *const AtomicU32)).store(v, order) }
}
/// Atomic `u64` load at `off` (must be 8-aligned).
#[inline]
pub fn load_u64(&self, off: usize, order: Ordering) -> u64 {
self.check(off, 8, 8);
// SAFETY: as `load_u32`, with 8-byte size/alignment checked.
unsafe { (*(self.base.add(off) as *const AtomicU64)).load(order) }
}
/// Plain byte read at `off` (bulk-region accessor — protocol-guarded, see the type docs).
#[inline]
pub fn read_u8(&self, off: usize) -> u8 {
self.check(off, 1, 1);
// SAFETY: in-bounds per `check`; a one-byte read cannot tear.
unsafe { *self.base.add(off) }
}
/// Plain byte write at `off`.
#[inline]
pub fn write_u8(&self, off: usize, v: u8) {
self.check(off, 1, 1);
// SAFETY: in-bounds per `check`; a one-byte write cannot tear.
unsafe { *self.base.add(off) = v }
}
/// Plain (unaligned) `u16` read at `off`.
#[inline]
pub fn read_u16(&self, off: usize) -> u16 {
self.check(off, 2, 1);
// SAFETY: in-bounds per `check`; `read_unaligned` has no alignment requirement.
unsafe { core::ptr::read_unaligned(self.base.add(off) as *const u16) }
}
/// Plain (unaligned) `u32` read at `off` — the bulk-region accessor for a DATA-section scalar
/// (host-written state / a driver-written publish counter; consistency comes from the channel
/// protocol's seq fences, not from this access, exactly as on the host side).
#[inline]
pub fn read_u32(&self, off: usize) -> u32 {
self.check(off, 4, 1);
// SAFETY: in-bounds per `check`; `read_unaligned` has no alignment requirement.
unsafe { core::ptr::read_unaligned(self.base.add(off) as *const u32) }
}
/// Plain (unaligned) `u32` write at `off` (bulk-region accessor).
#[inline]
pub fn write_u32(&self, off: usize, v: u32) {
self.check(off, 4, 1);
// SAFETY: in-bounds per `check`; `write_unaligned` has no alignment requirement.
unsafe { core::ptr::write_unaligned(self.base.add(off) as *mut u32, v) }
}
/// Plain (unaligned) `i16` read at `off`.
#[inline]
pub fn read_i16(&self, off: usize) -> i16 {
self.check(off, 2, 1);
// SAFETY: in-bounds per `check`; `read_unaligned` has no alignment requirement.
unsafe { core::ptr::read_unaligned(self.base.add(off) as *const i16) }
}
/// Copy `dst.len()` bytes out of the view starting at `off`.
pub fn read_bytes(&self, off: usize, dst: &mut [u8]) {
self.check(off, dst.len(), 1);
// SAFETY: the source range is in-bounds per `check`; `dst` is a live exclusive borrow of
// `dst.len()` writable bytes and cannot overlap the foreign mapping.
unsafe { core::ptr::copy_nonoverlapping(self.base.add(off), dst.as_mut_ptr(), dst.len()) }
}
/// Copy `src` into the view starting at `off`.
pub fn write_bytes(&self, off: usize, src: &[u8]) {
self.check(off, src.len(), 1);
// SAFETY: the destination range is in-bounds per `check`; `src` is a live borrow that
// cannot overlap the foreign mapping.
unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), self.base.add(off), src.len()) }
}
}
impl Drop for MappedView {
fn drop(&mut self) {
// SAFETY: `base` is the live view from `MapViewOfFile`, unmapped exactly once (here).
unsafe {
UnmapViewOfFile(self.base as *const c_void);
}
}
}
/// Close a raw handle VALUE owned by this process — the sealed channel's adopt-on-success step
/// (the mapped view keeps the section alive after the close). Closing a value that is not a live
/// handle of this process is a logic error the OS rejects (returns FALSE); it is not memory-unsafe.
pub fn close_handle_value(value: u64) {
if value == 0 {
return;
}
// SAFETY: `CloseHandle` validates the value against this process's handle table; no memory is
// dereferenced through it.
unsafe { CloseHandle(value as usize as *mut c_void) };
}
/// A lock-free cell holding the driver's adopted DATA view as a **leaked** `&'static MappedView`.
/// [`set`](Self::set) leaks the new view (and abandons the old one) instead of ever unmapping:
/// a concurrent framework callback may still be reading through a previously-returned reference, so
/// the mapping must never be torn down — a deliberate, bounded leak (one small view per delivery,
/// at most a handful per pad lifetime).
pub struct ViewCell(AtomicPtr<MappedView>);
impl Default for ViewCell {
fn default() -> Self {
Self::new()
}
}
impl ViewCell {
pub const fn new() -> ViewCell {
ViewCell(AtomicPtr::new(core::ptr::null_mut()))
}
/// The current view, if one was published. The `'static` lifetime is real: published views are
/// leaked and never unmapped.
pub fn get(&self) -> Option<&'static MappedView> {
let p = self.0.load(Ordering::Acquire);
// SAFETY: `p` is either null or a `Box::leak`ed `MappedView` published by `set`, which is
// never dropped or unmapped — so the reference is valid for the process lifetime.
(!p.is_null()).then(|| unsafe { &*p })
}
/// Publish `view`, leaking it (and abandoning — NOT freeing — any previous view; see the type
/// docs for why the old mapping must stay alive).
pub fn set(&self, view: MappedView) {
let leaked: &'static mut MappedView = Box::leak(Box::new(view));
self.0.swap(leaked, Ordering::Release);
}
}