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+ }
0 commit comments