palisade-errors 2.0.0

Security-conscious error handling with operational security principles
//! Local zeroization primitives.
//!
//! The crate avoids an external zeroization dependency by keeping the
//! byte-clearing logic here and routing all sensitive-memory cleanup through
//! this module.

#[cfg(test)]
use std::borrow::Cow;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr;
use std::sync::atomic::{Ordering, compiler_fence};

/// Trait for types that can scrub their sensitive contents in place.
pub(crate) trait Zeroize {
    /// Overwrite the sensitive contents of `self`.
    fn zeroize(&mut self);
}

/// Execute `Zeroize::zeroize` and suppress any panic during drop cleanup.
#[inline]
pub(crate) fn drop_zeroize<T>(value: &mut T)
where
    T: Zeroize + ?Sized,
{
    let _ = catch_unwind(AssertUnwindSafe(|| value.zeroize()));
}

/// Overwrite a raw byte region using volatile stores.
#[inline(never)]
pub(crate) unsafe fn zeroize_raw(ptr: *mut u8, len: usize) {
    for index in 0..len {
        // SAFETY: The caller guarantees that `ptr..ptr+len` is a valid,
        // writable region for the lifetime of this call.
        unsafe {
            ptr::write_volatile(ptr.add(index), 0);
        }
    }
    compiler_fence(Ordering::SeqCst);
}

/// Overwrite a mutable byte slice using volatile stores.
#[inline(never)]
pub(crate) fn zeroize_bytes(bytes: &mut [u8]) {
    // SAFETY: `bytes` is a valid writable region owned by the caller.
    unsafe {
        zeroize_raw(bytes.as_mut_ptr(), bytes.len());
    }
}

/// Test-only helper: overwrite a `String`'s backing buffer and then clear its
/// visible length.
#[cfg(test)]
#[inline(never)]
pub(crate) fn zeroize_string(s: &mut String) {
    // SAFETY: `String` guarantees its pointer is valid for `len` writable bytes.
    unsafe {
        zeroize_raw(s.as_mut_ptr(), s.len());
    }
    s.clear();
}

#[cfg(test)]
impl Zeroize for String {
    fn zeroize(&mut self) {
        zeroize_string(self);
    }
}

#[cfg(test)]
impl Zeroize for Cow<'static, str> {
    fn zeroize(&mut self) {
        if let Self::Owned(value) = self {
            value.zeroize();
        }
    }
}

impl<T> Zeroize for Option<T>
where
    T: Zeroize,
{
    fn zeroize(&mut self) {
        if let Some(value) = self {
            value.zeroize();
        }
    }
}

#[cfg(test)]
impl Zeroize for Vec<u8> {
    fn zeroize(&mut self) {
        zeroize_bytes(self.as_mut_slice());
        self.clear();
    }
}

impl Zeroize for [u8] {
    fn zeroize(&mut self) {
        zeroize_bytes(self);
    }
}

impl<const N: usize> Zeroize for [u8; N] {
    fn zeroize(&mut self) {
        zeroize_bytes(self.as_mut_slice());
    }
}

impl Zeroize for u8 {
    fn zeroize(&mut self) {
        // SAFETY: `self` is a valid writable byte for the duration of the call.
        unsafe {
            ptr::write_volatile(self, 0);
        }
        compiler_fence(Ordering::SeqCst);
    }
}

impl Zeroize for bool {
    fn zeroize(&mut self) {
        // SAFETY: `self` is a valid writable byte for the duration of the call.
        unsafe {
            ptr::write_volatile(self, false);
        }
        compiler_fence(Ordering::SeqCst);
    }
}

macro_rules! impl_zeroize_int {
    ($($ty:ty),+ $(,)?) => {
        $(
            impl Zeroize for $ty {
                fn zeroize(&mut self) {
                    // SAFETY: `self` is a valid writable integer for the duration of the call.
                    unsafe {
                        ptr::write_volatile(self, 0);
                    }
                    compiler_fence(Ordering::SeqCst);
                }
            }
        )+
    };
}

impl_zeroize_int!(u16, u32, u64, usize);

#[cfg(test)]
mod tests {
    use super::Zeroize;
    use std::borrow::Cow;

    #[test]
    fn string_zeroize_clears_length() {
        let mut value = String::from("sensitive");
        value.zeroize();
        assert!(value.is_empty());
    }

    #[test]
    fn array_zeroize_clears_bytes() {
        let mut value = [0xAB_u8; 8];
        value.zeroize();
        assert_eq!(value, [0u8; 8]);
    }

    #[test]
    fn cow_owned_zeroize_clears_contents() {
        let mut value: Cow<'static, str> = Cow::Owned(String::from("secret"));
        value.zeroize();
        assert_eq!(value.as_ref(), "");
    }
}