syd 3.58.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/zeroize.rs: Safe interface to explicit_bzero(3)
//
// Copyright (c) 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

//! Safe interface to explicit_bzero(3)

use std::{
    mem::{size_of, size_of_val, MaybeUninit},
    ops::{Deref, DerefMut},
    ptr,
};

use libc::{c_void, explicit_bzero};

/// Securely zero memory.
pub trait Zeroize {
    /// Overwrite `self` with zeros using explicit_bzero(3).
    fn zeroize(&mut self);
}

/// Marker trait asserting that a type zeroizes itself on `Drop`.
pub trait ZeroizeOnDrop {}

/// Marker trait for types whose all-zero byte pattern equals their default.
pub trait DefaultIsZeroes: Copy + Default + Sized {}

impl<Z: DefaultIsZeroes> Zeroize for Z {
    fn zeroize(&mut self) {
        // SAFETY:
        // 1. "ptr::from_mut(self)" points to "self", which owns "size_of::<Z>()" bytes.
        // 2. "self" is a unique mutable borrow, so bytes are valid to overwrite.
        unsafe { explicit_bzero(ptr::from_mut(self).cast::<c_void>(), size_of::<Z>()) };
    }
}

macro_rules! impl_default_is_zeroes {
    ($($ty:ty),+ $(,)?) => {
        $(impl DefaultIsZeroes for $ty {})+
    };
}

impl_default_is_zeroes! {
    (), bool, char,
    f32, f64,
    i8, i16, i32, i64, i128, isize,
    u8, u16, u32, u64, u128, usize,
}

impl<Z: DefaultIsZeroes> Zeroize for [Z] {
    fn zeroize(&mut self) {
        // SAFETY:
        // 1. "self.as_mut_ptr()" points to slice's "size_of_val(self)" bytes.
        // 2. "self" is a unique mutable borrow, so bytes are valid to overwrite.
        unsafe { explicit_bzero(self.as_mut_ptr().cast::<c_void>(), size_of_val(self)) };
    }
}

impl<Z> Zeroize for MaybeUninit<Z> {
    fn zeroize(&mut self) {
        // SAFETY:
        // 1. "ptr::from_mut(self)" points to "self", which owns "size_of::<Z>()" bytes.
        // 2. "self" is a unique mutable borrow, so bytes are valid to overwrite.
        unsafe { explicit_bzero(ptr::from_mut(self).cast::<c_void>(), size_of::<Z>()) };
    }
}

impl<Z> Zeroize for [MaybeUninit<Z>] {
    fn zeroize(&mut self) {
        // SAFETY:
        // 1. "self.as_mut_ptr()" points to slice's "size_of_val(self)" bytes.
        // 2. "self" is a unique mutable borrow, so bytes are valid to overwrite.
        unsafe { explicit_bzero(self.as_mut_ptr().cast::<c_void>(), size_of_val(self)) };
    }
}

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

impl<Z, const N: usize> ZeroizeOnDrop for [Z; N] where Z: ZeroizeOnDrop {}

impl<Z> Zeroize for Vec<Z>
where
    [Z]: Zeroize,
{
    fn zeroize(&mut self) {
        self.as_mut_slice().zeroize();
        self.clear();
        self.spare_capacity_mut().zeroize();
    }
}

impl Zeroize for String {
    fn zeroize(&mut self) {
        // SAFETY: Bytes are overwritten with zeros.
        unsafe { self.as_mut_vec() }.zeroize();
    }
}

/// Wrapper that zeroizes value it holds when dropped.
pub struct Zeroizing<Z: Zeroize + ?Sized>(Z);

impl<Z: Zeroize> Zeroizing<Z> {
    /// Wrap `value` so it is zeroized when dropped.
    pub fn new(value: Z) -> Zeroizing<Z> {
        Zeroizing(value)
    }
}

impl<Z: Zeroize + Clone> Clone for Zeroizing<Z> {
    fn clone(&self) -> Zeroizing<Z> {
        Zeroizing(self.0.clone())
    }

    fn clone_from(&mut self, source: &Zeroizing<Z>) {
        self.0.zeroize();
        self.0.clone_from(&source.0);
    }
}

