cocoon_tpm_utils_common/zeroize.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2023-2025 SUSE LLC
3// Author: Nicolai Stange <nstange@suse.de>
4
5//! Configuration dependent, transparent aliases as well as some utilities
6//! related to the [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) crate.
7//!
8//! Depending on whether or not the `zeroize` Cargo feature is enabled,
9//! [`Zeroize`], [`ZeroizeOnDrop`] and [`Zeroizing`] are either defined as
10//! aliases to the actual definitions from the [`zeroize crate`](https://docs.rs/zeroize/latest/zeroize/index.html) or to trivial
11//! drop-in substitutes.
12//!
13//! In addition to that, zeroization related helpers like [`ZeroizingFlat`] are
14//! being provided.
15
16extern crate alloc;
17use alloc::boxed::Box;
18
19use core::{clone::Clone, convert, mem, ops};
20
21#[cfg(feature = "zeroize")]
22use zeroize;
23
24#[cfg(feature = "zeroize")]
25#[doc(hidden)]
26mod cfg {
27 pub use zeroize::Zeroize;
28 pub use zeroize::ZeroizeOnDrop;
29 pub use zeroize::Zeroizing;
30}
31
32#[cfg(not(feature = "zeroize"))]
33#[doc(hidden)]
34mod cfg {
35 use core::ops;
36
37 pub trait Zeroize {
38 fn zeroize(&mut self);
39 }
40
41 impl<T> Zeroize for T {
42 fn zeroize(&mut self) {}
43 }
44
45 pub trait ZeroizeOnDrop {}
46
47 #[derive(Clone, Copy)]
48 #[repr(transparent)]
49 pub struct Zeroizing<T>(T);
50
51 impl<T> core::ops::Deref for Zeroizing<T> {
52 type Target = T;
53
54 fn deref(&self) -> &Self::Target {
55 &self.0
56 }
57 }
58
59 impl<T> ops::DerefMut for Zeroizing<T> {
60 fn deref_mut(&mut self) -> &mut <Self as ops::Deref>::Target {
61 &mut self.0
62 }
63 }
64
65 impl<T> From<T> for Zeroizing<T> {
66 fn from(value: T) -> Self {
67 Self(value)
68 }
69 }
70}
71
72/// Configuration abstraction alias definition for
73/// [`zeroize::Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html).
74///
75/// Depending on whether or the Cargo feature `zeroize` is enabled, this is
76/// either an alias to the real [`zeroize::Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) or to some
77/// API compatible drop-in substitute implemented trivially for any type.
78pub use cfg::Zeroize;
79
80/// Configuration abstraction alias definition for
81/// [`zeroize::ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html).
82///
83/// Depending on whether or the Cargo feature `zeroize` is enabled, this is
84/// either an alias to the real [`zeroize::ZeroizeOnDrop`](https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html)
85/// or to some API compatible drop-in substitute.
86pub use cfg::ZeroizeOnDrop;
87
88/// Configuration abstraction alias definition for
89/// [`zeroize::Zeroizing`](https://docs.rs/zeroize/latest/zeroize/struct.Zeroizing.html).
90///
91/// Depending on whether or the Cargo feature `zeroize` is enabled, this is
92/// either an alias to the real [`zeroize::Zeroizing`](https://docs.rs/zeroize/latest/zeroize/struct.Zeroizing.html) or to
93/// some trivial, API compatible drop-in substitute.
94pub use cfg::Zeroizing;
95
96/// Zeroize a flat type/struct on `drop`.
97///
98/// For external types not implementing `Zeroize`, this can be used to still
99/// clear its memory after it has been dropped.
100///
101/// Only the flat memory backing `T` itself is getting cleared, but **not** any
102/// heap allocations the value itself possibly owns, like e.g. some managed
103/// through `Vec`s, `Box`es or alike.
104///
105/// <div class="warning">
106///
107/// Works reliably only once `Box`ed and only for the memory owned by the `Box`,
108/// no guarantees are being made for temporary copies emitted by the compiler
109/// during construction or unpeeling through [`take_with()`](Self::take_with),
110/// [`take_boxed_with()`](Self::take_boxed_with) or
111/// [`into_inner()`](Self::into_inner) -- it all depends on compiler
112/// optimizations then
113///
114/// </div>
115///
116/// If the `zeroize` Cargo feature is off, `ZeroizingFlat` becomes a trivial
117/// wrapper.
118#[repr(transparent)]
119pub struct ZeroizingFlat<T> {
120 value: mem::MaybeUninit<T>,
121}
122
123impl<T> ZeroizingFlat<T> {
124 /// Wrap a value for zeroization at drop.
125 ///
126 /// <div class="warning">
127 ///
128 /// Even when constructed from rvalues, it all depends on compiler
129 /// optimizations whether or not the value will effectively get
130 /// constructed in place or non-zeroized intermediate copies will
131 /// be made on the stack.
132 ///
133 /// </div>
134 ///
135 /// # Arguments:
136 ///
137 /// * `value` - The value to wrap.
138 pub fn new(value: T) -> Self {
139 Self {
140 value: mem::MaybeUninit::new(value),
141 }
142 }
143
144 /// Take the wrapped value and invoke a callback on it.
145 ///
146 /// Functionally equivalent to
147 /// ```ignore
148 /// f(self.into_inner())
149 /// ```
150 /// Compared to the code above, `take_with()` fosters certain compiler
151 /// optimizations for copy elisions because it makes it possible to
152 /// invoke `f()` directly on the original memory backing the wrapped value
153 /// instead of on a temporary stack copy thereof.
154 ///
155 /// <div class="warning">
156 ///
157 /// There are no guarantees regarding whether such an optimization will
158 /// actually be made by the compiler. In particular, the compiler might
159 /// create non-zeroized temporary copies of the wrapped data on the
160 /// stack.
161 ///
162 /// </div>
163 ///
164 /// # Arguments:
165 ///
166 /// * `f` - The callback to invoke on the unwrapped value. The return value
167 /// gets propagated back.
168 #[allow(unused_mut)]
169 pub fn take_with<R, F: FnOnce(T) -> R>(mut self, f: F) -> R {
170 // Enable the compiler to call f() on the original data without preparing a
171 // temporary copy on the stack. Whether or not this works out depends on
172 // compiler optimizations though.
173 let inner = unsafe { self.value.assume_init_read() };
174 let mut this = mem::ManuallyDrop::new(self);
175 let r = f(inner);
176 #[cfg(feature = "zeroize")]
177 {
178 let p_value = &raw mut this.value;
179 unsafe { zeroize::zeroize_flat_type(p_value) };
180 }
181 r
182 }
183
184 /// Take the wrapped value.
185 ///
186 /// <div class="warning">
187 ///
188 /// Once unwrapped, no zeroization guarantees will apply to the unwrapped
189 /// value anymore, even in the following example:
190 /// ```ignore
191 /// let secret: ZeroizingFlat<T>;
192 /// let secret = ZeroizingFlat::new(secret.into_inner());
193 /// ```
194 ///
195 /// </div>
196 pub fn into_inner(self) -> T {
197 self.take_with(|value| value)
198 }
199
200 /// Take the wrapped value from a `Box<Self>` and invoke a callback on it.
201 ///
202 /// Functionally equivalent to
203 /// ```ignore
204 /// Box::into_inner(self).take_with(f)
205 /// ```
206 /// with `Box::into_inner()` being unstable at the time of writing.
207 ///
208 /// Note that in the code snippet above, the `Box::into_inner()` to be more
209 /// specific, would almost certainly move `Self` into a temporary
210 /// location on the stack, and that stack copy would then eventually get
211 /// zeroized, **not** the memory previously owned by the `Box`.
212 /// `take_boxed_with()` on the other hand guarantees that the memory owned
213 /// by the `Box` will get zeroized.
214 ///
215 /// Furthermore, `take_boxed_with()` fosters certain compiler
216 /// optimizations for copy elisions because it makes it possible to
217 /// invoke `f()` directly on the original memory managed by the `Box`
218 /// instead of on a temporary stack copy thereof.
219 ///
220 /// <div class="warning">
221 ///
222 /// There are no guarantees regarding whether such an optimization will
223 /// actually be made by the compiler. In particular, the compiler might
224 /// create non-zeroized temporary copies of the wrapped data on the
225 /// stack.
226 ///
227 /// </div>
228 ///
229 /// # Arguments:
230 ///
231 /// * `f` - The callback to invoke on the unwrapped value. The return value
232 /// gets propagated back.
233 pub fn take_boxed_with<R, F: FnOnce(T) -> R>(self: Box<Self>, f: F) -> R {
234 // Transform the Box<Self> to Box<ManuallyDrop<Self>> in order to avoid double
235 // frees upon drop of Self on unwind from f() -- the ownership of the
236 // wrapped value gets moved into f() and it ought to get dropped from
237 // there only.
238 let p_this = Box::into_raw(self) as *mut mem::ManuallyDrop<Self>;
239 let this = unsafe { Box::from_raw(p_this) };
240
241 // Enable the compiler to call f() on the original data owned by the Box without
242 // preparing a temporary copy on the stack. Whether or not this works
243 // out depends on compiler optimizations though.
244 let inner = unsafe { this.value.assume_init_read() };
245 let r = f(inner);
246
247 // Now zeroize the memory and deallocate.
248 let p_this = Box::into_raw(this) as *mut Self;
249 #[cfg(feature = "zeroize")]
250 {
251 let p_value = &raw mut unsafe { &mut *p_this }.value;
252 unsafe { zeroize::zeroize_flat_type(p_value) }
253 };
254 // Don't drop upon deallocation, the value had been moved into f() above.
255 let p_this = p_this as *mut mem::ManuallyDrop<Self>;
256 drop(unsafe { Box::from_raw(p_this) });
257
258 r
259 }
260
261 /// Replace the wrapped value with a new one.
262 ///
263 /// Compared to mere reassignment of Self, this avoids a redundant
264 /// zeroization pass between dropping the old and assigning the new
265 /// value.
266 ///
267 /// <div class="warning">
268 ///
269 /// The compiler might emit temporary copies of `value` on the stack not
270 /// covered by any zeroization.
271 ///
272 /// </div>
273 ///
274 /// # Arguments:
275 ///
276 /// * `value` - The new value to wrap.
277 pub fn replace(&mut self, value: T) {
278 unsafe { self.value.assume_init_drop() };
279 self.value = mem::MaybeUninit::new(value);
280 }
281
282 /// Replace the value wrapped in a `Box`ed `Self`.
283 ///
284 ///
285 /// Compared to [`replace`](Self::replace), `replace_boxed_with()` fosters
286 /// certain compiler optimizations for copy elisions because it makes it
287 /// possible to place the new value directly into the memory backing the
288 /// originally wrapped value instead of into an intermediate stack
289 /// copy first.
290 ///
291 /// <div class="warning">
292 ///
293 /// There are no guarantees regarding whether such an optimization will
294 /// actually be made by the compiler. In particular, the compiler might
295 /// create non-zeroized temporary copies of the wrapped data on the stack.
296 ///
297 /// </div>
298 ///
299 /// `replace_boxed_with()` takes a `Box<Self>` and a callback for obtaining
300 /// the new replacement for the wrapped value, invokes `f()` to obtain
301 /// the replacement, wraps it in `self` and returns the `Box<Self>`
302 /// back. No memory reallocation will be made in the course.
303 ///
304 /// # Arguments:
305 ///
306 /// * `f` - The callback to obtain the replacement for the wrapped value
307 /// from.
308 pub fn replace_boxed_with<F: FnOnce() -> T>(mut self: Box<Self>, f: F) -> Box<Self> {
309 unsafe { self.value.assume_init_drop() };
310 // Temporarily turn the Box<Self> into a Box<ManuallyDrop<Self>> to avoid double
311 // frees upon unwinding from f() -- the formerly wrapped valued has just
312 // been dropped, don't do it again.
313 let p_this = Box::into_raw(self) as *mut mem::ManuallyDrop<Self>;
314 let mut this = unsafe { Box::from_raw(p_this) };
315 this.value = mem::MaybeUninit::new(f());
316 let p_this = Box::into_raw(this) as *mut Self;
317 unsafe { Box::from_raw(p_this) }
318 }
319}
320
321impl<T> Drop for ZeroizingFlat<T> {
322 fn drop(&mut self) {
323 unsafe { mem::MaybeUninit::assume_init_drop(&mut self.value) };
324 #[cfg(feature = "zeroize")]
325 unsafe {
326 zeroize::zeroize_flat_type(&raw mut self.value)
327 };
328 }
329}
330
331impl<T> convert::From<T> for ZeroizingFlat<T> {
332 fn from(value: T) -> Self {
333 Self::new(value)
334 }
335}
336
337impl<T> ops::Deref for ZeroizingFlat<T> {
338 type Target = T;
339
340 fn deref(&self) -> &Self::Target {
341 unsafe { self.value.assume_init_ref() }
342 }
343}
344
345impl<T> ops::DerefMut for ZeroizingFlat<T> {
346 fn deref_mut(&mut self) -> &mut Self::Target {
347 unsafe { self.value.assume_init_mut() }
348 }
349}
350
351impl<T: Clone> Clone for ZeroizingFlat<T> {
352 fn clone(&self) -> Self {
353 Self {
354 value: mem::MaybeUninit::new(unsafe { self.value.assume_init_ref() }.clone()),
355 }
356 }
357}