From 5930b064eafda682e8f3b9e4100a99b40f30ebc3 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: Wed, 7 Jan 2026 23:03:23 +0300 Subject: [PATCH 1/6] zeroize: add `zeroize_stack` function --- zeroize/src/lib.rs | 3 +++ zeroize/src/stack.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 zeroize/src/stack.rs diff --git a/zeroize/src/lib.rs b/zeroize/src/lib.rs index 0a452163..0fb628e6 100644 --- a/zeroize/src/lib.rs +++ b/zeroize/src/lib.rs @@ -253,6 +253,9 @@ mod x86; mod barrier; pub use barrier::optimization_barrier; +mod stack; +pub use stack::zeroize_stack; + use core::{ marker::{PhantomData, PhantomPinned}, mem::{MaybeUninit, size_of}, diff --git a/zeroize/src/stack.rs b/zeroize/src/stack.rs new file mode 100644 index 00000000..e9ace6ee --- /dev/null +++ b/zeroize/src/stack.rs @@ -0,0 +1,45 @@ +/// Zeroize `N` bytes of stack space. +/// +/// Most algorithm implementations use stack to store temporary data. +/// This temporaries may contain sensitive information (e.g. cryptgraphic keys) +/// and can stay on stack after the computation is finished. If an attacker +/// is able for some reasons to read stack data freely, it may result in +/// leaking of the sensitive data. +/// +/// # WARNING +/// This function requires you to estimate how much stack space is used by your +/// sensitive computation. This can be done by tools like [`cargo-call-stack`], +/// but note that stack usage depends on optimization level and compiler flags. +/// +/// [`cargo-call-stack`]: https://github.com/japaric/cargo-call-stack +/// +/// Additionally, you must annotate your sensitive function with `#[inline(never)]`. +/// +/// For example, the following example **DOES NOT** erase stack properly: +/// ```ignore +/// pub fn encrypt_data(cipher: &Cipher, data: &mut [u8]) { +/// cipher.encrypt(data); +/// zeroize::zeroize_stack::<65_536>(); +/// } +/// ``` +/// The `cipher.encrypt` method may get inlined and `zeroize_stack` will erase +/// stack memory above stack frame reserved by `encrypt_data`. In other words, +/// it will **NOT** erase stack memory used by `cipher.encrypt`. +/// +/// You should wrap your computation in the following way: +/// ```ignore +/// #[inline(never)] +/// fn encrypt_data_inner(cipher: &Cipher, data: &mut [u8]) { +/// cipher.encrypt(data); +/// } +/// +/// pub fn encrypt_data(cipher: &Cipher, data: &mut [u8]) { +/// cipher.encrypt(data); +/// zeroize::zeroize_stack::<65_536>(); +/// } +/// ``` +#[inline(never)] +pub fn zeroize_stack() { + let buf = [0u8; N]; + crate::optimization_barrier(&buf); +} From 9c28ff7730de486b689fa47fedca7f642fe85b98 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: Wed, 7 Jan 2026 23:10:00 +0300 Subject: [PATCH 2/6] tweak docs --- zeroize/src/stack.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zeroize/src/stack.rs b/zeroize/src/stack.rs index e9ace6ee..13488863 100644 --- a/zeroize/src/stack.rs +++ b/zeroize/src/stack.rs @@ -38,6 +38,11 @@ /// zeroize::zeroize_stack::<65_536>(); /// } /// ``` +/// Finally, note that `#[inline(never)]` is just a hint and may be ignored +/// by the compiler. It works properly in practice, but such stack zeroization +/// should be considered as "best effort" and in cases where it's not enough +/// you should inspect the generated binary to verify that you got a desired +/// codegen. #[inline(never)] pub fn zeroize_stack() { let buf = [0u8; N]; From 6026357183b4169373a52c3f5b7d643ccc26d164 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: Wed, 7 Jan 2026 23:14:42 +0300 Subject: [PATCH 3/6] fmt --- zeroize/src/stack.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zeroize/src/stack.rs b/zeroize/src/stack.rs index 13488863..bf143643 100644 --- a/zeroize/src/stack.rs +++ b/zeroize/src/stack.rs @@ -14,7 +14,7 @@ /// [`cargo-call-stack`]: https://github.com/japaric/cargo-call-stack /// /// Additionally, you must annotate your sensitive function with `#[inline(never)]`. -/// +/// /// For example, the following example **DOES NOT** erase stack properly: /// ```ignore /// pub fn encrypt_data(cipher: &Cipher, data: &mut [u8]) { @@ -32,7 +32,7 @@ /// fn encrypt_data_inner(cipher: &Cipher, data: &mut [u8]) { /// cipher.encrypt(data); /// } -/// +/// /// pub fn encrypt_data(cipher: &Cipher, data: &mut [u8]) { /// cipher.encrypt(data); /// zeroize::zeroize_stack::<65_536>(); From b80c32482ef3bdc16acf8c917c4aad223424a40b 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: Wed, 7 Jan 2026 23:30:17 +0300 Subject: [PATCH 4/6] tweak examples --- zeroize/src/stack.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/zeroize/src/stack.rs b/zeroize/src/stack.rs index bf143643..959f3d2b 100644 --- a/zeroize/src/stack.rs +++ b/zeroize/src/stack.rs @@ -16,27 +16,29 @@ /// Additionally, you must annotate your sensitive function with `#[inline(never)]`. /// /// For example, the following example **DOES NOT** erase stack properly: -/// ```ignore -/// pub fn encrypt_data(cipher: &Cipher, data: &mut [u8]) { -/// cipher.encrypt(data); +/// ``` +/// pub fn encrypt_data(key: &[u8; 16], data: &mut [u8]) { +/// leaking_encryption(key, data); /// zeroize::zeroize_stack::<65_536>(); /// } +/// # fn leaking_encryption(_: &[u8; 16], _: &mut [u8]) {} /// ``` -/// The `cipher.encrypt` method may get inlined and `zeroize_stack` will erase +/// `leaking_encryption` may get inlined and `zeroize_stack` will erase /// stack memory above stack frame reserved by `encrypt_data`. In other words, -/// it will **NOT** erase stack memory used by `cipher.encrypt`. +/// it will **NOT** erase stack memory used by `leaking_encryption`. /// /// You should wrap your computation in the following way: -/// ```ignore +/// ``` /// #[inline(never)] -/// fn encrypt_data_inner(cipher: &Cipher, data: &mut [u8]) { -/// cipher.encrypt(data); +/// fn encrypt_data_inner(key: &[u8; 16], data: &mut [u8]) { +/// leaking_encryption(key, data); /// } /// -/// pub fn encrypt_data(cipher: &Cipher, data: &mut [u8]) { -/// cipher.encrypt(data); +/// pub fn encrypt_data(key: &[u8; 16], data: &mut [u8]) { +/// encrypt_data_inner(key, data); /// zeroize::zeroize_stack::<65_536>(); /// } +/// # fn leaking_encryption(_: &[u8; 16], _: &mut [u8]) {} /// ``` /// Finally, note that `#[inline(never)]` is just a hint and may be ignored /// by the compiler. It works properly in practice, but such stack zeroization From 7099615cb659e54408ecd21c0eeca5b58b899c42 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: Wed, 7 Jan 2026 23:35:17 +0300 Subject: [PATCH 5/6] tweak docs --- zeroize/src/stack.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zeroize/src/stack.rs b/zeroize/src/stack.rs index 959f3d2b..cd4875ca 100644 --- a/zeroize/src/stack.rs +++ b/zeroize/src/stack.rs @@ -1,7 +1,7 @@ /// Zeroize `N` bytes of stack space. /// /// Most algorithm implementations use stack to store temporary data. -/// This temporaries may contain sensitive information (e.g. cryptgraphic keys) +/// Such temporaries may contain sensitive information (e.g. cryptgraphic keys) /// and can stay on stack after the computation is finished. If an attacker /// is able for some reasons to read stack data freely, it may result in /// leaking of the sensitive data. @@ -24,7 +24,7 @@ /// # fn leaking_encryption(_: &[u8; 16], _: &mut [u8]) {} /// ``` /// `leaking_encryption` may get inlined and `zeroize_stack` will erase -/// stack memory above stack frame reserved by `encrypt_data`. In other words, +/// stack memory above the stack frame reserved by `encrypt_data`, i.e. /// it will **NOT** erase stack memory used by `leaking_encryption`. /// /// You should wrap your computation in the following way: From ee4fbfb7f53601df23975d45eb7dd869c3c0c270 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: Wed, 7 Jan 2026 23:36:39 +0300 Subject: [PATCH 6/6] Update changelog --- zeroize/CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zeroize/CHANGELOG.md b/zeroize/CHANGELOG.md index 13f4cba1..84f3ee27 100644 --- a/zeroize/CHANGELOG.md +++ b/zeroize/CHANGELOG.md @@ -7,12 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 1.9.0 (unreleased) ### Added - `optimization_barrier` function ([#1261]) +- `zeroize_stack` function ([#1331]) ### Changed - Edition changed to 2024 and MSRV bumped to 1.85 ([#1149]) [#1149]: https://github.com/RustCrypto/utils/pull/1149 [#1261]: https://github.com/RustCrypto/utils/pull/1261 +[#1331]: https://github.com/RustCrypto/utils/pull/1331 ## 1.8.2 (2025-09-29) ### Changed @@ -164,7 +166,7 @@ if you would like to support older Rust versions. ## 1.3.0 (2021-04-19) ### Added - impl `Zeroize` for `Box<[Z]>` -- Clear residual space within `Option +- Clear residual space within `Option` ### Changed - Ensure `Option` is `None` when zeroized @@ -172,7 +174,7 @@ if you would like to support older Rust versions. ## 1.2.0 (2020-12-09) ### Added -- `Zeroize` support for x86(_64) SIMD registers +- `Zeroize` support for x86(-64) SIMD registers ### Changed - Simplify `String::zeroize`