Skip to main content

starnix_stack/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use zx;
6
7unsafe extern "C" {
8    fn __get_unsafe_stack_ptr() -> usize;
9}
10
11/// Compute the safe stack pointer inside this function.
12///
13/// This must never be inlined to ensure the stack pointer is not the one for the previous
14/// function.
15#[inline(never)]
16fn get_safe_stack_ptr() -> Option<usize> {
17    let mut sp: usize;
18    unsafe {
19        #[cfg(target_arch = "x86_64")]
20        std::arch::asm!(
21            "mov {0}, rsp",
22            out(reg) sp,
23            options(nomem)
24        );
25        #[cfg(target_arch = "aarch64")]
26        std::arch::asm!(
27            "mov {0}, sp",
28            out(reg) sp,
29            options(nomem)
30        );
31        #[cfg(target_arch = "riscv64")]
32        std::arch::asm!(
33            "mv {0}, sp",
34            out(reg) sp,
35            options(nomem)
36        );
37    }
38    Some(sp)
39}
40
41/// Compute the safe stack pointer inside this function.
42///
43/// This can be inlined because it calls into C, so the underlying call will never be inlined.
44fn get_unsafe_stack_ptr() -> Option<usize> {
45    // Only x86_64 uses the unsafe stack. Other architecture use the shadow call stack that
46    // doesn't need to be cleaned up.
47    if cfg!(target_arch = "x86_64") { unsafe { Some(__get_unsafe_stack_ptr()) } } else { None }
48}
49
50pub fn clean_stack() {
51    // Stacks are 2Mo, clean 1Mo.
52    const CLEAN_SIZE: usize = 0x100000;
53    let page_size: usize = zx::system_get_page_size() as usize;
54    let stack_ptr_factories = [get_unsafe_stack_ptr, get_safe_stack_ptr];
55    for factory in stack_ptr_factories {
56        // Compute the pointers to the stack as a function call from this function to ensure the
57        // pointed memory is unused.
58        let Some(v) = factory() else {
59            continue;
60        };
61        // Afford some space on the stack for the op_range call.
62        let v = v.checked_sub(64).unwrap_or_else(|| {
63            panic!("Starnix kernel stack pointer underflow: v = {v}");
64        });
65        let max_addr = v - (v % page_size);
66        let start_addr = max_addr.checked_sub(CLEAN_SIZE).unwrap_or_else(|| {
67            panic!(
68                "Starnix kernel stack range calculation underflow: max_addr = {max_addr}, CLEAN_SIZE = {CLEAN_SIZE}"
69            );
70        });
71        let _ =
72            fuchsia_runtime::vmar_root_self().op_range(zx::VmarOp::ZERO, start_addr, CLEAN_SIZE);
73    }
74}
75
76#[cfg(test)]
77mod test {
78    use super::*;
79
80    #[inline(never)]
81    fn clean_stack_at_level(level: usize) -> usize {
82        if level == 0 {
83            clean_stack();
84            0
85        } else {
86            1 + clean_stack_at_level(level - 1)
87        }
88    }
89
90    #[::fuchsia::test]
91    fn test_clean_stack() {
92        let mut array = [0u8; 256];
93        for i in 0..256 {
94            array[i] = 255 - (i as u8);
95        }
96        for i in 0..4096 {
97            assert_eq!(clean_stack_at_level(i), i);
98        }
99        for i in 0..256 {
100            assert_eq!(array[i], 255 - (i as u8));
101        }
102    }
103}