From 1490c2cc0052d356177063795ba03e3c3748e40e Mon Sep 17 00:00:00 2001 From: David Palm Date: Mon, 5 May 2025 15:52:05 +0200 Subject: [PATCH 1/2] Add unsafe test to check that dropped values are zeroized --- zeroize/Cargo.toml | 6 +++-- zeroize/tests/zeroize.rs | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/zeroize/Cargo.toml b/zeroize/Cargo.toml index 16db9cdc..f8aabab7 100644 --- a/zeroize/Cargo.toml +++ b/zeroize/Cargo.toml @@ -27,9 +27,11 @@ default = ["alloc"] alloc = [] std = ["alloc"] -aarch64 = [] # NOTE: vestigial no-op feature; AArch64 support is always enabled now +aarch64 = [] # NOTE: vestigial no-op feature; AArch64 support is always enabled now derive = ["zeroize_derive"] -simd = [] # NOTE: MSRV 1.72 +simd = [] # NOTE: MSRV 1.72 + +test-allocator = ["std"] [package.metadata.docs.rs] all-features = true diff --git a/zeroize/tests/zeroize.rs b/zeroize/tests/zeroize.rs index afc824e0..6cad1907 100644 --- a/zeroize/tests/zeroize.rs +++ b/zeroize/tests/zeroize.rs @@ -208,3 +208,60 @@ fn asref() { let _asmut: &mut [u8] = buffer.as_mut(); let _asref: &[u8] = buffer.as_ref(); } + +#[cfg(not(miri))] +#[cfg(feature = "test-allocator")] +mod zeroization_with_custom_allocator { + use super::*; + use core::ptr; + use std::alloc::{GlobalAlloc, Layout, System}; + // Allocator that leaks all memory it allocates, thus leaving the memory open for inspection. + struct UnfreeAllocator; + unsafe impl GlobalAlloc for UnfreeAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // Do nothing, leak memory + let _ = (ptr, layout); + } + } + + #[global_allocator] + static UNFREE_ALLOCATOR: UnfreeAllocator = UnfreeAllocator; + + #[test] + #[allow(unsafe_code, unused_assignments)] + fn clears_memory_when_scope_ends() { + struct SecretBox(Box); + impl Drop for SecretBox { + fn drop(&mut self) { + self.0.as_mut().zeroize() + } + } + + let mut ptr: *const u128 = ptr::null(); + + unsafe { + { + let secret = SecretBox(Box::new(0xdeadbeef_u128)); + let boxptr = &secret as *const SecretBox; + let boxptr = boxptr as *const *const u128; + ptr = *boxptr; + assert!(!ptr.is_null(), "ptr is null before drop, not ok"); + let bytes: &[u8] = core::slice::from_raw_parts(ptr as *const u8, size_of::()); + assert!( + !bytes.iter().all(|&b| b == 0), + "Expected non-zero data, instead found 0s: {:X?}", + bytes + ); + } + // Check that the memory is cleared after the scope ends + for _ in 0..size_of::() { + // This is UB but proooobably fine given the leaking allocator. + let byte = *(ptr as *const u8).add(1); + assert_eq!(byte, 0); + } + } + } +} From b08a52532f1a542cff20d919a958cb0727904a67 Mon Sep 17 00:00:00 2001 From: David Palm Date: Tue, 6 May 2025 11:22:59 +0200 Subject: [PATCH 2/2] Remove feature, move test to own file --- zeroize/Cargo.toml | 2 -- zeroize/tests/unsafe_tests.rs | 56 ++++++++++++++++++++++++++++++++++ zeroize/tests/zeroize.rs | 57 ----------------------------------- 3 files changed, 56 insertions(+), 59 deletions(-) create mode 100644 zeroize/tests/unsafe_tests.rs diff --git a/zeroize/Cargo.toml b/zeroize/Cargo.toml index f8aabab7..9430829f 100644 --- a/zeroize/Cargo.toml +++ b/zeroize/Cargo.toml @@ -31,7 +31,5 @@ aarch64 = [] # NOTE: vestigial no-op feature; AArch64 support is derive = ["zeroize_derive"] simd = [] # NOTE: MSRV 1.72 -test-allocator = ["std"] - [package.metadata.docs.rs] all-features = true diff --git a/zeroize/tests/unsafe_tests.rs b/zeroize/tests/unsafe_tests.rs new file mode 100644 index 00000000..7f25d4bd --- /dev/null +++ b/zeroize/tests/unsafe_tests.rs @@ -0,0 +1,56 @@ +#![cfg(not(miri))] +use core::ptr; +use std::alloc::{GlobalAlloc, Layout, System}; + +use zeroize::Zeroize; + +// Allocator that leaks all memory it allocates, thus leaving the memory open for inspection. +struct UnfreeAllocator; +unsafe impl GlobalAlloc for UnfreeAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // Do nothing, leak memory + eprintln!("Leaking memory: {:?}", ptr); + let _ = (ptr, layout); + } +} + +#[global_allocator] +static UNFREE_ALLOCATOR: UnfreeAllocator = UnfreeAllocator; + +#[test] +#[allow(unsafe_code, unused_assignments)] +fn clears_memory_when_scope_ends() { + struct SecretBox(Box); + impl Drop for SecretBox { + fn drop(&mut self) { + self.0.as_mut().zeroize() + } + } + + let mut ptr: *const u128 = ptr::null(); + + unsafe { + { + let secret = SecretBox(Box::new(0xdeadbeef_u128)); + let boxptr = &secret as *const SecretBox; + let boxptr = boxptr as *const *const u128; + ptr = *boxptr; + assert!(!ptr.is_null(), "ptr is null before drop, not ok"); + let bytes: &[u8] = core::slice::from_raw_parts(ptr as *const u8, size_of::()); + assert!( + !bytes.iter().all(|&b| b == 0), + "Expected non-zero data, instead found 0s: {:X?}", + bytes + ); + } + // Check that the memory is cleared after the scope ends + for _ in 0..size_of::() { + // This is UB but proooobably fine given the leaking allocator. + let byte = *(ptr as *const u8).add(1); + assert_eq!(byte, 0); + } + } +} diff --git a/zeroize/tests/zeroize.rs b/zeroize/tests/zeroize.rs index 6cad1907..afc824e0 100644 --- a/zeroize/tests/zeroize.rs +++ b/zeroize/tests/zeroize.rs @@ -208,60 +208,3 @@ fn asref() { let _asmut: &mut [u8] = buffer.as_mut(); let _asref: &[u8] = buffer.as_ref(); } - -#[cfg(not(miri))] -#[cfg(feature = "test-allocator")] -mod zeroization_with_custom_allocator { - use super::*; - use core::ptr; - use std::alloc::{GlobalAlloc, Layout, System}; - // Allocator that leaks all memory it allocates, thus leaving the memory open for inspection. - struct UnfreeAllocator; - unsafe impl GlobalAlloc for UnfreeAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - unsafe { System.alloc(layout) } - } - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - // Do nothing, leak memory - let _ = (ptr, layout); - } - } - - #[global_allocator] - static UNFREE_ALLOCATOR: UnfreeAllocator = UnfreeAllocator; - - #[test] - #[allow(unsafe_code, unused_assignments)] - fn clears_memory_when_scope_ends() { - struct SecretBox(Box); - impl Drop for SecretBox { - fn drop(&mut self) { - self.0.as_mut().zeroize() - } - } - - let mut ptr: *const u128 = ptr::null(); - - unsafe { - { - let secret = SecretBox(Box::new(0xdeadbeef_u128)); - let boxptr = &secret as *const SecretBox; - let boxptr = boxptr as *const *const u128; - ptr = *boxptr; - assert!(!ptr.is_null(), "ptr is null before drop, not ok"); - let bytes: &[u8] = core::slice::from_raw_parts(ptr as *const u8, size_of::()); - assert!( - !bytes.iter().all(|&b| b == 0), - "Expected non-zero data, instead found 0s: {:X?}", - bytes - ); - } - // Check that the memory is cleared after the scope ends - for _ in 0..size_of::() { - // This is UB but proooobably fine given the leaking allocator. - let byte = *(ptr as *const u8).add(1); - assert_eq!(byte, 0); - } - } - } -}