Skip to main content

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}