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}