redoubt_zero_core/
zeroize_on_drop_sentinel.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
5extern crate alloc;
6
7use alloc::sync::Arc;
8use core::ptr;
9use core::sync::atomic::{AtomicBool, Ordering};
10
11use crate::{FastZeroizable, ZeroizeMetadata};
12
13/// Runtime verification that zeroization happened before drop.
14///
15/// `ZeroizeOnDropSentinel` is a guard type used to verify that `.zeroize()` was called
16/// before a value is dropped. This provides **runtime enforcement** of zeroization
17/// invariants, complementing compile-time checks.
18///
19/// # Design
20///
21/// - Wraps a shared boolean flag (`Arc<AtomicBool>`) representing pristine state
22/// - Initially `true` (pristine/untouched)
23/// - `.zeroize()` sets the flag to `false` (no longer pristine)
24/// - Can be cloned to verify zeroization from tests
25///
26/// # Panics
27///
28/// Panics on drop if `.zeroize()` was not called before drop. This is intentional:
29/// forgetting to zeroize sensitive data is a critical bug that must be caught.
30///
31/// # Usage
32///
33/// Typically used as a field in structs to verify zeroization:
34///
35/// ```rust,ignore
36/// use redoubt_zero_core::{ZeroizeOnDropSentinel, FastZeroizable};
37///
38/// struct Secret {
39///     data: Vec<u8>,
40///     __sentinel: ZeroizeOnDropSentinel,
41/// }
42///
43/// impl Drop for Secret {
44///     fn drop(&mut self) {
45///         self.data.fast_zeroize();
46///         self.__sentinel.fast_zeroize();
47///     }
48/// }
49/// ```
50///
51/// The `__sentinel` field tracks whether `.zeroize()` was called before drop.
52/// You'll need to implement `FastZeroizable`, `ZeroizationProbe`, and `AssertZeroizeOnDrop`
53/// manually, or use the `RedoubtZero` umbrella crate which provides `#[derive(RedoubtZero)]`.
54///
55/// # Testing
56///
57/// Clone the sentinel to verify zeroization behavior:
58///
59/// ```rust
60/// use redoubt_zero_core::ZeroizeOnDropSentinel;
61/// use redoubt_zero_core::FastZeroizable;
62///
63/// let mut sentinel = ZeroizeOnDropSentinel::default();
64/// let sentinel_clone = sentinel.clone();
65///
66/// assert!(!sentinel_clone.is_zeroized());
67/// sentinel.fast_zeroize();
68/// assert!(sentinel_clone.is_zeroized());
69/// ```
70#[derive(Clone, Debug)]
71pub struct ZeroizeOnDropSentinel(Arc<AtomicBool>);
72
73impl PartialEq for ZeroizeOnDropSentinel {
74    fn eq(&self, other: &Self) -> bool {
75        self.0.load(Ordering::Relaxed) == other.0.load(Ordering::Relaxed)
76    }
77}
78
79impl Eq for ZeroizeOnDropSentinel {}
80
81impl ZeroizeOnDropSentinel {
82    /// Resets the sentinel to "not zeroized" (pristine) state.
83    ///
84    /// This is useful in tests when reusing a sentinel for multiple assertions.
85    ///
86    /// # Example
87    ///
88    /// ```rust
89    /// use redoubt_zero_core::{ZeroizeOnDropSentinel, FastZeroizable, ZeroizationProbe};
90    ///
91    /// let mut sentinel = ZeroizeOnDropSentinel::default();
92    /// sentinel.fast_zeroize();
93    /// assert!(sentinel.is_zeroized());
94    ///
95    /// sentinel.reset();
96    /// assert!(!sentinel.is_zeroized());
97    /// ```
98    pub fn reset(&mut self) {
99        self.0.store(true, Ordering::Relaxed);
100    }
101
102    /// Checks if zeroization happened (i.e., if `.zeroize()` was called).
103    ///
104    /// Returns `true` if the sentinel was zeroized, `false` if still pristine.
105    ///
106    /// # Example
107    ///
108    /// ```rust
109    /// use redoubt_zero_core::{ZeroizeOnDropSentinel, FastZeroizable, ZeroizationProbe};
110    ///
111    /// let mut sentinel = ZeroizeOnDropSentinel::default();
112    /// assert!(!sentinel.is_zeroized());
113    ///
114    /// sentinel.fast_zeroize();
115    /// assert!(sentinel.is_zeroized());
116    /// ```
117    pub fn is_zeroized(&self) -> bool {
118        !self.0.load(Ordering::Relaxed)
119    }
120}
121
122impl Default for ZeroizeOnDropSentinel {
123    fn default() -> Self {
124        Self(Arc::new(AtomicBool::new(true)))
125    }
126}
127
128impl ZeroizeMetadata for ZeroizeOnDropSentinel {
129    const CAN_BE_BULK_ZEROIZED: bool = false;
130}
131
132impl FastZeroizable for ZeroizeOnDropSentinel {
133    fn fast_zeroize(&mut self) {
134        // SAFETY: Using volatile write to prevent compiler from optimizing away the store
135        unsafe {
136            ptr::write_volatile(&mut *self.0.as_ptr(), false);
137        }
138    }
139}