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}