Skip to content

Commit ba7f0f4

Browse files
committed
added AlignedHeapStack to reuse stack space in sequential calls; see marked out TODO task for details; next up: better panics
1 parent d665300 commit ba7f0f4

3 files changed

Lines changed: 89 additions & 19 deletions

File tree

‎zeroize_stack/TODO.md‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ Copilot provided that code, but Gemini says that after the future is awaited, th
3030

3131
## Safe
3232

33-
* Allow stack reuse. More efficient to zero one stack shared by multiple functions. `impl Drop` and `ZeroizeOnDrop` and make the main public function only accept a mutable `HeapStack` struct, and allow for the stack to get zeroed on drop.
34-
3533
* Panic when the OS is `hermit` or it is running on `wasm32` or `wasm64`, as their stacks don't behave the same as all of the others.
3634

3735
* Handle unwinds better: currently we return a `Result<R, Box<dyn Any + Send>>`. The error case is a little bit tricky to handle, as dropping the error could cause a panic. The program should either panic, or return the panic payload's message.

‎zeroize_stack/src/lib.rs‎

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
//! - Secure enclave transitions
5656
//! - Sanitizing temporary buffers in high-assurance systems
5757
58-
use zeroize::Zeroize;
58+
use zeroize::{Zeroize, ZeroizeOnDrop};
5959

6060
extern crate alloc;
6161

@@ -74,6 +74,55 @@ type StackSwitchResult<T> = T;
7474

7575
use core::panic::{AssertUnwindSafe, UnwindSafe};
7676

