redoubt_zero_core/
zeroizing_guard.rs

1// Copyright (c) 2025-2026 Federico Hoerth <memparanoid@gmail.com>
2// SPDX-License-Identifier: GPL-3.0-only
3// See LICENSE in the repository root for full license text.
4
5//! RAII guard for owned values that auto-zeroizes on drop.
6
7use alloc::boxed::Box;
8use core::fmt;
9use core::mem;
10use core::ops::{Deref, DerefMut};
11use core::sync::atomic::{Ordering, compiler_fence};
12
13use crate::collections::{collection_zeroed, to_zeroization_probe_dyn_ref};
14
15use super::assert::assert_zeroize_on_drop;
16use super::traits::{AssertZeroizeOnDrop, FastZeroizable, ZeroizationProbe};
17use super::zeroize_on_drop_sentinel::ZeroizeOnDropSentinel;
18
19/// RAII guard for owned values that automatically zeroizes on drop.
20///
21/// `ZeroizingGuard` wraps an owned value `T` in a `Box` and ensures that it is zeroized
22/// when the guard is dropped. This is useful for returning sensitive data from
23/// functions while guaranteeing automatic cleanup.
24///
25/// # Design
26///
27/// - Wraps `Box<T>` (owns the value on the heap, avoiding stack copies)
28/// - Takes `&mut T` in constructor and swaps with `T::default()`, zeroizing the source
29/// - Implements `Deref` and `DerefMut` for convenient access
30/// - Zeroizes `inner` on drop
31/// - Contains [`ZeroizeOnDropSentinel`] to verify zeroization happened
32///
33/// # Usage
34///
35/// ```rust
36/// use redoubt_zero_core::{ZeroizingGuard, ZeroizationProbe, FastZeroizable};
37///
38/// fn create_sensitive_data() -> ZeroizingGuard<u64> {
39///     let mut value = 12345u64;
40///     ZeroizingGuard::from_mut(&mut value)
41/// }
42///
43/// {
44///     let guard = create_sensitive_data();
45///     assert_eq!(*guard, 12345);
46/// } // guard drops here → value is zeroized
47/// ```
48///
49/// # Panics
50///
51/// The guard panics on drop if the wrapped value's [`ZeroizeOnDropSentinel`] was not
52/// marked as zeroized. This ensures zeroization invariants are enforced.
53pub struct ZeroizingGuard<T>
54where
55    T: FastZeroizable + ZeroizationProbe + Default,
56{
57    inner: Box<T>,
58    __sentinel: ZeroizeOnDropSentinel,
59}
60
61impl<T> fmt::Debug for ZeroizingGuard<T>
62where
63    T: FastZeroizable + ZeroizationProbe + Default,
64{
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "[REDACTED ZeroizingGuard]")
67    }
68}
69
70impl<T> ZeroizingGuard<T>
71where
72    T: FastZeroizable + ZeroizationProbe + Default,
73{
74    /// Creates a new guard by swapping the value from the source and zeroizing it.
75    ///
76    /// The source location is swapped with `T::default()` and then zeroized,
77    /// ensuring no copies of the sensitive data remain on the stack.
78    /// The value is stored in a `Box` on the heap.
79    ///
80    /// # Example
81    ///
82    /// ```rust
83    /// use redoubt_zero_core::{ZeroizingGuard, ZeroizationProbe};
84    ///
85    /// let mut value = 42u32;
86    /// let guard = ZeroizingGuard::from_mut(&mut value);
87    /// assert_eq!(*guard, 42);
88    /// assert!(value.is_zeroized()); // source is zeroized
89    /// ```
90    pub fn from_mut(value: &mut T) -> Self {
91        // Allocate box with default value
92        let mut boxed = Box::new(T::default());
93        // Swap value into the box
94        mem::swap(&mut *boxed, value);
95        // Zeroize the source location
96        value.fast_zeroize();
97
98        Self {
99            inner: boxed,
100            __sentinel: ZeroizeOnDropSentinel::default(),
101        }
102    }
103
104    /// Creates a new guard with the default value of `T`.
105    ///
106    /// This is a convenience method equivalent to:
107    /// ```rust,ignore
108    /// let mut value = T::default();
109    /// ZeroizingGuard::from_mut(&mut value)
110    /// ```
111    ///
112    /// # Example
113    ///
114    /// ```rust
115    /// use redoubt_zero_core::{ZeroizingGuard, ZeroizationProbe};
116    ///
117    /// let guard: ZeroizingGuard<u64> = ZeroizingGuard::from_default();
118    /// assert!(guard.is_zeroized());
119    /// ```
120    #[inline(always)]
121    pub fn from_default() -> Self {
122        let mut value = T::default();
123        Self::from_mut(&mut value)
124    }
125}
126
127impl<T> Deref for ZeroizingGuard<T>
128where
129    T: FastZeroizable + ZeroizationProbe + Default,
130{
131    type Target = T;
132
133    fn deref(&self) -> &Self::Target {
134        &self.inner
135    }
136}
137
138impl<T> DerefMut for ZeroizingGuard<T>
139where
140    T: FastZeroizable + ZeroizationProbe + Default,
141{
142    fn deref_mut(&mut self) -> &mut Self::Target {
143        &mut self.inner
144    }
145}
146
147impl<T> FastZeroizable for ZeroizingGuard<T>
148where
149    T: FastZeroizable + ZeroizationProbe + Default,
150{
151    fn fast_zeroize(&mut self) {
152        self.inner.fast_zeroize();
153        compiler_fence(Ordering::SeqCst);
154
155        self.__sentinel.fast_zeroize();
156        compiler_fence(Ordering::SeqCst);
157    }
158}
159
160impl<T> AssertZeroizeOnDrop for ZeroizingGuard<T>
161where
162    T: FastZeroizable + ZeroizationProbe + Default,
163{
164    fn clone_sentinel(&self) -> ZeroizeOnDropSentinel {
165        self.__sentinel.clone()
166    }
167
168    fn assert_zeroize_on_drop(self) {
169        assert_zeroize_on_drop(self);
170    }
171}
172
173impl<T> ZeroizationProbe for ZeroizingGuard<T>
174where
175    T: FastZeroizable + ZeroizationProbe + Default,
176{
177    fn is_zeroized(&self) -> bool {
178        let fields: [&dyn ZeroizationProbe; 1] = [to_zeroization_probe_dyn_ref(&*self.inner)];
179        collection_zeroed(&mut fields.into_iter())
180    }
181}
182
183impl<T> Drop for ZeroizingGuard<T>
184where
185    T: FastZeroizable + ZeroizationProbe + Default,
186{
187    fn drop(&mut self) {
188        self.fast_zeroize();
189    }
190}