diff --git a/zeroize/Cargo.toml b/zeroize/Cargo.toml index 29d6cd99..4ffe23ee 100644 --- a/zeroize/Cargo.toml +++ b/zeroize/Cargo.toml @@ -24,6 +24,7 @@ default = ["alloc"] aarch64 = [] alloc = [] derive = ["zeroize_derive"] +std = [] [package.metadata.docs.rs] all-features = true diff --git a/zeroize/src/lib.rs b/zeroize/src/lib.rs index 5006fb61..4e1bf221 100644 --- a/zeroize/src/lib.rs +++ b/zeroize/src/lib.rs @@ -63,6 +63,11 @@ //! but ensures the backing memory is securely zeroed with some caveats. //! (NOTE: see "Stack/Heap Zeroing Notes" for important `Vec`/`String` details) //! +//! With the `std` feature enabled (which it is **not** by default), [`Zeroize`] +//! is also implemented for [`CString`]. Due to its requirement of containing no +//! null bytes, calling `zeroize()` on a `CString` will drop it after zeroing the +//! memory. +//! //! The [`DefaultIsZeroes`] marker trait can be impl'd on types which also //! impl [`Default`], which implements [`Zeroize`] by overwriting a value with //! the default value. @@ -233,6 +238,9 @@ #[cfg_attr(test, macro_use)] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + #[cfg(feature = "zeroize_derive")] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize_derive")))] pub use zeroize_derive::{Zeroize, ZeroizeOnDrop}; @@ -253,6 +261,9 @@ use core::{ops, ptr, slice::IterMut, sync::atomic}; #[cfg(feature = "alloc")] use alloc::{boxed::Box, string::String, vec::Vec}; +#[cfg(feature = "std")] +use std::ffi::CString; + /// Trait for securely erasing types from memory pub trait Zeroize { /// Zero out this object from memory using Rust intrinsics which ensure the @@ -511,6 +522,20 @@ impl Zeroize for String { } } +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl Zeroize for CString { + fn zeroize(&mut self) { + // - mem::take uses replace internally to swap the pointer + let this = std::mem::take(self); + // - CString::into_bytes calls ::into_vec which takes ownership of the heap pointer + // as a Vec + // - Calling .zeroize() on the resulting vector clears out the bytes + // From: https://github.com/RustCrypto/utils/pull/759#issuecomment-1087976570 + this.into_bytes().zeroize(); + } +} + /// Fallible trait for representing cases where zeroization may or may not be /// possible. /// @@ -700,6 +725,9 @@ mod tests { #[cfg(feature = "alloc")] use alloc::boxed::Box; + #[cfg(feature = "std")] + use std::ffi::CString; + #[derive(Clone, Debug, PartialEq)] struct ZeroizedOnDrop(u64); @@ -858,6 +886,28 @@ mod tests { assert!(as_vec.iter().all(|byte| *byte == 0)); } + #[cfg(feature = "std")] + #[test] + fn zeroize_c_string() { + let mut cstring = CString::new("Hello, world!").expect("CString::new failed"); + let clone = cstring.clone(); + let orig_len = cstring.as_bytes().len(); + let orig_ptr = cstring.as_bytes().as_ptr(); + cstring.zeroize(); + // This doesn't quite test that the original memory has been cleared, but only that + // cstring now owns an empty buffer + assert!(cstring.into_bytes().is_empty()); + unsafe { + // DEFINITELY UB! We reconstruct the original buffer which should be deallocated after + // the .zeroize() + let orig_buf = std::slice::from_raw_parts(orig_ptr, orig_len); + // Check that the original memory is different from what it was originally. It is not + // possible to reliably check if it is zeroed, as other objects might be allocated + // in the freed memory + assert_ne!(orig_buf, clone.as_bytes()); + } + } + #[cfg(feature = "alloc")] #[test] fn zeroize_box() {