impl<Z: Zeroize> From<Z> for Zeroizing<Z> {
    fn from(value: Z) -> Zeroizing<Z> {
        Zeroizing(value)
    }
}

impl<Z: Zeroize + ?Sized> Deref for Zeroizing<Z> {
    type Target = Z;

    fn deref(&self) -> &Z {
        &self.0
    }
}

impl<Z: Zeroize + ?Sized> DerefMut for Zeroizing<Z> {
    fn deref_mut(&mut self) -> &mut Z {
        &mut self.0
    }
}

impl<T: ?Sized, Z: AsRef<T> + Zeroize + ?Sized> AsRef<T> for Zeroizing<Z> {
    fn as_ref(&self) -> &T {
        self.0.as_ref()
    }
}

impl<T: ?Sized, Z: AsMut<T> + Zeroize + ?Sized> AsMut<T> for Zeroizing<Z> {
    fn as_mut(&mut self) -> &mut T {
        self.0.as_mut()
    }
}

impl<Z: Zeroize + ?Sized> Zeroize for Zeroizing<Z> {
    fn zeroize(&mut self) {
        self.0.zeroize();
    }
}

impl<Z: Zeroize + ?Sized> ZeroizeOnDrop for Zeroizing<Z> {}

impl<Z: Zeroize + ?Sized> Drop for Zeroizing<Z> {
    fn drop(&mut self) {
        self.0.zeroize();
    }
}

#[cfg(test)]
mod tests {
    use std::mem::MaybeUninit;

    use super::*;

    #[test]
    fn test_zeroize_1() {
        let mut value = 0xdead_beef_u32;
        value.zeroize();
        assert_eq!(value, 0);
        let mut flag = true;
        flag.zeroize();
        assert!(!flag);
    }

    #[test]
    fn test_zeroize_2() {
        let mut data = [1u8, 2, 3, 4];
        data.as_mut_slice().zeroize();
        assert_eq!(data, [0, 0, 0, 0]);
    }

    #[test]
    fn test_zeroize_3() {
        let mut data = [1u32, 2, 3];
        data.zeroize();
        assert_eq!(data, [0, 0, 0]);
        let mut empty: [u8; 0] = [];
        empty.zeroize();
        assert!(empty.is_empty());
    }

    #[test]
    fn test_zeroize_4() {
        let mut value = MaybeUninit::new(0x42u32);
        value.zeroize();
        assert_eq!(unsafe { value.assume_init() }, 0);
        let mut data: [MaybeUninit<u8>; 4] = [MaybeUninit::new(0xff); 4];
        data.zeroize();
        for cell in &data {
            assert_eq!(unsafe { cell.assume_init() }, 0);
        }
    }

    #[test]
    fn test_zeroize_5() {
        let mut data = vec![1u8, 2, 3, 4];
        data.reserve(8);
        data.zeroize();
        assert!(data.is_empty());
    }

    #[test]
    fn test_zeroize_6() {
        let mut secret = String::from("s3cr3t");
        secret.zeroize();
        assert!(secret.is_empty());
    }

    #[test]
    fn test_zeroizing_1() {
        let value = Zeroizing::new(0x1234u32);
        assert_eq!(*value, 0x1234);
    }

    #[test]
    fn test_zeroizing_2() {
        let mut value = Zeroizing::new([1u8, 2, 3]);
        value[0] = 9;
        let as_ref: &[u8] = value.as_ref();
        assert_eq!(as_ref, &[9, 2, 3]);
        let as_mut: &mut [u8] = value.as_mut();
        as_mut[2] = 7;
        assert_eq!(*value, [9, 2, 7]);
    }

    #[test]
    fn test_zeroizing_3() {
        let original = Zeroizing::new(vec![1u8, 2, 3]);
        let cloned = original.clone();
        assert_eq!(*cloned, vec![1, 2, 3]);
        let mut target = Zeroizing::new(vec![9u8]);
        target.clone_from(&original);
        assert_eq!(*target, vec![1, 2, 3]);
    }

    #[test]
    fn test_zeroizing_4() {
        let from_value: Zeroizing<u32> = 7u32.into();
        assert_eq!(*from_value, 7);
        let mut value = Zeroizing::new(0x55u32);
        value.zeroize();
        assert_eq!(*value, 0);
    }
}