77+
/// An aligned HeapStack. Aligned to the alignment of a `u128`, and aligned using
78+
/// safe code instead of manual alloc calls. This implements `ZeroizeOnDrop` and
79+
/// contains a lock flag to prevent the stack from being reused while it is being
80+
/// used.
81+
pub struct AlignedHeapStack {
82+
locked: bool,
83+
stack: Vec<u8>,
84+
}
85+
86+
impl AlignedHeapStack {
87+
/// Creates a new `AlignedHeapStack`. `psm` recommends using at least `4 KB`
88+
/// of stack space.
89+
///
90+
/// # Panics
91+
///
92+
/// This function panics when `size_kb * 1024` overflows `isize`.
93+
pub fn new(size_kb: usize) -> Self {
94+
assert!(
95+
size_kb as isize * 1024 > 0,
96+
"size_kb must be positive and must not overflow isize when expanded to number of bytes instead of kb"
97+
);
98+
let result = Self {
99+
locked: false,
100+
stack: create_aligned_vec(size_kb, align_of::<u128>()),
101+
};
102+
// these may be redundant but I just want to be sure that the alignment doesn't
103+
// change somehow
104+
debug_assert_eq!(result.stack.as_ptr() as usize % align_of::<u128>(), 0);
105+
debug_assert_eq!(result.stack.len() % align_of::<u128>(), 0);
106+
result
107+
}
108+
109+
fn is_locked(&self) -> bool {
110+
self.locked
111+
}
112+
113+
fn set_lock(&mut self, locked: bool) {
114+
self.locked = locked;
115+
}
116+
}
117+
118+
impl Drop for AlignedHeapStack {
119+
fn drop(&mut self) {
120+
self.stack.zeroize();
121+
}
122+
}
123+
124+
impl ZeroizeOnDrop for AlignedHeapStack {}
125+
77126
psm::psm_stack_manipulation! {
78127
yes {
79128
/// Executes a function/closure and clears the function's stack by using
@@ -84,9 +133,11 @@ psm::psm_stack_manipulation! {
84133
///
85134
/// # Arguments
86135
///
87-
/// * `stack_size_kb` - how large the stack will be. `psm` recommends at least
88-
/// `4 KB` of stack size, but the total size cannot overflow an `isize`. Also,
89-
/// some architectures might consume more memory in the stack, such as SPARC.
136+
/// * `aligned_heap_stack` - the heap-based aligned region of memory to
137+
/// be used as the stack. `psm` recommends at least `4 KB` of stack
138+
/// space, but the total size cannot overflow an `isize`. Also,
139+
/// some architectures might consume more memory in the stack, such as
140+
/// SPARC.
90141
/// * `crypto_fn` - the code to run while on the separate stack.
91142
///
92143
/// ## Panicking
@@ -106,28 +157,24 @@ psm::psm_stack_manipulation! {
106157
/// make it easier to debug as the function(s) should show up in backtraces.
107158
///
108159
/// # Returns
109-
///
160+
///
110161
/// * If `std` is enabled, this returns a `Result<R, Box<dyn Any + Send>>`
111162
/// * Otherwise, this returns `R` directly; no panics are caught.
112-
///
163+
///
113164
/// # Safety
114165
///
115166
/// * The stack needs to be large enough for `crypto_fn()` to execute without
116167
/// overflow.
117168
/// * `nostd` only: `crypto_fn()` must not unwind or return control flow by any other means
118169
/// than by directly returning.
119-
pub unsafe fn exec_on_sanitized_stack<F, R>(stack_size_kb: isize, crypto_fn: F) -> StackSwitchResult<R>
170+
pub unsafe fn exec_on_sanitized_stack<F, R>(aligned_heap_stack: &mut AlignedHeapStack, crypto_fn: F) -> StackSwitchResult<R>
120171
where
121172
F: FnOnce() -> R + UnwindSafe,
122173
{
123-
assert!(
124-
stack_size_kb * 1024 > 0,
125-
"Stack size must be greater than 0 kb and `* 1024` must not overflow `isize`"
126-
);
127-
let mut stack = create_aligned_vec(stack_size_kb as usize, align_of::<u128>());
128-
174+
assert!(!aligned_heap_stack.is_locked(), "AlignedHeapStack was locked. You must not use it while it is being used already!");
175+
aligned_heap_stack.set_lock(true);
129176
let res = unsafe {
130-
psm::on_stack(stack.as_mut_ptr(), stack.len(), || {
177+
psm::on_stack(aligned_heap_stack.stack.as_mut_ptr(), aligned_heap_stack.stack.len(), || {
131178
#[cfg(not(feature = "std"))]
132179
{
133180
crypto_fn()
@@ -138,7 +185,7 @@ psm::psm_stack_manipulation! {
138185
}
139186
})
140187
};
141-
stack.zeroize();
188+
aligned_heap_stack.set_lock(false);
142189
res
143190
}
144191
}

‎zeroize_stack/tests/zeroize_stack.rs‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Stack sanitization integration tests
22
33
mod stack_sanitization_tests {
4-
use zeroize_stack::exec_on_sanitized_stack;
4+
use std::panic::AssertUnwindSafe;
5+
6+
use zeroize_stack::{AlignedHeapStack, exec_on_sanitized_stack};
57

68
#[inline(never)]
79
fn dummy_fn() -> (*const u8, u64) {
@@ -12,10 +14,33 @@ mod stack_sanitization_tests {
1214

1315
#[test]
1416
fn stack_sanitization_v2() {
15-
let result = unsafe { exec_on_sanitized_stack(4, || dummy_fn()) };
17+
let mut heap_stack = AlignedHeapStack::new(4);
18+
let result = unsafe { exec_on_sanitized_stack(&mut heap_stack, || dummy_fn()) };
1619
assert_eq!(result.unwrap().1, 12345);
1720
// results in segmentation fault, which is somewhat normal... just wanted
1821
// to try it
1922
// assert_eq!(unsafe {*result.0}, 42);
2023
}
24+
25+
#[test]
26+
fn allow_stack_reuse_between_calls() {
27+
let mut heap_stack = AlignedHeapStack::new(4);
28+
let result_1 = unsafe { exec_on_sanitized_stack(&mut heap_stack, || dummy_fn()) };
29+
assert!(result_1.is_ok());
30+
assert_eq!(result_1.unwrap().1, 12345);
31+
let result_2 = unsafe { exec_on_sanitized_stack(&mut heap_stack, || dummy_fn()) };
32+
assert!(result_2.is_ok());
33+
assert_eq!(result_2.unwrap().1, 12345);
34+
}
35+
36+
fn non_returning_function(v: &mut u32) {
37+
*v += 5;
38+
}
39+
#[test]
40+
fn non_returning_function_test() {
41+
let mut heap_stack = AlignedHeapStack::new(4);
42+
let mut v = 0;
43+
unsafe { exec_on_sanitized_stack(&mut heap_stack, AssertUnwindSafe(|| non_returning_function(&mut v)))}.unwrap();
44+
assert_eq!(v, 5);
45+
}
2146
}

0 commit comments

Comments
 (0)