diff --git a/.gitea/workflows/windows-drivers.yml b/.gitea/workflows/windows-drivers.yml index ca74f3c2..e77da010 100644 --- a/.gitea/workflows/windows-drivers.yml +++ b/.gitea/workflows/windows-drivers.yml @@ -159,9 +159,10 @@ jobs: # The gamepad drivers' business logic is 100% safe (it moved onto pf-umdf-util, the audited # unsafe layer); pf-vdisplay + wdk-iddcx are inherently FFI-bound but every `unsafe {}` carries a # `// SAFETY:` proof. Both invariants are lint-gated (`unsafe_op_in_unsafe_fn` + - # `undocumented_unsafe_blocks`); this step keeps them from regressing. (wdk-probe is a - # toolchain-only probe crate and is excluded.) - run: cargo clippy -p pf-umdf-util -p pf-xusb -p pf-gamepad -p pf-mouse -p wdk-iddcx -p pf-vdisplay --all-targets -- -D warnings + # `undocumented_unsafe_blocks`); this step keeps them from regressing. wdk-probe is a + # toolchain-only probe crate, but it holds real DDI slot-dispatch unsafe (iddcx_rt.rs), so it + # runs the same gates. + run: cargo clippy -p pf-umdf-util -p pf-xusb -p pf-gamepad -p pf-mouse -p wdk-iddcx -p pf-vdisplay -p wdk-probe --all-targets -- -D warnings - name: cargo fmt --check the safe-layer + gamepad/mouse drivers run: cargo fmt -p pf-umdf-util -p pf-xusb -p pf-gamepad -p pf-mouse --check - name: Inspect /INTEGRITYCHECK (before) — expect FORCE_INTEGRITY set by wdk-build diff --git a/crates/pf-clipboard/src/host/windows.rs b/crates/pf-clipboard/src/host/windows.rs index 2af936a2..43dfc54a 100644 --- a/crates/pf-clipboard/src/host/windows.rs +++ b/crates/pf-clipboard/src/host/windows.rs @@ -17,8 +17,8 @@ //! (`PostMessage` is the documented thread-safe way to poke a message loop). Per-window state hangs //! off `GWLP_USERDATA`, so multiple concurrent sessions each get their own window + state. -// Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it (unsafe-proof program). -#![deny(clippy::undocumented_unsafe_blocks)] +// Every `unsafe` block in this file carries a `// SAFETY:` proof; the deny enforcing it sits at +// the crate root (lib.rs), covering every backend. use std::cell::RefCell; use std::sync::{Arc, Mutex}; diff --git a/crates/pf-clipboard/src/lib.rs b/crates/pf-clipboard/src/lib.rs index d035fca4..ac3c0343 100644 --- a/crates/pf-clipboard/src/lib.rs +++ b/crates/pf-clipboard/src/lib.rs @@ -10,6 +10,10 @@ //! [`spawn_decline_loop`] — so its control loop compiles unchanged on every host platform; the //! platform split lives entirely behind [`start`]. +// Crate-wide: every `unsafe` block in any backend carries a `// SAFETY:` proof (unsafe-proof +// program). At the root — not per-module — so a new backend under `host/` is covered on creation. +#![deny(clippy::undocumented_unsafe_blocks)] + use std::sync::atomic::AtomicBool; use std::sync::Arc; diff --git a/packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs b/packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs index 289a7074..fc46f71a 100644 --- a/packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs +++ b/packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs @@ -26,6 +26,10 @@ unsafe fn ddi(index: i32) -> T { let table = (&raw const IddFunctions).cast::(); // SAFETY: `index` is a valid IddCx table slot; the slot holds a `PFN_*` whose layout is `T`. let slot = unsafe { table.add(index as usize) }; + // SAFETY: `slot` is in bounds of the stub-populated table (per this function's contract), + // pointer-aligned (a table of `PFN_IDD_CX` entries), and initialized by `IddCxStub` at driver + // load; every `PFN_*` is an `Option`, for which both null (None) and a populated + // entry are valid values to read as `T`. unsafe { slot.cast::().read() } } @@ -45,8 +49,12 @@ pub unsafe fn IddCxDeviceInitConfig( device_init: PWDFDEVICE_INIT, config: *const IDD_CX_CLIENT_CONFIG, ) -> NTSTATUS { + // SAFETY: index and PFN type name the same DDI (IddCxDeviceInitConfig), and this function's + // contract — call only during EvtDriverDeviceAdd — implies the driver is loaded and the + // stub's table populated. let f: PFN_IDDCXDEVICEINITCONFIG = unsafe { ddi(_IDDFUNCENUM::IddCxDeviceInitConfigTableIndex) }; + // SAFETY: the driver is loaded (same premise as above), so the stub globals are set. let g = unsafe { globals() }; // SAFETY: dispatching a populated DDI with the stub globals and caller-valid args. unsafe { (f.unwrap())(g, device_init, config) } @@ -55,8 +63,12 @@ pub unsafe fn IddCxDeviceInitConfig( /// # Safety /// `device` must be a valid `WDFDEVICE` previously configured via [`IddCxDeviceInitConfig`]. pub unsafe fn IddCxDeviceInitialize(device: WDFDEVICE) -> NTSTATUS { + // SAFETY: index and PFN type name the same DDI (IddCxDeviceInitialize), and this function's + // contract — `device` came out of a WdfDeviceCreate on an IddCx-configured init — implies + // the driver is loaded and the stub's table populated. let f: PFN_IDDCXDEVICEINITIALIZE = unsafe { ddi(_IDDFUNCENUM::IddCxDeviceInitializeTableIndex) }; + // SAFETY: the driver is loaded (same premise as above), so the stub globals are set. let g = unsafe { globals() }; // SAFETY: dispatching a populated DDI with the stub globals and a caller-valid device. unsafe { (f.unwrap())(g, device) } @@ -68,8 +80,12 @@ pub unsafe fn IddCxAdapterInitAsync( in_args: *const IDARG_IN_ADAPTER_INIT, out_args: *mut IDARG_OUT_ADAPTER_INIT, ) -> NTSTATUS { + // SAFETY: index and PFN type name the same DDI (IddCxAdapterInitAsync), and IddCx DDIs are + // only reachable from framework callbacks — i.e. with the driver loaded and the stub's + // table populated. let f: PFN_IDDCXADAPTERINITASYNC = unsafe { ddi(_IDDFUNCENUM::IddCxAdapterInitAsyncTableIndex) }; + // SAFETY: the driver is loaded (same premise as above), so the stub globals are set. let g = unsafe { globals() }; // SAFETY: dispatching a populated DDI with the stub globals and caller-valid args. unsafe { (f.unwrap())(g, in_args, out_args) } diff --git a/packaging/windows/drivers/wdk-probe/src/lib.rs b/packaging/windows/drivers/wdk-probe/src/lib.rs index 7a315e05..723c0d19 100644 --- a/packaging/windows/drivers/wdk-probe/src/lib.rs +++ b/packaging/windows/drivers/wdk-probe/src/lib.rs @@ -4,7 +4,9 @@ //! stub link AND prove the `iddcx` subset is callable + links against `IddCxStub`. Also force-links the //! shared `pf-driver-proto` ABI crate (no_std + bytemuck) across the workspace boundary. -#![allow(non_snake_case, clippy::missing_safety_doc)] +#![allow(non_snake_case)] +#![deny(unsafe_op_in_unsafe_fn)] +#![deny(clippy::undocumented_unsafe_blocks)] mod iddcx_rt; mod iddcx_surface_assert; @@ -29,6 +31,9 @@ static PROTO_GUID_LO: u64 = pf_driver_proto::PF_VDISPLAY_INTERFACE_GUID_U128 as #[unsafe(no_mangle)] pub static IddMinimumVersionRequired: ULONG = 4; +/// # Safety +/// Called by the UMDF host at driver load: `driver` and `registry_path` must be the +/// loader-provided driver object and registry path, both valid for the duration of the call. #[unsafe(export_name = "DriverEntry")] pub unsafe extern "system" fn driver_entry( driver: PDRIVER_OBJECT, @@ -57,6 +62,9 @@ extern "C" fn evt_device_add(_driver: WDFDRIVER, mut device_init: PWDFDEVICE_INI // IddCxStub (CI), and that a self-signed IddCx driver loads + dispatches (box); the full callback // surface + a valid adapter come with the driver port. At runtime IddCxDeviceInitConfig will reject // the null callbacks, but the call site is what links IddCxStub and exercises table dispatch. + // SAFETY: IDD_CX_CLIENT_CONFIG is a C aggregate of integers, pointers, and nullable + // callbacks (`Option`) — all-zero is a valid value (null callbacks, see above); + // Size is stamped on the next line. let mut cfg: IDD_CX_CLIENT_CONFIG = unsafe { core::mem::zeroed() }; cfg.Size = core::mem::size_of::() as ULONG; // SAFETY: device_init is the framework-provided init; cfg is a valid (if minimal) config. @@ -76,8 +84,11 @@ extern "C" fn evt_device_add(_driver: WDFDRIVER, mut device_init: PWDFDEVICE_INI // SAFETY: device is the just-created WDFDEVICE. let _ = unsafe { iddcx_rt::IddCxDeviceInitialize(device) }; - // SAFETY: zeroed adapter-init args — a link/dispatch reference, not a valid adapter (see above). + // SAFETY: IDARG_IN_ADAPTER_INIT is a C aggregate of handles/pointers/integers — all-zero is + // a valid value (a link/dispatch reference, not a valid adapter; see above). let in_args: IDARG_IN_ADAPTER_INIT = unsafe { core::mem::zeroed() }; + // SAFETY: IDARG_OUT_ADAPTER_INIT is a C aggregate of handles — all-zero is a valid value, + // and the call below only writes through it. let mut out_args: IDARG_OUT_ADAPTER_INIT = unsafe { core::mem::zeroed() }; // SAFETY: in/out args are valid local storage for the call. let _ = unsafe { iddcx_rt::IddCxAdapterInitAsync(&in_args, &mut out_args) }; diff --git a/packaging/windows/pf-vkhdr-layer/Cargo.toml b/packaging/windows/pf-vkhdr-layer/Cargo.toml index e6876a4e..bbd08740 100644 --- a/packaging/windows/pf-vkhdr-layer/Cargo.toml +++ b/packaging/windows/pf-vkhdr-layer/Cargo.toml @@ -13,6 +13,14 @@ crate-type = ["cdylib"] [dependencies] ash = "=0.38.0+1.3.281" +# This package is its own workspace (see below), so the main workspace's lint tables never reach +# it — the unsafe discipline has to be restated here or it silently doesn't apply. +[lints.rust] +unsafe_op_in_unsafe_fn = "deny" + +[lints.clippy] +undocumented_unsafe_blocks = "deny" + [profile.release] opt-level = 2 panic = "abort" diff --git a/packaging/windows/pf-vkhdr-layer/src/lib.rs b/packaging/windows/pf-vkhdr-layer/src/lib.rs index 92ae1f34..8d9517eb 100644 --- a/packaging/windows/pf-vkhdr-layer/src/lib.rs +++ b/packaging/windows/pf-vkhdr-layer/src/lib.rs @@ -23,9 +23,17 @@ //! Off-switches: the loader-standard `DISABLE_PF_VKHDR=1` (disables the whole layer), and //! `PF_VKHDR_EXCLUDE` (comma/semicolon list of exe basenames to skip — defaults include known //! kernel-anti-cheat titles). `PF_VKHDR_LOG=1` enables a debug log in `%TEMP%\pf_vkhdr_layer.log`. +//! +//! ## Safety model +//! This cdylib runs inside someone else's process, called by the Vulkan loader. Two contract +//! sources cover every unsafe operation here: the **loader layer protocol** (negotiate struct, +//! per-layer chain links, dispatchable handles whose first word is the loader's dispatch-table +//! pointer) and **Vulkan valid-usage rules** the application must already uphold for the ICD +//! (NUL-terminated command names, valid create-info chains, count/array pairs in the two-call +//! idiom). Every `unsafe` block cites the specific clause it leans on. The layer targets x86_64 +//! only (the two hand-computed struct offsets below say so explicitly). #![allow(non_snake_case)] -#![allow(clippy::missing_safety_doc)] #![allow(clippy::too_many_arguments)] // HWND / HMONITOR etc. deliberately mirror the Win32 names. #![allow(clippy::upper_case_acronyms)] @@ -97,6 +105,14 @@ struct SurfaceFormat2Raw { p_next: *mut c_void, surface_format: vk::SurfaceFormatKHR, } +// Layout proof for the mirror: arrays of it are handed to the ICD and stamped over the caller's +// VkSurfaceFormat2KHR array, so a size or alignment drift would corrupt either side. +const _: () = assert!( + std::mem::size_of::() == std::mem::size_of::() +); +const _: () = assert!( + std::mem::align_of::() == std::mem::align_of::() +); // ---- ICD function-pointer typedefs we call down to (raw pointers, no lifetimes) ---- type FnCreateInstance = @@ -128,6 +144,9 @@ type FnCreateWin32Surface = unsafe extern "system" fn( ) -> vk::Result; type FnDestroySurface = unsafe extern "system" fn(vk::Instance, vk::SurfaceKHR, *const c_void); +// Both maps' values are raw `extern "system"` fn pointers plus plain handles; fn pointers are +// process-global code addresses and implement Send/Sync intrinsically, so the auto traits hold +// and no `unsafe impl Send` is needed (two used to sit here as unproven markers). struct InstanceData { instance: vk::Instance, next_gipa: PfnGipa, @@ -138,12 +157,10 @@ struct InstanceData { create_win32_surface: Option, destroy_surface: Option, } -unsafe impl Send for InstanceData {} struct DeviceData { next_gdpa: PfnGdpa, } -unsafe impl Send for DeviceData {} fn instances() -> &'static Mutex> { static M: OnceLock>> = OnceLock::new(); @@ -159,21 +176,47 @@ fn surface_hwnds() -> &'static Mutex> { M.get_or_init(|| Mutex::new(HashMap::new())) } -// dispatch key = first pointer word of a dispatchable handle (loader dispatch table ptr). +/// Dispatch key of a dispatchable handle. +/// +/// # Safety +/// `raw` must be the value of a **live dispatchable** Vulkan handle (`VkInstance`, +/// `VkPhysicalDevice`, `VkDevice`). The loader ABI mandates that every dispatchable object's +/// first pointer-sized word is the loader's dispatch-table pointer, which is what makes the read +/// in-bounds and initialized — and what makes it a stable per-chain key. #[inline] unsafe fn key(raw: u64) -> usize { - *(raw as usize as *const usize) + // SAFETY: per this function's contract, `raw` points at a live dispatchable object whose + // first pointer-sized word exists and is initialized (the loader wrote it at creation). + unsafe { *(raw as usize as *const usize) } } + +/// Reinterpret a function address as the loader's type-erased void-function pointer. +/// +/// # Safety +/// `p` must be the address of an `extern "system"` function. Whoever receives the returned PFN +/// must cast it back to the exact prototype of the command it was queried under before calling — +/// which is precisely what the Vulkan `*ProcAddr` contract obliges callers to do. #[inline] unsafe fn as_pfn(p: *const c_void) -> vk::PFN_vkVoidFunction { - Some(std::mem::transmute::< - *const c_void, - unsafe extern "system" fn(), - >(p)) + // SAFETY: data and function pointers have identical size and representation on all Windows + // targets, and per this function's contract `p` is a real `extern "system"` fn address. + Some(unsafe { std::mem::transmute::<*const c_void, unsafe extern "system" fn()>(p) }) } + +/// Resolve `name` down-chain and reinterpret the result as fn-pointer type `T`. +/// +/// # Safety +/// `gipa` must be a valid `vkGetInstanceProcAddr` for `inst` (or for the null instance when +/// resolving global commands), and `T` must be the exact fn-pointer prototype of the Vulkan +/// command named by `name` — `transmute_copy` erases all type checking between them. #[inline] unsafe fn resolve(gipa: PfnGipa, inst: vk::Instance, name: &CStr) -> Option { - gipa(inst, name.as_ptr()).map(|f| std::mem::transmute_copy::<_, T>(&f)) + // SAFETY: per this function's contract `gipa` is a valid loader/ICD GetInstanceProcAddr for + // `inst`, and `name` is a live NUL-terminated string for the duration of the call. + let f = unsafe { gipa(inst, name.as_ptr()) }; + // SAFETY: `f` was returned for `name`, and per this function's contract `T` is that + // command's exact prototype; both sides are fn pointers of identical size. + f.map(|f| unsafe { std::mem::transmute_copy::<_, T>(&f) }) } fn log(msg: &str) { @@ -356,21 +399,34 @@ mod hdr { const GET_ADVANCED_COLOR_INFO: i32 = 9; const MONITOR_DEFAULTTONEAREST: u32 = 2; - unsafe fn active_paths() -> Vec { + // Safe fn: every invariant below is local (out-params point at live locals / exact-length + // Vecs); callers carry no obligations. + fn active_paths() -> Vec { let (mut np, mut nm) = (0u32, 0u32); - if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm) != 0 || np == 0 { + // SAFETY: both out-pointers come from live local `u32`s that outlive the call. + if unsafe { GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm) } != 0 + || np == 0 + { return Vec::new(); } - let mut pa: Vec = vec![std::mem::zeroed(); np as usize]; - let mut ma: Vec = vec![std::mem::zeroed(); nm as usize]; - if QueryDisplayConfig( - QDC_ONLY_ACTIVE_PATHS, - &mut np, - pa.as_mut_ptr(), - &mut nm, - ma.as_mut_ptr(), - std::ptr::null_mut(), - ) != 0 + // SAFETY: PathInfo is a #[repr(C)] aggregate of integers — all-zero is a valid value. + let mut pa: Vec = vec![unsafe { std::mem::zeroed() }; np as usize]; + // SAFETY: ModeInfo is an opaque byte blob — all-zero is a valid value. + let mut ma: Vec = vec![unsafe { std::mem::zeroed() }; nm as usize]; + // SAFETY: `pa`/`ma` hold exactly `np`/`nm` elements and those counts are passed by + // pointer as the in/out capacities: QueryDisplayConfig writes at most that many entries + // (if the topology grew between the two calls it returns ERROR_INSUFFICIENT_BUFFER + // rather than writing past the end) and shrinks np/nm to what it actually wrote. + if unsafe { + QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, + &mut np, + pa.as_mut_ptr(), + &mut nm, + ma.as_mut_ptr(), + std::ptr::null_mut(), + ) + } != 0 { return Vec::new(); } @@ -378,38 +434,52 @@ mod hdr { pa } - unsafe fn target_hdr_enabled(p: &PathInfo) -> bool { - let mut ai: AdvInfo = std::mem::zeroed(); + fn target_hdr_enabled(p: &PathInfo) -> bool { + // SAFETY: AdvInfo is a #[repr(C)] aggregate of integers — all-zero is a valid value. + let mut ai: AdvInfo = unsafe { std::mem::zeroed() }; ai.header.typ = GET_ADVANCED_COLOR_INFO; ai.header.size = std::mem::size_of::() as u32; ai.header.adapter = p.tgt.adapter; ai.header.id = p.tgt.id; - if DisplayConfigGetDeviceInfo(&mut ai as *mut _ as *mut c_void) != 0 { + // SAFETY: the request header carries this struct's exact size, which is the documented + // bound for DisplayConfigGetDeviceInfo's write; `ai` is a live local across the call. + if unsafe { DisplayConfigGetDeviceInfo(&mut ai as *mut _ as *mut c_void) } != 0 { return false; } // value bitfield: bit0 advancedColorSupported, bit1 advancedColorEnabled. (ai.value & 0b10) != 0 } - unsafe fn source_gdi(p: &PathInfo) -> [u16; 32] { - let mut sn: SourceName = std::mem::zeroed(); + fn source_gdi(p: &PathInfo) -> [u16; 32] { + // SAFETY: SourceName is a #[repr(C)] aggregate of integers — all-zero is a valid value. + let mut sn: SourceName = unsafe { std::mem::zeroed() }; sn.header.typ = GET_SOURCE_NAME; sn.header.size = std::mem::size_of::() as u32; sn.header.adapter = p.src.adapter; sn.header.id = p.src.id; - let _ = DisplayConfigGetDeviceInfo(&mut sn as *mut _ as *mut c_void); + // SAFETY: the request header carries this struct's exact size, which is the documented + // bound for DisplayConfigGetDeviceInfo's write; `sn` is a live local across the call. + let _ = unsafe { DisplayConfigGetDeviceInfo(&mut sn as *mut _ as *mut c_void) }; sn.gdi } /// Is HDR (Windows advanced color) currently enabled on the display this surface lives on? /// `hwnd == 0`/unknown falls back to "any active display has HDR enabled". - pub unsafe fn enabled_for(hwnd: HWND) -> bool { + /// + /// Safe fn: `MonitorFromWindow` with `DEFAULTTONEAREST` tolerates any HWND value — including + /// a destroyed or foreign one (our map can be stale) — so callers carry no obligations. + pub fn enabled_for(hwnd: HWND) -> bool { let paths = active_paths(); if hwnd != 0 { - let mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - let mut mi: MonitorInfoExW = std::mem::zeroed(); + // SAFETY: MonitorFromWindow accepts arbitrary HWND values with DEFAULTTONEAREST + // (falling back to the nearest/primary monitor) and takes nothing by pointer. + let mon = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; + // SAFETY: MonitorInfoExW is a #[repr(C)] aggregate of integers — all-zero is valid. + let mut mi: MonitorInfoExW = unsafe { std::mem::zeroed() }; mi.cb = std::mem::size_of::() as u32; - if GetMonitorInfoW(mon, &mut mi) != 0 { + // SAFETY: `mi.cb` carries the struct's exact size, which is the documented bound for + // GetMonitorInfoW's write; `mi` is a live local across the call. + if unsafe { GetMonitorInfoW(mon, &mut mi) } != 0 { for p in &paths { if source_gdi(p) == mi.sz_device { return target_hdr_enabled(p); @@ -417,12 +487,13 @@ mod hdr { } } } - paths.iter().any(|p| target_hdr_enabled(p)) + paths.iter().any(target_hdr_enabled) } } -/// Should we inject HDR formats for this surface right now? -unsafe fn should_inject(surface: vk::SurfaceKHR) -> bool { +/// Should we inject HDR formats for this surface right now? Safe fn — the surface handle is only +/// used as a map key, and a stale/unknown HWND degrades to the any-display fallback. +fn should_inject(surface: vk::SurfaceKHR) -> bool { if !injection_allowed_for_process() { return false; } @@ -436,6 +507,12 @@ unsafe fn should_inject(surface: vk::SurfaceKHR) -> bool { // ---- entry point ---- +/// Layer negotiation entry point; the loader calls this export when it loads the DLL. +/// +/// # Safety +/// `p` must be null or point to a live `NegotiateLayerInterface` the caller has exclusive access +/// to for the duration of the call. The Vulkan loader — the only intended caller of this +/// export — guarantees exactly that for the negotiate handshake. #[no_mangle] pub unsafe extern "system" fn vkNegotiateLoaderLayerInterfaceVersion( p: *mut NegotiateLayerInterface, @@ -443,7 +520,9 @@ pub unsafe extern "system" fn vkNegotiateLoaderLayerInterfaceVersion( if p.is_null() { return vk::Result::ERROR_INITIALIZATION_FAILED; } - let s = &mut *p; + // SAFETY: `p` is non-null, and per this export's contract the loader hands us exclusive + // access to a live NegotiateLayerInterface for the duration of the call. + let s = unsafe { &mut *p }; if s.s_type != LAYER_NEGOTIATE_INTERFACE_STRUCT { return vk::Result::ERROR_INITIALIZATION_FAILED; } @@ -466,27 +545,46 @@ unsafe extern "system" fn layer_gipa( if p_name.is_null() { return None; } - match CStr::from_ptr(p_name).to_bytes() { - b"vkGetInstanceProcAddr" => as_pfn(layer_gipa as *const c_void), - b"vkGetDeviceProcAddr" => as_pfn(layer_gdpa as *const c_void), - b"vkCreateInstance" => as_pfn(create_instance as *const c_void), - b"vkDestroyInstance" => as_pfn(destroy_instance as *const c_void), - b"vkCreateDevice" => as_pfn(create_device as *const c_void), - b"vkGetPhysicalDeviceSurfaceFormatsKHR" => as_pfn(get_surface_formats as *const c_void), - b"vkGetPhysicalDeviceSurfaceFormats2KHR" => as_pfn(get_surface_formats2 as *const c_void), - b"vkCreateWin32SurfaceKHR" => as_pfn(create_win32_surface as *const c_void), - b"vkDestroySurfaceKHR" => as_pfn(destroy_surface as *const c_void), - _ => { - if instance == vk::Instance::null() { - return None; + // SAFETY: `p_name` is non-null, and vkGetInstanceProcAddr's valid-usage rules require pName + // to be a valid NUL-terminated string for the duration of the call. + let name = unsafe { CStr::from_ptr(p_name) }.to_bytes(); + // SAFETY: every arm hands `as_pfn` the address of the `extern "system"` hook this layer + // substitutes for exactly that command name; the *ProcAddr contract obliges the caller to + // cast the returned PFN back to the named command's prototype before invoking it, which is + // what makes the type-erased transmute inside `as_pfn` sound for each arm. + let hook = unsafe { + match name { + b"vkGetInstanceProcAddr" => as_pfn(layer_gipa as *const c_void), + b"vkGetDeviceProcAddr" => as_pfn(layer_gdpa as *const c_void), + b"vkCreateInstance" => as_pfn(create_instance as *const c_void), + b"vkDestroyInstance" => as_pfn(destroy_instance as *const c_void), + b"vkCreateDevice" => as_pfn(create_device as *const c_void), + b"vkGetPhysicalDeviceSurfaceFormatsKHR" => as_pfn(get_surface_formats as *const c_void), + b"vkGetPhysicalDeviceSurfaceFormats2KHR" => { + as_pfn(get_surface_formats2 as *const c_void) } - let next = { - let g = instances().lock().ok()?; - g.get(&key(instance.as_raw())).map(|d| d.next_gipa) - }; - next.and_then(|gipa| gipa(instance, p_name)) + b"vkCreateWin32SurfaceKHR" => as_pfn(create_win32_surface as *const c_void), + b"vkDestroySurfaceKHR" => as_pfn(destroy_surface as *const c_void), + _ => None, } + }; + if hook.is_some() { + return hook; } + if instance == vk::Instance::null() { + return None; + } + let next = { + let g = instances().lock().ok()?; + // SAFETY: `instance` is non-null, and vkGetInstanceProcAddr's valid-usage rules make a + // non-null instance argument a live instance handle — a dispatchable object whose first + // word is the dispatch key. + g.get(&unsafe { key(instance.as_raw()) }) + .map(|d| d.next_gipa) + }; + // SAFETY: `next` is the down-chain GetInstanceProcAddr captured from the loader's link at + // create_instance for this very chain; `p_name` is still valid NUL-terminated. + next.and_then(|gipa| unsafe { gipa(instance, p_name) }) } unsafe extern "system" fn layer_gpdpa( @@ -496,20 +594,37 @@ unsafe extern "system" fn layer_gpdpa( if p_name.is_null() { return None; } - match CStr::from_ptr(p_name).to_bytes() { - b"vkGetPhysicalDeviceSurfaceFormatsKHR" => as_pfn(get_surface_formats as *const c_void), - b"vkGetPhysicalDeviceSurfaceFormats2KHR" => as_pfn(get_surface_formats2 as *const c_void), - _ => { - if instance == vk::Instance::null() { - return None; + // SAFETY: `p_name` is non-null, and the GetPhysicalDeviceProcAddr contract mirrors + // vkGetInstanceProcAddr's: pName is a valid NUL-terminated string for the call. + let name = unsafe { CStr::from_ptr(p_name) }.to_bytes(); + // SAFETY: both arms hand `as_pfn` the address of the `extern "system"` hook this layer + // substitutes for exactly that command name; the *ProcAddr contract obliges the caller to + // cast the returned PFN back to that command's prototype before invoking it. + let hook = unsafe { + match name { + b"vkGetPhysicalDeviceSurfaceFormatsKHR" => as_pfn(get_surface_formats as *const c_void), + b"vkGetPhysicalDeviceSurfaceFormats2KHR" => { + as_pfn(get_surface_formats2 as *const c_void) } - let next = { - let g = instances().lock().ok()?; - g.get(&key(instance.as_raw())).and_then(|d| d.next_gpdpa) - }; - next.and_then(|gpdpa| gpdpa(instance, p_name)) + _ => None, } + }; + if hook.is_some() { + return hook; } + if instance == vk::Instance::null() { + return None; + } + let next = { + let g = instances().lock().ok()?; + // SAFETY: `instance` is non-null and (per the caller's contract) a live instance + // handle — a dispatchable object whose first word is the dispatch key. + g.get(&unsafe { key(instance.as_raw()) }) + .and_then(|d| d.next_gpdpa) + }; + // SAFETY: `next` is the down-chain GPDPA captured from the loader's link at create_instance + // for this very chain; `p_name` is still valid NUL-terminated. + next.and_then(|gpdpa| unsafe { gpdpa(instance, p_name) }) } unsafe extern "system" fn layer_gdpa( @@ -519,8 +634,13 @@ unsafe extern "system" fn layer_gdpa( if p_name.is_null() { return None; } - if CStr::from_ptr(p_name).to_bytes() == b"vkGetDeviceProcAddr" { - return as_pfn(layer_gdpa as *const c_void); + // SAFETY: `p_name` is non-null, and vkGetDeviceProcAddr's valid-usage rules require pName to + // be a valid NUL-terminated string for the duration of the call. + let name = unsafe { CStr::from_ptr(p_name) }.to_bytes(); + if name == b"vkGetDeviceProcAddr" { + // SAFETY: `layer_gdpa` is an `extern "system"` fn whose prototype is exactly what the + // caller will cast the returned PFN back to for this name (*ProcAddr contract). + return unsafe { as_pfn(layer_gdpa as *const c_void) }; } if device == vk::Device::null() { return None; @@ -530,23 +650,38 @@ unsafe extern "system" fn layer_gdpa( Ok(g) => g, Err(_) => return None, }; - g.get(&key(device.as_raw())).map(|d| d.next_gdpa) + // SAFETY: `device` is non-null, and vkGetDeviceProcAddr's valid-usage rules make it a + // live device handle — a dispatchable object whose first word is the dispatch key. + g.get(&unsafe { key(device.as_raw()) }).map(|d| d.next_gdpa) }; - next.and_then(|gdpa| gdpa(device, p_name)) + // SAFETY: `next` is the down-chain GetDeviceProcAddr captured from the loader's link at + // create_device for this very chain; `p_name` is still valid NUL-terminated. + next.and_then(|gdpa| unsafe { gdpa(device, p_name) }) } // ---- instance chain ---- +/// Walk `p_ci`'s pNext chain for the loader's layer-link node. +/// +/// # Safety +/// `p_ci` must point to a valid create-info struct whose `pNext` chain is a well-formed list in +/// which every node begins with the `{sType, pNext}` header — the loader builds exactly that +/// chain for the create call that invokes us. unsafe fn find_instance_link(p_ci: *const c_void) -> *mut LayerInstanceCreateInfo { - let mut node = (*(p_ci as *const BaseIn)).p_next as *const BaseIn; - while !node.is_null() { - if (*node).s_type.as_raw() == LOADER_INSTANCE_CREATE_INFO { - let lci = node as *mut LayerInstanceCreateInfo; - if (*lci).function == VK_LAYER_LINK_INFO { - return lci; + // SAFETY: per this function's contract, `p_ci` and every non-null `pNext` reached from it + // point at nodes beginning with a {sType, pNext} header; a node whose sType says it is the + // loader's instance create-info really is a LayerInstanceCreateInfo (the loader wrote it). + unsafe { + let mut node = (*(p_ci as *const BaseIn)).p_next as *const BaseIn; + while !node.is_null() { + if (*node).s_type.as_raw() == LOADER_INSTANCE_CREATE_INFO { + let lci = node as *mut LayerInstanceCreateInfo; + if (*lci).function == VK_LAYER_LINK_INFO { + return lci; + } } + node = (*node).p_next as *const BaseIn; } - node = (*node).p_next as *const BaseIn; } ptr::null_mut() } @@ -556,40 +691,67 @@ unsafe extern "system" fn create_instance( p_alloc: *const c_void, p_inst: *mut vk::Instance, ) -> vk::Result { - let lci = find_instance_link(p_ci); + // SAFETY: the loader invokes this hook with a valid VkInstanceCreateInfo whose pNext chain + // it built — the chain find_instance_link's contract requires. + let lci = unsafe { find_instance_link(p_ci) }; if lci.is_null() { return vk::Result::ERROR_INITIALIZATION_FAILED; } - let link = (*lci).u; - if link.is_null() { - return vk::Result::ERROR_INITIALIZATION_FAILED; - } - let next_gipa = (*link).next_gipa; - let next_gpdpa = (*link).next_gpdpa; - (*lci).u = (*link).p_next; + // SAFETY: `lci` is non-null and points into the loader's live chain. Its `u` link (checked + // non-null before use) is this layer's link entry; reading the down-chain pointers out of it + // and advancing `(*lci).u` to the next link is the layer protocol every layer must follow + // before calling down. + let (next_gipa, next_gpdpa) = unsafe { + let link = (*lci).u; + if link.is_null() { + return vk::Result::ERROR_INITIALIZATION_FAILED; + } + let gipa = (*link).next_gipa; + let gpdpa = (*link).next_gpdpa; + (*lci).u = (*link).p_next; + (gipa, gpdpa) + }; + // SAFETY: `next_gipa` is the loader-supplied down-chain GIPA; vkCreateInstance is a global + // command resolvable from the null instance, and FnCreateInstance mirrors its prototype. let create: FnCreateInstance = - match resolve(next_gipa, vk::Instance::null(), c"vkCreateInstance") { + match unsafe { resolve(next_gipa, vk::Instance::null(), c"vkCreateInstance") } { Some(f) => f, None => return vk::Result::ERROR_INITIALIZATION_FAILED, }; - let res = create(p_ci, p_alloc, p_inst); + // SAFETY: forwarding the loader's own arguments unchanged to the down-chain create, which + // expects exactly this (p_ci with the link advanced, the caller's allocator, the caller's + // out-pointer). + let res = unsafe { create(p_ci, p_alloc, p_inst) }; if res != vk::Result::SUCCESS { return res; } - let inst = *p_inst; - let data = InstanceData { - instance: inst, - next_gipa, - next_gpdpa, - destroy_instance: resolve(next_gipa, inst, c"vkDestroyInstance"), - get_surface_formats: resolve(next_gipa, inst, c"vkGetPhysicalDeviceSurfaceFormatsKHR"), - get_surface_formats2: resolve(next_gipa, inst, c"vkGetPhysicalDeviceSurfaceFormats2KHR"), - create_win32_surface: resolve(next_gipa, inst, c"vkCreateWin32SurfaceKHR"), - destroy_surface: resolve(next_gipa, inst, c"vkDestroySurfaceKHR"), + // SAFETY: vkCreateInstance requires pInstance to be a valid pointer, and on SUCCESS the + // down-chain just wrote the new instance handle through it. + let inst = unsafe { *p_inst }; + // SAFETY: `inst` is the live instance created above; `next_gipa` resolves its + // instance-level commands, and every Fn* typedef used here mirrors the prototype of the + // exact command name it is resolved from. + let data = unsafe { + InstanceData { + instance: inst, + next_gipa, + next_gpdpa, + destroy_instance: resolve(next_gipa, inst, c"vkDestroyInstance"), + get_surface_formats: resolve(next_gipa, inst, c"vkGetPhysicalDeviceSurfaceFormatsKHR"), + get_surface_formats2: resolve( + next_gipa, + inst, + c"vkGetPhysicalDeviceSurfaceFormats2KHR", + ), + create_win32_surface: resolve(next_gipa, inst, c"vkCreateWin32SurfaceKHR"), + destroy_surface: resolve(next_gipa, inst, c"vkDestroySurfaceKHR"), + } }; if let Ok(mut g) = instances().lock() { - g.insert(key(inst.as_raw()), data); + // SAFETY: `inst` is the live dispatchable handle created above — its first word is the + // dispatch key. + g.insert(unsafe { key(inst.as_raw()) }, data); } log("create_instance: hooked"); vk::Result::SUCCESS @@ -602,26 +764,40 @@ unsafe extern "system" fn destroy_instance(inst: vk::Instance, p_alloc: *const c let data = instances() .lock() .ok() - .and_then(|mut g| g.remove(&key(inst.as_raw()))); + // SAFETY: `inst` is non-null, and vkDestroyInstance requires a live instance handle — + // still live during this call — whose first word is the dispatch key. + .and_then(|mut g| g.remove(&unsafe { key(inst.as_raw()) })); if let Some(d) = data { if let Some(f) = d.destroy_instance { - f(inst, p_alloc); + // SAFETY: `f` is the down-chain vkDestroyInstance resolved for this very instance at + // create time; forwarding the caller's own arguments unchanged. + unsafe { f(inst, p_alloc) }; } } } // ---- device chain (pass-through; keeps device-level dispatch working) ---- +/// Walk `p_ci`'s pNext chain for the loader's device layer-link node. +/// +/// # Safety +/// Same contract as [`find_instance_link`]: `p_ci` must point to a valid create-info struct +/// whose `pNext` chain is a well-formed list of `{sType, pNext}`-headed nodes. unsafe fn find_device_link(p_ci: *const c_void) -> *mut LayerDeviceCreateInfo { - let mut node = (*(p_ci as *const BaseIn)).p_next as *const BaseIn; - while !node.is_null() { - if (*node).s_type.as_raw() == LOADER_DEVICE_CREATE_INFO { - let lci = node as *mut LayerDeviceCreateInfo; - if (*lci).function == VK_LAYER_LINK_INFO { - return lci; + // SAFETY: per this function's contract, `p_ci` and every non-null `pNext` reached from it + // point at nodes beginning with a {sType, pNext} header; a node whose sType says it is the + // loader's device create-info really is a LayerDeviceCreateInfo (the loader wrote it). + unsafe { + let mut node = (*(p_ci as *const BaseIn)).p_next as *const BaseIn; + while !node.is_null() { + if (*node).s_type.as_raw() == LOADER_DEVICE_CREATE_INFO { + let lci = node as *mut LayerDeviceCreateInfo; + if (*lci).function == VK_LAYER_LINK_INFO { + return lci; + } } + node = (*node).p_next as *const BaseIn; } - node = (*node).p_next as *const BaseIn; } ptr::null_mut() } @@ -632,35 +808,54 @@ unsafe extern "system" fn create_device( p_alloc: *const c_void, p_dev: *mut vk::Device, ) -> vk::Result { - let lci = find_device_link(p_ci); + // SAFETY: the loader invokes this hook with a valid VkDeviceCreateInfo whose pNext chain it + // built — the chain find_device_link's contract requires. + let lci = unsafe { find_device_link(p_ci) }; if lci.is_null() { return vk::Result::ERROR_INITIALIZATION_FAILED; } - let link = (*lci).u; - if link.is_null() { - return vk::Result::ERROR_INITIALIZATION_FAILED; - } - let next_gipa = (*link).next_gipa; - let next_gdpa = (*link).next_gdpa; - (*lci).u = (*link).p_next; + // SAFETY: `lci` is non-null and points into the loader's live chain. Its `u` link (checked + // non-null before use) is this layer's link entry; reading the down-chain pointers and + // advancing `(*lci).u` is the layer protocol, exactly as in create_instance. + let (next_gipa, next_gdpa) = unsafe { + let link = (*lci).u; + if link.is_null() { + return vk::Result::ERROR_INITIALIZATION_FAILED; + } + let gipa = (*link).next_gipa; + let gdpa = (*link).next_gdpa; + (*lci).u = (*link).p_next; + (gipa, gdpa) + }; let inst = instances() .lock() .ok() - .and_then(|g| g.get(&key(pdev.as_raw())).map(|d| d.instance)) + // SAFETY: vkCreateDevice requires `pdev` to be a live physical-device handle — a + // dispatchable object sharing its instance's dispatch table, so its first word is the + // same dispatch key create_instance stored. + .and_then(|g| g.get(&unsafe { key(pdev.as_raw()) }).map(|d| d.instance)) .unwrap_or(vk::Instance::null()); - let create: FnCreateDevice = match resolve(next_gipa, inst, c"vkCreateDevice") { + // SAFETY: `next_gipa` is the loader-supplied down-chain GIPA for this create call, `inst` is + // the (possibly null) instance owning `pdev`, and FnCreateDevice mirrors vkCreateDevice's + // prototype. + let create: FnCreateDevice = match unsafe { resolve(next_gipa, inst, c"vkCreateDevice") } { Some(f) => f, None => return vk::Result::ERROR_INITIALIZATION_FAILED, }; - let res = create(pdev, p_ci, p_alloc, p_dev); + // SAFETY: forwarding the loader's own arguments unchanged to the down-chain create. + let res = unsafe { create(pdev, p_ci, p_alloc, p_dev) }; if res != vk::Result::SUCCESS { return res; } - let dev = *p_dev; + // SAFETY: vkCreateDevice requires pDevice to be a valid pointer, and on SUCCESS the + // down-chain just wrote the new device handle through it. + let dev = unsafe { *p_dev }; if let Ok(mut g) = devices().lock() { - g.insert(key(dev.as_raw()), DeviceData { next_gdpa }); + // SAFETY: `dev` is the live dispatchable handle created above — its first word is the + // dispatch key. + g.insert(unsafe { key(dev.as_raw()) }, DeviceData { next_gdpa }); } vk::Result::SUCCESS } @@ -674,19 +869,28 @@ unsafe extern "system" fn create_win32_surface( p_surface: *mut vk::SurfaceKHR, ) -> vk::Result { let down = instances().lock().ok().and_then(|g| { - g.get(&key(inst.as_raw())) + // SAFETY: vkCreateWin32SurfaceKHR requires `inst` to be a live instance handle — a + // dispatchable object whose first word is the dispatch key. + g.get(&unsafe { key(inst.as_raw()) }) .and_then(|d| d.create_win32_surface) }); let down = match down { Some(f) => f, None => return vk::Result::ERROR_EXTENSION_NOT_PRESENT, }; - let res = down(inst, p_ci, p_alloc, p_surface); + // SAFETY: `down` is the down-chain vkCreateWin32SurfaceKHR resolved for this instance at + // create time; forwarding the caller's own arguments unchanged. + let res = unsafe { down(inst, p_ci, p_alloc, p_surface) }; if res == vk::Result::SUCCESS { - // VkWin32SurfaceCreateInfoKHR: sType@0, pNext@8, flags@16, hinstance@24, hwnd@32 - let hwnd = *((p_ci as *const u8).add(32) as *const isize); + // SAFETY: the down-chain call succeeded, so `p_ci` pointed at a valid + // VkWin32SurfaceCreateInfoKHR for the whole call (the ICD just consumed it). On x86_64 + // its layout is sType(4+4 pad)@0, pNext@8, flags(4+4 pad)@16, hinstance@24, hwnd@32 — + // so `p_ci + 32` is an in-bounds, 8-aligned read of the HWND field. + let hwnd = unsafe { *((p_ci as *const u8).add(32) as *const isize) }; if let Ok(mut m) = surface_hwnds().lock() { - m.insert((*p_surface).as_raw(), hwnd); + // SAFETY: vkCreateWin32SurfaceKHR requires pSurface to be a valid pointer, and on + // SUCCESS the down-chain just wrote the new surface handle through it. + m.insert(unsafe { *p_surface }.as_raw(), hwnd); } } res @@ -703,9 +907,16 @@ unsafe extern "system" fn destroy_surface( let down = instances() .lock() .ok() - .and_then(|g| g.get(&key(inst.as_raw())).and_then(|d| d.destroy_surface)); + // SAFETY: vkDestroySurfaceKHR requires `inst` to be a live instance handle — a + // dispatchable object whose first word is the dispatch key. + .and_then(|g| { + g.get(&unsafe { key(inst.as_raw()) }) + .and_then(|d| d.destroy_surface) + }); if let Some(f) = down { - f(inst, surface, p_alloc); + // SAFETY: `f` is the down-chain vkDestroySurfaceKHR resolved for this instance at + // create time; forwarding the caller's own arguments unchanged. + unsafe { f(inst, surface, p_alloc) }; } } @@ -718,7 +929,9 @@ unsafe extern "system" fn get_surface_formats( p_formats: *mut vk::SurfaceFormatKHR, ) -> vk::Result { let down = instances().lock().ok().and_then(|g| { - g.get(&key(pdev.as_raw())) + // SAFETY: vkGetPhysicalDeviceSurfaceFormatsKHR requires `pdev` to be a live + // physical-device handle — a dispatchable object whose first word is the dispatch key. + g.get(&unsafe { key(pdev.as_raw()) }) .and_then(|d| d.get_surface_formats) }); let down = match down { @@ -727,13 +940,17 @@ unsafe extern "system" fn get_surface_formats( }; let mut n = 0u32; - let r = down(pdev, surface, &mut n, ptr::null_mut()); + // SAFETY: count-query form of the two-call idiom — null pFormats plus a count out-pointer + // backed by the live local `n`; the caller's pdev/surface are forwarded unchanged. + let r = unsafe { down(pdev, surface, &mut n, ptr::null_mut()) }; if r != vk::Result::SUCCESS { return r; } let mut real = vec![vk::SurfaceFormatKHR::default(); n as usize]; if n > 0 { - let r = down(pdev, surface, &mut n, real.as_mut_ptr()); + // SAFETY: `real` holds exactly `n` elements and `n` is passed as the in/out capacity, so + // the down-chain writes at most `n` entries and shrinks `n` to what it wrote. + let r = unsafe { down(pdev, surface, &mut n, real.as_mut_ptr()) }; if r != vk::Result::SUCCESS { return r; } @@ -753,16 +970,24 @@ unsafe extern "system" fn get_surface_formats( } if p_formats.is_null() { - *p_count = aug.len() as u32; + // SAFETY: vkGetPhysicalDeviceSurfaceFormatsKHR requires pSurfaceFormatCount to be a + // valid u32 pointer in both call forms. + unsafe { *p_count = aug.len() as u32 }; return vk::Result::SUCCESS; } - let m = (*p_count as usize).min(aug.len()); - ptr::copy_nonoverlapping(aug.as_ptr(), p_formats, m); - *p_count = m as u32; - if m < aug.len() { - vk::Result::INCOMPLETE - } else { - vk::Result::SUCCESS + // SAFETY: with a non-null pSurfaceFormats the spec requires *pSurfaceFormatCount to be + // readable and pSurfaceFormats to point at at least that many elements; `m` is clamped to + // both that capacity and our own length, and the caller's buffer cannot overlap the + // freshly-allocated `aug`. + unsafe { + let m = (*p_count as usize).min(aug.len()); + ptr::copy_nonoverlapping(aug.as_ptr(), p_formats, m); + *p_count = m as u32; + if m < aug.len() { + vk::Result::INCOMPLETE + } else { + vk::Result::SUCCESS + } } } @@ -773,7 +998,9 @@ unsafe extern "system" fn get_surface_formats2( p_formats: *mut c_void, ) -> vk::Result { let down = instances().lock().ok().and_then(|g| { - g.get(&key(pdev.as_raw())) + // SAFETY: vkGetPhysicalDeviceSurfaceFormats2KHR requires `pdev` to be a live + // physical-device handle — a dispatchable object whose first word is the dispatch key. + g.get(&unsafe { key(pdev.as_raw()) }) .and_then(|d| d.get_surface_formats2) }); let down = match down { @@ -782,7 +1009,10 @@ unsafe extern "system" fn get_surface_formats2( }; let mut n = 0u32; - let r = down(pdev, p_info, &mut n, ptr::null_mut()); + // SAFETY: count-query form of the two-call idiom — null pSurfaceFormats plus a count + // out-pointer backed by the live local `n`; the caller's pdev/pSurfaceInfo are forwarded + // unchanged (the spec requires pSurfaceInfo to stay valid for the call). + let r = unsafe { down(pdev, p_info, &mut n, ptr::null_mut()) }; if r != vk::Result::SUCCESS { return r; } @@ -794,15 +1024,21 @@ unsafe extern "system" fn get_surface_formats2( }) .collect(); if n > 0 { - let r = down(pdev, p_info, &mut n, real.as_mut_ptr() as *mut c_void); + // SAFETY: `real` holds exactly `n` properly-stamped VkSurfaceFormat2KHR mirrors + // (SurfaceFormat2Raw layout-asserted at the type), and `n` is passed as the in/out + // capacity, so the down-chain writes at most `n` entries. + let r = unsafe { down(pdev, p_info, &mut n, real.as_mut_ptr() as *mut c_void) }; if r != vk::Result::SUCCESS { return r; } } real.truncate(n as usize); - // VkPhysicalDeviceSurfaceInfo2KHR: sType@0, pNext@8, surface@16 - let surface = vk::SurfaceKHR::from_raw(*((p_info as *const u8).add(16) as *const u64)); + // SAFETY: the spec requires pSurfaceInfo to point at a valid VkPhysicalDeviceSurfaceInfo2KHR + // for the whole call. On x86_64 its layout is sType(4+4 pad)@0, pNext@8, surface(u64)@16 — + // so `p_info + 16` is an in-bounds, 8-aligned read of the non-dispatchable surface handle. + let surface = + vk::SurfaceKHR::from_raw(unsafe { *((p_info as *const u8).add(16) as *const u64) }); let mut extras: Vec = Vec::new(); if !real.is_empty() && should_inject(surface) { @@ -817,25 +1053,34 @@ unsafe extern "system" fn get_surface_formats2( let total = real.len() + extras.len(); if p_formats.is_null() { - *p_count = total as u32; + // SAFETY: vkGetPhysicalDeviceSurfaceFormats2KHR requires pSurfaceFormatCount to be a + // valid u32 pointer in both call forms. + unsafe { *p_count = total as u32 }; return vk::Result::SUCCESS; } - let m = (*p_count as usize).min(total); - let out = p_formats as *mut SurfaceFormat2Raw; - for i in 0..m { - let sf = if i < real.len() { - real[i].surface_format + // SAFETY: with a non-null pSurfaceFormats the spec requires *pSurfaceFormatCount to be + // readable and pSurfaceFormats to point at at least that many VkSurfaceFormat2KHR — whose + // layout SurfaceFormat2Raw mirrors (asserted at the type). `m` is clamped to both bounds. + // Only sType and surfaceFormat are stamped per element; each element's pNext chain is left + // exactly as the caller built it. + unsafe { + let m = (*p_count as usize).min(total); + let out = p_formats as *mut SurfaceFormat2Raw; + for i in 0..m { + let sf = if i < real.len() { + real[i].surface_format + } else { + extras[i - real.len()] + }; + let dst = out.add(i); + (*dst).s_type = vk::StructureType::SURFACE_FORMAT_2_KHR; + (*dst).surface_format = sf; + } + *p_count = m as u32; + if m < total { + vk::Result::INCOMPLETE } else { - extras[i - real.len()] - }; - let dst = out.add(i); - (*dst).s_type = vk::StructureType::SURFACE_FORMAT_2_KHR; - (*dst).surface_format = sf; - } - *p_count = m as u32; - if m < total { - vk::Result::INCOMPLETE - } else { - vk::Result::SUCCESS + vk::Result::SUCCESS + } } }