@@ -45,7 +45,7 @@ use std::ffi::c_void;
4545use std:: ptr;
4646use std:: sync:: OnceLock ;
4747
48- use windows:: Win32 :: System :: ErrorReporting :: { WerRegisterExcludedMemoryBlock , WerUnregisterExcludedMemoryBlock } ;
48+ use windows:: Win32 :: System :: LibraryLoader :: { GetProcAddress , LOAD_LIBRARY_SEARCH_SYSTEM32 , LoadLibraryExW } ;
4949use windows:: Win32 :: System :: Memory :: {
5050 MEM_COMMIT , MEM_RELEASE , MEM_RESERVE , PAGE_NOACCESS , PAGE_PROTECTION_FLAGS , PAGE_READONLY , PAGE_READWRITE ,
5151 VirtualAlloc , VirtualFree , VirtualLock , VirtualProtect , VirtualUnlock ,
@@ -54,6 +54,49 @@ use windows::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
5454
5555use crate :: ProtectionStatus ;
5656
57+ /// Function pointer type for `WerRegisterExcludedMemoryBlock`.
58+ type WerRegisterFn = unsafe extern "system" fn ( * const c_void , u32 ) -> windows:: core:: HRESULT ;
59+
60+ /// Function pointer type for `WerUnregisterExcludedMemoryBlock`.
61+ type WerUnregisterFn = unsafe extern "system" fn ( * const c_void ) -> windows:: core:: HRESULT ;
62+
63+ /// Returns the `WerRegisterExcludedMemoryBlock` function pointer if available on this OS.
64+ ///
65+ /// `WerRegisterExcludedMemoryBlock` is not available on all Windows Server 2016 builds
66+ /// (NT 10.0.14393) — it was only backported via later servicing updates.
67+ /// We resolve it dynamically at runtime so the binary loads on any Windows 10/Server 2016
68+ /// build, gracefully degrading to no WER exclusion when the function is absent.
69+ fn wer_register_fn ( ) -> Option < WerRegisterFn > {
70+ static FN : OnceLock < Option < WerRegisterFn > > = OnceLock :: new ( ) ;
71+ * FN . get_or_init ( || {
72+ // SAFETY: `LoadLibraryExW` with `LOAD_LIBRARY_SEARCH_SYSTEM32` is always safe to call.
73+ // The handle is intentionally not freed: `wer.dll` is a system DLL that must
74+ // remain loaded for as long as the cached function pointer is in use.
75+ let hmod = unsafe { LoadLibraryExW ( windows:: core:: w!( "wer.dll" ) , None , LOAD_LIBRARY_SEARCH_SYSTEM32 ) } . ok ( ) ?;
76+ // SAFETY: `hmod` is a valid module handle; the function name is a null-terminated C string.
77+ let proc = unsafe { GetProcAddress ( hmod, windows:: core:: s!( "WerRegisterExcludedMemoryBlock" ) ) } ?;
78+ // SAFETY: `proc` is the function pointer for `WerRegisterExcludedMemoryBlock`,
79+ // which has signature `fn(*const c_void, u32) -> HRESULT`.
80+ Some ( unsafe { std:: mem:: transmute :: < unsafe extern "system" fn ( ) -> isize , WerRegisterFn > ( proc) } )
81+ } )
82+ }
83+
84+ /// Returns the `WerUnregisterExcludedMemoryBlock` function pointer if available on this OS.
85+ fn wer_unregister_fn ( ) -> Option < WerUnregisterFn > {
86+ static FN : OnceLock < Option < WerUnregisterFn > > = OnceLock :: new ( ) ;
87+ * FN . get_or_init ( || {
88+ // SAFETY: `LoadLibraryExW` with `LOAD_LIBRARY_SEARCH_SYSTEM32` is always safe to call.
89+ // The handle is intentionally not freed: `wer.dll` is a system DLL that must
90+ // remain loaded for as long as the cached function pointer is in use.
91+ let hmod = unsafe { LoadLibraryExW ( windows:: core:: w!( "wer.dll" ) , None , LOAD_LIBRARY_SEARCH_SYSTEM32 ) } . ok ( ) ?;
92+ // SAFETY: `hmod` is a valid module handle; the function name is a null-terminated C string.
93+ let proc = unsafe { GetProcAddress ( hmod, windows:: core:: s!( "WerUnregisterExcludedMemoryBlock" ) ) } ?;
94+ // SAFETY: `proc` is the function pointer for `WerUnregisterExcludedMemoryBlock`,
95+ // which has signature `fn(*const c_void) -> HRESULT`.
96+ Some ( unsafe { std:: mem:: transmute :: < unsafe extern "system" fn ( ) -> isize , WerUnregisterFn > ( proc) } )
97+ } )
98+ }
99+
57100/// Page-based secure allocation for Windows.
58101pub ( crate ) struct SecureAlloc < const N : usize > {
59102 /// Start of the entire 3-page `VirtualAlloc` region (the first guard page).
@@ -148,18 +191,30 @@ impl<const N: usize> SecureAlloc<N> {
148191 // Registration covers the full page, not just N bytes, because the
149192 // allocation model is page-based.
150193 //
151- // SAFETY: `data` is a valid, committed, page-aligned pointer; `ps` is
152- // exactly one page — the size passed to `VirtualAlloc`.
153- let wer_hr = unsafe {
154- WerRegisterExcludedMemoryBlock ( data. cast :: < c_void > ( ) , u32:: try_from ( ps) . expect ( "page size fits in u32" ) )
194+ // `WerRegisterExcludedMemoryBlock` is resolved dynamically: it is absent on
195+ // some Windows Server 2016 (NT 10.0.14393) builds, and loading it statically
196+ // would prevent the binary from starting on those hosts.
197+ let wer_excluded = match wer_register_fn ( ) {
198+ Some ( func) => {
199+ // SAFETY: `data` is a valid, committed, page-aligned pointer; `ps` is
200+ // exactly one page — the size passed to `VirtualAlloc`.
201+ let wer_hr = unsafe { func ( data. cast :: < c_void > ( ) , u32:: try_from ( ps) . expect ( "page size fits in u32" ) ) } ;
202+ if wer_hr. is_err ( ) {
203+ tracing:: debug!(
204+ "secure-memory: WerRegisterExcludedMemoryBlock failed ({wer_hr:?}); \
205+ the data page will not be excluded from WER crash reports"
206+ ) ;
207+ }
208+ wer_hr. is_ok ( )
209+ }
210+ None => {
211+ tracing:: debug!(
212+ "secure-memory: WerRegisterExcludedMemoryBlock not available on this Windows version; \
213+ the data page will not be excluded from WER crash reports"
214+ ) ;
215+ false
216+ }
155217 } ;
156- let wer_excluded = wer_hr. is_ok ( ) ;
157- if !wer_excluded {
158- tracing:: debug!(
159- "secure-memory: WerRegisterExcludedMemoryBlock failed ({wer_hr:?}); \
160- the data page will not be excluded from WER crash reports"
161- ) ;
162- }
163218
164219 // ── Copy secret into the data page ──────────────────────────────────
165220 // SAFETY: `src` (caller stack) and `data` (VirtualAlloc region) are
@@ -229,10 +284,12 @@ impl<const N: usize> Drop for SecureAlloc<N> {
229284
230285 // Unregister WER exclusion before freeing the page.
231286 // Must happen before `VirtualFree` to avoid a dangling registration.
232- if self . wer_excluded {
287+ if self . wer_excluded
288+ && let Some ( func) = wer_unregister_fn ( )
289+ {
233290 // SAFETY: `self.data` is the same pointer passed to
234291 // `WerRegisterExcludedMemoryBlock`; still valid here.
235- let _ = unsafe { WerUnregisterExcludedMemoryBlock ( self . data . cast :: < c_void > ( ) ) } ;
292+ let _ = unsafe { func ( self . data . cast :: < c_void > ( ) ) } ;
236293 }
237294
238295 // Release the entire three-page region.
0 commit comments