From 4d4a7372a684b9c455eaae499e3385bfbd25515f Mon Sep 17 00:00:00 2001 From: qtcqtc <236572877+qtcqtc@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:07:06 +0800 Subject: [PATCH] Avoid panicking when Windows memory is already unlocked --- src/lib.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 682e990..bc766cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,9 +101,12 @@ impl Drop for SecretBox { #[cfg(windows)] unsafe { - if windows_sys::Win32::System::Memory::VirtualUnlock(secret_ptr.cast(), len) == 0 { - panic!("VirtualUnlock failed",); - } + // VirtualLock does not maintain a lock count for overlapping pages. + // Heap allocations commonly share pages, so dropping one SecretBox + // can unlock a page that still contains another SecretBox. A later + // VirtualUnlock then reports ERROR_NOT_LOCKED. Drop must still + // zeroize the secret and must never panic while unwinding. + let _ = windows_sys::Win32::System::Memory::VirtualUnlock(secret_ptr.cast(), len); } self.zeroize() @@ -363,6 +366,16 @@ mod tests { assert!(TestSecret::default().check_zero()); } + #[cfg(windows)] + #[test] + fn dropping_multiple_secrets_does_not_panic() { + let secrets = (0..64) + .map(|index| SecretString::new(Box::new(format!("secret-{index}")))) + .collect::>(); + + drop(secrets); + } + #[test] fn test_secret_box_expose_secret_mut() { let secret = Box::new(String::from("Encrypted"));