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}