From 1f03a1452a00d63c85c6094d1d66e3c6bfa394a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 19 Dec 2025 15:40:59 +0300 Subject: [PATCH 1/5] zeroize: replace `atomic_fence` with `optimization_barrier` --- zeroize/src/aarch64.rs | 4 +- zeroize/src/lib.rs | 100 ++++++++++++++++++++++++++++++++++------- zeroize/src/x86.rs | 4 +- 3 files changed, 88 insertions(+), 20 deletions(-) diff --git a/zeroize/src/aarch64.rs b/zeroize/src/aarch64.rs index 317db34c..68c59980 100644 --- a/zeroize/src/aarch64.rs +++ b/zeroize/src/aarch64.rs @@ -1,6 +1,6 @@ //! [`Zeroize`] impls for ARM64 SIMD registers. -use crate::{Zeroize, atomic_fence, volatile_write}; +use crate::{Zeroize, optimization_barrier, volatile_write}; use core::arch::aarch64::*; @@ -11,7 +11,7 @@ macro_rules! impl_zeroize_for_simd_register { #[inline] fn zeroize(&mut self) { volatile_write(self, unsafe { core::mem::zeroed() }); - atomic_fence(); + optimization_barrier(self); } } )+ diff --git a/zeroize/src/lib.rs b/zeroize/src/lib.rs index 2c125168..90d60a94 100644 --- a/zeroize/src/lib.rs +++ b/zeroize/src/lib.rs @@ -259,7 +259,6 @@ use core::{ }, ops, ptr, slice::IterMut, - sync::atomic, }; #[cfg(feature = "alloc")] @@ -300,7 +299,7 @@ where { fn zeroize(&mut self) { volatile_write(self, Z::default()); - atomic_fence(); + optimization_barrier(self); } } @@ -334,7 +333,7 @@ macro_rules! impl_zeroize_for_non_zero { None => unreachable!(), }; volatile_write(self, ONE); - atomic_fence(); + optimization_barrier(self); } } )+ @@ -425,7 +424,7 @@ where // done so by take(). unsafe { ptr::write_volatile(self, None) } - atomic_fence(); + optimization_barrier(self); } } @@ -441,7 +440,7 @@ impl Zeroize for MaybeUninit { // Safety: // `MaybeUninit` is valid for any byte pattern, including zeros. unsafe { ptr::write_volatile(self, MaybeUninit::zeroed()) } - atomic_fence(); + optimization_barrier(self); } } @@ -467,7 +466,7 @@ impl Zeroize for [MaybeUninit] { // and 0 is a valid value for `MaybeUninit` // The memory of the slice should not wrap around the address space. unsafe { volatile_set(ptr, MaybeUninit::zeroed(), size) } - atomic_fence(); + optimization_barrier(self); } } @@ -493,7 +492,7 @@ where // `self.len()` is also not larger than an `isize`, because of the assertion above. // The memory of the slice should not wrap around the address space. unsafe { volatile_set(self.as_mut_ptr(), Z::default(), self.len()) }; - atomic_fence(); + optimization_barrier(self); } } @@ -749,14 +748,6 @@ where } } -/// Use fences to prevent accesses from being reordered before this -/// point, which should hopefully help ensure that all accessors -/// see zeroes after this point. -#[inline(always)] -fn atomic_fence() { - atomic::compiler_fence(atomic::Ordering::SeqCst); -} - /// Perform a volatile write to the destination #[inline(always)] fn volatile_write(dst: &mut T, src: T) { @@ -847,7 +838,84 @@ pub unsafe fn zeroize_flat_type(data: *mut F) { unsafe { volatile_set(data as *mut u8, 0, size); } - atomic_fence() + optimization_barrier(&data); +} + +/// Observe the referenced data and prevent the compiler from removing previous writes to it. +/// +/// This function acts like [`core::hint::black_box`] but takes a reference and +/// does not return the passed value. +/// +/// It's implemented using the [`core::arch::asm!`] macro on target arches where `asm!` is stable, +/// i.e. `aarch64`, `arm`, `arm64ec`, `loongarch64`, `riscv32`, `riscv64`, `s390x`, `x86`, and +/// `x86_64`. On all other targets it's implemented using [`core::hint::black_box`]. +/// +/// # Examples +/// ```ignore +/// use core::num::NonZeroU32; +/// use zeroize::{ZeroizeOnDrop, zeroize_flat_type}; +/// +/// struct DataToZeroize { +/// buf: [u8; 32], +/// pos: NonZeroU32, +/// } +/// +/// struct SomeMoreFlatData(u64); +/// +/// impl Drop for DataToZeroize { +/// fn drop(&mut self) { +/// self.buf = [0u8; 32]; +/// self.pos = NonZeroU32::new(32).unwrap(); +/// zeroize::optimization_barrier(self); +/// } +/// } +/// +/// impl zeroize::ZeroizeOnDrop for DataToZeroize {} +/// +/// let mut data = DataToZeroize { +/// buf: [3u8; 32], +/// pos: NonZeroU32::new(32).unwrap(), +/// }; +/// +/// // data gets zeroized when dropped +/// ``` +fn optimization_barrier(val: &R) { + #[cfg(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "arm64ec", + target_arch = "loongarch64", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + ) + ))] + unsafe { + core::arch::asm!( + "# {}", + in(reg) val as *const R as *const (), + options(readonly, preserves_flags, nostack), + ); + } + #[cfg(not(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "arm64ec", + target_arch = "loongarch64", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + ) + )))] + core::hint::black_box(val); } /// Internal module used as support for `AssertZeroizeOnDrop`. diff --git a/zeroize/src/x86.rs b/zeroize/src/x86.rs index 599fc494..cbaf7367 100644 --- a/zeroize/src/x86.rs +++ b/zeroize/src/x86.rs @@ -1,6 +1,6 @@ //! [`Zeroize`] impls for x86 SIMD registers -use crate::{Zeroize, atomic_fence, volatile_write}; +use crate::{Zeroize, optimization_barrier, volatile_write}; #[cfg(target_arch = "x86")] use core::arch::x86::*; @@ -15,7 +15,7 @@ macro_rules! impl_zeroize_for_simd_register { #[inline] fn zeroize(&mut self) { volatile_write(self, unsafe { core::mem::zeroed() }); - atomic_fence(); + optimization_barrier(self); } } )* From fd684b416e882d32dc9bd668bb7b95e01f935e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 19 Dec 2025 15:57:26 +0300 Subject: [PATCH 2/5] move `optimization_barrier` into a separate module --- zeroize/src/barrier.rs | 76 +++++++++++++++++++++++++++++++++++++++ zeroize/src/lib.rs | 80 ++---------------------------------------- 2 files changed, 79 insertions(+), 77 deletions(-) create mode 100644 zeroize/src/barrier.rs diff --git a/zeroize/src/barrier.rs b/zeroize/src/barrier.rs new file mode 100644 index 00000000..1ed95d77 --- /dev/null +++ b/zeroize/src/barrier.rs @@ -0,0 +1,76 @@ +/// Observe the referenced data and prevent the compiler from removing previous writes to it. +/// +/// This function acts like [`core::hint::black_box`] but takes a reference and +/// does not return the passed value. +/// +/// It's implemented using the [`core::arch::asm!`] macro on target arches where `asm!` is stable, +/// i.e. `aarch64`, `arm`, `arm64ec`, `loongarch64`, `riscv32`, `riscv64`, `s390x`, `x86`, and +/// `x86_64`. On all other targets it's implemented using [`core::hint::black_box`]. +/// +/// # Examples +/// ```ignore +/// use core::num::NonZeroU32; +/// use zeroize::{ZeroizeOnDrop, zeroize_flat_type}; +/// +/// struct DataToZeroize { +/// buf: [u8; 32], +/// pos: NonZeroU32, +/// } +/// +/// struct SomeMoreFlatData(u64); +/// +/// impl Drop for DataToZeroize { +/// fn drop(&mut self) { +/// self.buf = [0u8; 32]; +/// self.pos = NonZeroU32::new(32).unwrap(); +/// zeroize::optimization_barrier(self); +/// } +/// } +/// +/// impl zeroize::ZeroizeOnDrop for DataToZeroize {} +/// +/// let mut data = DataToZeroize { +/// buf: [3u8; 32], +/// pos: NonZeroU32::new(32).unwrap(), +/// }; +/// +/// // data gets zeroized when dropped +/// ``` +pub fn optimization_barrier(val: &R) { + #[cfg(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "arm64ec", + target_arch = "loongarch64", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + ) + ))] + unsafe { + core::arch::asm!( + "# {}", + in(reg) val as *const R as *const (), + options(readonly, preserves_flags, nostack), + ); + } + #[cfg(not(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "arm64ec", + target_arch = "loongarch64", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + ) + )))] + core::hint::black_box(val); +} diff --git a/zeroize/src/lib.rs b/zeroize/src/lib.rs index 90d60a94..46b4a0e7 100644 --- a/zeroize/src/lib.rs +++ b/zeroize/src/lib.rs @@ -250,6 +250,9 @@ mod aarch64; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86; +mod barrier; +use barrier::optimization_barrier; + use core::{ marker::{PhantomData, PhantomPinned}, mem::{MaybeUninit, size_of}, @@ -841,83 +844,6 @@ pub unsafe fn zeroize_flat_type(data: *mut F) { optimization_barrier(&data); } -/// Observe the referenced data and prevent the compiler from removing previous writes to it. -/// -/// This function acts like [`core::hint::black_box`] but takes a reference and -/// does not return the passed value. -/// -/// It's implemented using the [`core::arch::asm!`] macro on target arches where `asm!` is stable, -/// i.e. `aarch64`, `arm`, `arm64ec`, `loongarch64`, `riscv32`, `riscv64`, `s390x`, `x86`, and -/// `x86_64`. On all other targets it's implemented using [`core::hint::black_box`]. -/// -/// # Examples -/// ```ignore -/// use core::num::NonZeroU32; -/// use zeroize::{ZeroizeOnDrop, zeroize_flat_type}; -/// -/// struct DataToZeroize { -/// buf: [u8; 32], -/// pos: NonZeroU32, -/// } -/// -/// struct SomeMoreFlatData(u64); -/// -/// impl Drop for DataToZeroize { -/// fn drop(&mut self) { -/// self.buf = [0u8; 32]; -/// self.pos = NonZeroU32::new(32).unwrap(); -/// zeroize::optimization_barrier(self); -/// } -/// } -/// -/// impl zeroize::ZeroizeOnDrop for DataToZeroize {} -/// -/// let mut data = DataToZeroize { -/// buf: [3u8; 32], -/// pos: NonZeroU32::new(32).unwrap(), -/// }; -/// -/// // data gets zeroized when dropped -/// ``` -fn optimization_barrier(val: &R) { - #[cfg(all( - not(miri), - any( - target_arch = "aarch64", - target_arch = "arm", - target_arch = "arm64ec", - target_arch = "loongarch64", - target_arch = "riscv32", - target_arch = "riscv64", - target_arch = "s390x", - target_arch = "x86", - target_arch = "x86_64", - ) - ))] - unsafe { - core::arch::asm!( - "# {}", - in(reg) val as *const R as *const (), - options(readonly, preserves_flags, nostack), - ); - } - #[cfg(not(all( - not(miri), - any( - target_arch = "aarch64", - target_arch = "arm", - target_arch = "arm64ec", - target_arch = "loongarch64", - target_arch = "riscv32", - target_arch = "riscv64", - target_arch = "s390x", - target_arch = "x86", - target_arch = "x86_64", - ) - )))] - core::hint::black_box(val); -} - /// Internal module used as support for `AssertZeroizeOnDrop`. #[doc(hidden)] pub mod __internal { From 3ccf121a81a4d6b40fb519569507c8dc8a3dd3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 19 Dec 2025 16:00:53 +0300 Subject: [PATCH 3/5] Harden `optimization_barrier` for non-`asm!` targets --- zeroize/src/barrier.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/zeroize/src/barrier.rs b/zeroize/src/barrier.rs index 1ed95d77..69f87e5e 100644 --- a/zeroize/src/barrier.rs +++ b/zeroize/src/barrier.rs @@ -36,7 +36,7 @@ /// /// // data gets zeroized when dropped /// ``` -pub fn optimization_barrier(val: &R) { +pub fn optimization_barrier(val: &T) { #[cfg(all( not(miri), any( @@ -54,7 +54,7 @@ pub fn optimization_barrier(val: &R) { unsafe { core::arch::asm!( "# {}", - in(reg) val as *const R as *const (), + in(reg) val as *const T as *const (), options(readonly, preserves_flags, nostack), ); } @@ -72,5 +72,16 @@ pub fn optimization_barrier(val: &R) { target_arch = "x86_64", ) )))] - core::hint::black_box(val); + { + /// Custom version of `core::hint::black_box` implemented using `#[inline(never)]` + /// and `read_volatile`. + #[inline(never)] + fn custom_black_box(p: *const u8) { + let _ = unsafe { core::ptr::read_volatile(p) }; + } + core::hint::black_box(val); + if size_of_val(val) > 0 { + custom_black_box(val as *const T as *const u8); + } + } } From 4202bd895ad3d93d8991cd22a709a0e20a80537e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 19 Dec 2025 16:06:27 +0300 Subject: [PATCH 4/5] tweak docs --- zeroize/src/barrier.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/zeroize/src/barrier.rs b/zeroize/src/barrier.rs index 69f87e5e..ef026cad 100644 --- a/zeroize/src/barrier.rs +++ b/zeroize/src/barrier.rs @@ -5,7 +5,10 @@ /// /// It's implemented using the [`core::arch::asm!`] macro on target arches where `asm!` is stable, /// i.e. `aarch64`, `arm`, `arm64ec`, `loongarch64`, `riscv32`, `riscv64`, `s390x`, `x86`, and -/// `x86_64`. On all other targets it's implemented using [`core::hint::black_box`]. +/// `x86_64`. +/// +/// On all other targets it's implemented using [`core::hint::black_box`] and custom `black_box` +/// implemented using `#[inline(never)]` and `read_volatile`. /// /// # Examples /// ```ignore @@ -73,12 +76,13 @@ pub fn optimization_barrier(val: &T) { ) )))] { - /// Custom version of `core::hint::black_box` implemented using `#[inline(never)]` - /// and `read_volatile`. + /// Custom version of `core::hint::black_box` implemented using + /// `#[inline(never)]` and `read_volatile`. #[inline(never)] fn custom_black_box(p: *const u8) { let _ = unsafe { core::ptr::read_volatile(p) }; } + core::hint::black_box(val); if size_of_val(val) > 0 { custom_black_box(val as *const T as *const u8); From dc55d3f26249b56ecc0f5c66823980cbeb531da6 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Sat, 20 Dec 2025 04:01:15 +0300 Subject: [PATCH 5/5] Mark `optimization_barrier` as `pub(crate)` --- zeroize/src/barrier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeroize/src/barrier.rs b/zeroize/src/barrier.rs index ef026cad..abc8e600 100644 --- a/zeroize/src/barrier.rs +++ b/zeroize/src/barrier.rs @@ -39,7 +39,7 @@ /// /// // data gets zeroized when dropped /// ``` -pub fn optimization_barrier(val: &T) { +pub(crate) fn optimization_barrier(val: &T) { #[cfg(all( not(miri), any(