#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_root_url = "https://docs.rs/zeroize/1.4.3")]
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
#[cfg(feature = "alloc")]
#[cfg_attr(test, macro_use)]
extern crate alloc;
#[cfg(feature = "zeroize_derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "zeroize_derive")))]
pub use zeroize_derive::Zeroize;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86;
use core::mem::{self, MaybeUninit};
use core::num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
};
use core::{ops, ptr, slice::IterMut, sync::atomic};
#[cfg(feature = "alloc")]
use alloc::{boxed::Box, string::String, vec::Vec};
pub trait Zeroize {
fn zeroize(&mut self);
}
pub trait DefaultIsZeroes: Copy + Default + Sized {}
impl<Z> Zeroize for Z
where
Z: DefaultIsZeroes,
{
fn zeroize(&mut self) {
volatile_write(self, Z::default());
atomic_fence();
}
}
macro_rules! impl_zeroize_with_default {
($($type:ty),+) => {
$(impl DefaultIsZeroes for $type {})+
};
}
impl_zeroize_with_default!(i8, i16, i32, i64, i128, isize);
impl_zeroize_with_default!(u8, u16, u32, u64, u128, usize);
impl_zeroize_with_default!(f32, f64, char, bool);
macro_rules! impl_zeroize_for_non_zero {
($($type:ty),+) => {
$(impl Zeroize for $type
{
fn zeroize(&mut self) {
volatile_write(self, unsafe { <$type>::new_unchecked(1) });
atomic_fence();
}
})+
};
}
impl_zeroize_for_non_zero!(
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize
);
impl_zeroize_for_non_zero!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
impl<Z, const N: usize> Zeroize for [Z; N]
where
Z: Zeroize,
{
fn zeroize(&mut self) {
self.iter_mut().zeroize();
}
}
impl<'a, Z> Zeroize for IterMut<'a, Z>
where
Z: Zeroize,
{
fn zeroize(&mut self) {
for elem in self {
elem.zeroize();
}
}
}
impl<Z> Zeroize for Option<Z>
where
Z: Zeroize,
{
fn zeroize(&mut self) {
if let Some(value) = self {
value.zeroize();
self.take();
}
unsafe {
volatile_set(self as *mut _ as *mut u8, 0, mem::size_of::<Self>());
}
unsafe { ptr::write_volatile(self, Option::default()) }
atomic_fence();
}
}
impl<Z> Zeroize for [MaybeUninit<Z>] {
fn zeroize(&mut self) {
let ptr = self.as_mut_ptr() as *mut MaybeUninit<u8>;
let size = self.len().checked_mul(mem::size_of::<Z>()).unwrap();
assert!(size <= core::isize::MAX as usize);
unsafe { volatile_set(ptr, MaybeUninit::new(0), size) }
atomic_fence();
}
}
impl<Z> Zeroize for [Z]
where
Z: DefaultIsZeroes,
{
fn zeroize(&mut self) {
assert!(self.len() <= core::isize::MAX as usize);
unsafe { volatile_set(self.as_mut_ptr(), Z::default(), self.len()) };
atomic_fence();
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<Z> Zeroize for Vec<Z>
where
Z: Zeroize,
{
fn zeroize(&mut self) {
use core::slice;
self.iter_mut().zeroize();
self.clear();
let uninit_slice = unsafe {
slice::from_raw_parts_mut(self.as_mut_ptr() as *mut MaybeUninit<Z>, self.capacity())
};
uninit_slice.zeroize();
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<Z> Zeroize for Box<[Z]>
where
Z: Zeroize,
{
fn zeroize(&mut self) {
self.iter_mut().zeroize();
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Zeroize for String {
fn zeroize(&mut self) {
unsafe { self.as_mut_vec() }.zeroize();
}
}
pub trait TryZeroize {
#[must_use]
fn try_zeroize(&mut self) -> bool;
}
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Zeroizing<Z: Zeroize>(Z);
impl<Z> Zeroizing<Z>
where
Z: Zeroize,
{
pub fn new(value: Z) -> Self {
value.into()
}
}
impl<Z: Zeroize + Clone> Clone for Zeroizing<Z> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
fn clone_from(&mut self, source: &Self) {
self.0.zeroize();
self.0.clone_from(&source.0);
}
}
impl<Z> From<Z> for Zeroizing<Z>
where
Z: Zeroize,
{
fn from(value: Z) -> Zeroizing<Z> {
Zeroizing(value)
}
}
impl<Z> ops::Deref for Zeroizing<Z>
where
Z: Zeroize,
{
type Target = Z;
fn deref(&self) -> &Z {
&self.0
}
}
impl<Z> ops::DerefMut for Zeroizing<Z>
where
Z: Zeroize,
{
fn deref_mut(&mut self) -> &mut Z {
&mut self.0
}
}
impl<Z> Zeroize for Zeroizing<Z>
where
Z: Zeroize,
{
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<Z> Drop for Zeroizing<Z>
where
Z: Zeroize,
{
fn drop(&mut self) {
self.0.zeroize()
}
}
#[inline]
fn atomic_fence() {
atomic::compiler_fence(atomic::Ordering::SeqCst);
}
#[inline]
fn volatile_write<T: Copy + Sized>(dst: &mut T, src: T) {
unsafe { ptr::write_volatile(dst, src) }
}
#[inline]
unsafe fn volatile_set<T: Copy + Sized>(dst: *mut T, src: T, count: usize) {
for i in 0..count {
let ptr = dst.add(i);
ptr::write_volatile(ptr, src);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[test]
fn non_zero() {
macro_rules! non_zero_test {
($($type:ty),+) => {
$(let mut value = <$type>::new(42).unwrap();
value.zeroize();
assert_eq!(value.get(), 1);)+
};
}
non_zero_test!(
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize
);
non_zero_test!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
}
#[test]
fn zeroize_byte_arrays() {
let mut arr = [42u8; 137];
arr.zeroize();
assert_eq!(arr.as_ref(), [0u8; 137].as_ref());
}
#[test]
fn zeroize_maybeuninit_byte_arrays() {
let mut arr = [MaybeUninit::new(42u64); 64];
arr.zeroize();
let arr_init: [u64; 64] = unsafe { core::mem::transmute(arr) };
assert_eq!(arr_init, [0u64; 64]);
}
#[cfg(feature = "alloc")]
#[test]
fn zeroize_vec() {
let mut vec = vec![42; 3];
vec.zeroize();
assert!(vec.is_empty());
}
#[cfg(feature = "alloc")]
#[test]
fn zeroize_vec_entire_capacity() {
#[derive(Clone)]
struct PanicOnNonZeroDrop(u64);
impl Zeroize for PanicOnNonZeroDrop {
fn zeroize(&mut self) {
self.0 = 0;
}
}
impl Drop for PanicOnNonZeroDrop {
fn drop(&mut self) {
if self.0 != 0 {
panic!("dropped non-zeroized data");
}
}
}
let mut vec = vec![PanicOnNonZeroDrop(42); 2];
unsafe {
vec.set_len(1);
}
vec.zeroize();
unsafe {
vec.set_len(2);
}
drop(vec);
}
#[cfg(feature = "alloc")]
#[test]
fn zeroize_string() {
let mut string = String::from("Hello, world!");
string.zeroize();
assert!(string.is_empty());
}
#[cfg(feature = "alloc")]
#[test]
fn zeroize_string_entire_capacity() {
let mut string = String::from("Hello, world!");
string.truncate(5);
string.zeroize();
let mut as_vec = string.into_bytes();
unsafe { as_vec.set_len(as_vec.capacity()) };
assert!(as_vec.iter().all(|byte| *byte == 0));
}
#[cfg(feature = "alloc")]
#[test]
fn zeroize_box() {
let mut boxed_arr = Box::new([42u8; 3]);
boxed_arr.zeroize();
assert_eq!(boxed_arr.as_ref(), &[0u8; 3]);
}
}