Skip to content

Commit 560ce88

Browse files
committed
move stack_sanitization.rs to its own crate
1 parent 9719738 commit 560ce88

4 files changed

Lines changed: 237 additions & 0 deletions

File tree

‎stack_sanitizer/Cargo.toml‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "stack_sanitizer"
3+
version = "0.1.0"
4+
description = """
5+
Securely sanitize the stack with a simple function built on
6+
the Portable Stack Manipulation (psm) crate.
7+
"""
8+
authors = ["The RustCrypto Project Developers"]
9+
license = "Apache-2.0 OR MIT"
10+
homepage = "https://github.com/RustCrypto/utils/tree/master/stack_sanitizer"
11+
repository = "https://github.com/RustCrypto/utils"
12+
readme = "README.md"
13+
categories = ["cryptography", "memory-management", "no-std", "os"]
14+
keywords = ["memory", "memset", "secure", "volatile", "zero", "stack"]
15+
edition = "2024"
16+
rust-version = "1.85"
17+
18+
[dependencies]
19+
psm = { version = "0.1.26", optional = true }
20+
zeroize = { version = "1.0" }
21+
22+
[features]
23+
24+
[package.metadata.docs.rs]
25+
all-features = true

‎stack_sanitizer/README.md‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# [RustCrypto]: stack_sanitizer
2+
3+
[![Crate][crate-image]][crate-link]
4+
[![Docs][docs-image]][docs-link]
5+
![Apache 2.0/MIT Licensed][license-image]
6+
![MSRV][rustc-image]
7+
[![Build Status][build-image]][build-link]
8+
9+
Securely zero the stack (a.k.a. [zeroize]) while avoiding compiler optimizations.
10+
11+
This crate implements a portable approach to securely zeroing the stack using
12+
techniques which guarantee they won't be "optimized away" by the compiler.
13+
14+
[Documentation]
15+
16+
## About
17+
18+
[Zeroing memory securely is hard] - compilers optimize for performance, and
19+
in doing so they love to "optimize away" unnecessary zeroing calls, as well
20+
as make extra copies of data on the stack that cannot be easily zeroed. That's
21+
what this crate is for.
22+
23+
This crate isn't about tricks: it uses [psm::on_stack] to run a function on
24+
a portable stack, and then uses [zeroize] to zero the stack. `psm` implements
25+
all of the assembly for several different architectures, whereas the [zeroize]
26+
segment was implemented in pure Rust.
27+
28+
- `#![no_std]` i.e. **embedded-friendly**! (`alloc` is required)
29+
- No functionality besides securely zeroing the a function's stack usage!
30+
31+
## License
32+
33+
Licensed under either of:
34+
35+
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
36+
* [MIT license](http://opensource.org/licenses/MIT)
37+
38+
at your option.
39+
40+
### Contribution
41+
42+
Unless you explicitly state otherwise, any contribution intentionally submitted
43+
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
44+
dual licensed as above, without any additional terms or conditions.
45+
46+
[//]: # (badges)
47+
48+
[crate-image]: https://img.shields.io/crates/v/zeroize.svg
49+
[crate-link]: https://crates.io/crates/zeroize
50+
[docs-image]: https://docs.rs/zeroize/badge.svg
51+
[docs-link]: https://docs.rs/zeroize/
52+
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
53+
[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg
54+
[build-image]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml/badge.svg?branch=master
55+
[build-link]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml?query=branch:master
56+
57+
[//]: # (general links)
58+
59+
[RustCrypto]: https://github.com/RustCrypto
60+
[zeroize]: https://en.wikipedia.org/wiki/Zeroisation
61+
[`Zeroize` trait]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
62+
[Documentation]: https://docs.rs/zeroize/
63+
[Zeroing memory securely is hard]: http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html
64+
[psm::on_stack]: https://docs.rs/psm/latest/psm/fn.on_stack.html
65+
[good cryptographic hygiene]: https://github.com/veorq/cryptocoding#clean-memory-of-secret-data

‎stack_sanitizer/src/lib.rs‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! # stack_bleach
2+
//!
3+
//! A crate for sanitizing stack memory after sensitive operations—sometimes referred to as _Stack Bleaching_.
4+
//!
5+
//! Modern compilers and CPUs routinely copy, spill, and rearrange data during execution. Even if sensitive values are scoped to a function, they may:
6+
//! - Be duplicated across multiple stack frames
7+
//! - Be spilled from registers to the stack during register pressure
8+
//! - Persist in memory long after the function returns
9+
//!
10+
//! This crate provides tools to explicitly zeroize stack regions used during
11+
//! cryptographic or sensitive computations, helping mitigate:
12+
//! - Leakage through stack inspection or memory dumps
13+
//! - Residual data from compiler-inserted spills
14+
//! - ABI-visible register reuse across function boundaries
15+
//!
16+
//! ## Why Stack Sanitization Matters
17+
//!
18+
//! Unlike heap memory, stack allocations are ephemeral and compiler-controlled.
19+
//! Sensitive data may be:
20+
//! - Copied implicitly by the optimizer
21+
//! - Stored temporarily during register allocation
22+
//! - Left behind in stack frames even after function return
23+
//!
24+
//! This crate offers abstractions for:
25+
//! - Executing functions on isolated, aligned stack buffers
26+
//! - Zeroizing stack memory after execution
27+
//!
28+
//! ## Safety
29+
//!
30+
//! These operations involve low-level stack manipulation and unsafe code. The
31+
//! caller must ensure:
32+
//! - The stack size provided is large enough for the closure to run with.
33+
//! - The closure does not unwind or return control flow by any means other than
34+
//! directly returning.
35+
//!
36+
//! ## Use Cases
37+
//!
38+
//! - Cryptographic routines
39+
//! - Secure enclave transitions
40+
//! - Sanitizing temporary buffers in high-assurance systems
41+
42+
use psm::on_stack;
43+
44+
use zeroize::Zeroize;
45+
46+
extern crate alloc;
47+
48+
use alloc::{
49+
vec,
50+
vec::{Vec}
51+
};
52+
53+
/// Executes a function/closure and clears the function's stack frames by using
54+
/// preallocated space on the heap as the function's stack, and then zeroing
55+
/// that allocated data once the code has ran.
56+
///
57+
/// This function does not clear the CPU registers.
58+
///
59+
/// # Arguments
60+
///
61+
/// * `stack_size_kb` - how large the stack will be. `psm` recommends at least
62+
/// `4 KB` of stack size, but the total size cannot overflow an `isize`. Also,
63+
/// some architectures might consume more memory in the stack, such as SPARC.
64+
/// * `crypto_fn` - the code to run while on separate stack.
65+
///
66+
/// # Safety
67+
///
68+
/// * `crypto_fn` should be marked as `#[inline(never)]`, preventing register
69+
/// reuse and stack layout changes.
70+
/// * The stack needs to be large enough for `crypto_fn()` to execute without
71+
/// overflow.
72+
/// * `crypto_fn()` must not unwind or return control flow by any other means
73+
/// than by directly returning.
74+
pub unsafe fn exec_on_sanitized_stack<F, R>(stack_size_kb: isize, crypto_fn: F) -> R
75+
where
76+
F: FnOnce() -> R,
77+
{
78+
assert!(stack_size_kb * 1024 > 0, "Stack size must be greater than 0 kb and `* 1024` must not overflow `isize`");
79+
let mut stack = create_aligned_vec(stack_size_kb as usize, core::mem::align_of::<u128>());
80+
let res = unsafe {
81+
on_stack(stack.as_mut_ptr(), stack.len(), || {
82+
let res = crypto_fn();
83+
res
84+
})
85+
};
86+
stack.zeroize();
87+
res
88+
}
89+
90+
/// Round up to the nearest multiple of alignment
91+
const fn align_up(value: usize, alignment: usize) -> usize {
92+
(value + alignment - 1) & !(alignment - 1)
93+
}
94+
95+
/// Creates an aligned Vec<u8> with the specified size in KB and alignment.
96+
///
97+
/// This helps ensure that the safety requirements are met when using
98+
/// `fn secure_crypto_call_heap()`.
99+
///
100+
/// Both the data pointer and length will be aligned to the specified boundary.
101+
fn create_aligned_vec(size_kb: usize, alignment: usize) -> Vec<u8> {
102+
let size_bytes = size_kb * 1024;
103+
// checking one of the safety conditions of `psm::on_stack()`
104+
assert!(size_bytes <= isize::MAX as usize);
105+
106+
let aligned_size = align_up(size_bytes, alignment);
107+
108+
// Allocate extra space to ensure we can find an aligned region
109+
let mut vec = vec![0u8; aligned_size + alignment];
110+
111+
// Find the aligned position within the vec
112+
let ptr_addr = vec.as_ptr() as usize;
113+
let aligned_addr = align_up(ptr_addr, alignment);
114+
let offset = aligned_addr - ptr_addr;
115+
116+
// Remove elements from the beginning to align the start
117+
vec.drain(0..offset);
118+
119+
// Truncate to the exact aligned size we want
120+
vec.truncate(aligned_size);
121+
122+
// Verify alignment (these will be optimized out in release builds)
123+
debug_assert_eq!(vec.as_ptr() as usize % alignment, 0);
124+
debug_assert_eq!(vec.len() % alignment, 0);
125+
debug_assert_eq!(vec.len(), aligned_size);
126+
127+
vec
128+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! Stack sanitization integration tests
2+
3+
mod stack_sanitization_tests {
4+
use stack_sanitizer::exec_on_sanitized_stack;
5+
6+
fn dummy_fn() -> (*const u8, u64) {
7+
let temporary_data = 42;
8+
let ptr = temporary_data as *const u8;
9+
(ptr, 12345)
10+
}
11+
12+
#[test]
13+
fn stack_sanitization_v2() {
14+
let result = unsafe { exec_on_sanitized_stack(4, || dummy_fn())};
15+
assert_eq!(result.1, 12345);
16+
// results in segmentation fault
17+
// assert_eq!(unsafe {*result.0}, 42);
18+
}
19+
}

0 commit comments

Comments
 (0)