Skip to main content

zeroize/
barrier.rs

1/// Observe the referenced data and prevent the compiler from removing previous writes to it.
2///
3/// This function acts like [`core::hint::black_box`] but takes a reference and
4/// does not return the passed value.
5///
6/// It's implemented using the [`core::arch::asm!`] macro on target arches where `asm!` is stable,
7/// i.e. `aarch64`, `arm`, `arm64ec`, `loongarch64`, `riscv32`, `riscv64`, `s390x`, `x86`, and
8/// `x86_64`.
9///
10/// On all other targets it's implemented using [`core::hint::black_box`] and custom `black_box`
11/// implemented using `#[inline(never)]` and `read_volatile`.
12///
13/// # Examples
14/// ```
15/// use core::num::NonZeroU32;
16///
17/// # type ThirdPartyType = u32;
18///
19/// struct DataToZeroize {
20///     buf: [u8; 32],
21///     // `ThirdPartyType` can be a type with private fields
22///     // defined in a third-party crate and which does not implement
23///     // `Zeroize` or zeroization on drop.
24///     data: ThirdPartyType,
25///     pos: NonZeroU32,
26/// }
27///
28/// struct SomeMoreFlatData(u64);
29///
30/// impl Drop for DataToZeroize {
31///     fn drop(&mut self) {
32///         self.buf = [0u8; 32];
33///         self.data = ThirdPartyType::default();
34///         self.pos = NonZeroU32::new(32).unwrap();
35///         zeroize::optimization_barrier(self);
36///     }
37/// }
38///
39/// impl zeroize::ZeroizeOnDrop for DataToZeroize {}
40///
41/// let mut data = DataToZeroize {
42///     buf: [3u8; 32],
43///     data: ThirdPartyType::default(),
44///     pos: NonZeroU32::new(32).unwrap(),
45/// };
46///
47/// // data gets zeroized when dropped
48/// ```
49///
50/// Note that erasure of `ThirdPartyType` demonstrated above can be fragile if it contains
51/// `MaybeUninit` or `union` data. It also does not perform erasure of types like `Box` or `Vec`.
52pub fn optimization_barrier<T: ?Sized>(val: &T) {
53    #[cfg(all(
54        not(miri),
55        any(
56            target_arch = "aarch64",
57            target_arch = "arm",
58            target_arch = "arm64ec",
59            target_arch = "loongarch64",
60            target_arch = "riscv32",
61            target_arch = "riscv64",
62            target_arch = "s390x",
63            target_arch = "x86",
64            target_arch = "x86_64",
65        )
66    ))]
67    // Safety:
68    //
69    // The asm block only passes the address of `val` in a register and does not read or write
70    // any memory itself (`readonly`), so it has no additional requirements. Not specifying
71    // `nomem` is intentional: it makes the compiler assume the block may read memory, which is
72    // what prevents earlier writes to `val` from being optimized away.
73    unsafe {
74        core::arch::asm!(
75            "# {}",
76            in(reg) core::ptr::from_ref::<T>(val).cast::<()>(),
77            options(readonly, preserves_flags, nostack),
78        );
79    }
80    #[cfg(not(all(
81        not(miri),
82        any(
83            target_arch = "aarch64",
84            target_arch = "arm",
85            target_arch = "arm64ec",
86            target_arch = "loongarch64",
87            target_arch = "riscv32",
88            target_arch = "riscv64",
89            target_arch = "s390x",
90            target_arch = "x86",
91            target_arch = "x86_64",
92        )
93    )))]
94    {
95        /// Custom version of `core::hint::black_box` implemented using
96        /// `#[inline(never)]` and `read_volatile`.
97        #[inline(never)]
98        fn custom_black_box(p: *const u8) {
99            use core::mem::MaybeUninit;
100
101            // Safety:
102            //
103            // Callers only pass a non-null pointer to a live value (`custom_black_box` is only
104            // called when `size_of_val(val) > 0`), and `MaybeUninit<u8>` accepts any byte, so
105            // the volatile read is valid even for uninitialized padding.
106            let _: MaybeUninit<u8> = unsafe { core::ptr::read_volatile(p.cast()) };
107        }
108
109        core::hint::black_box(val);
110        if size_of_val(val) > 0 {
111            custom_black_box(core::ptr::from_ref(val).cast::<u8>());
112        }
113    }
114}