stacker/
mmap_stack_restore_guard.rs

1use crate::{get_stack_limit, set_stack_limit};
2
3pub struct StackRestoreGuard {
4    mapping: *mut u8,
5    size_with_guard: usize,
6    page_size: usize,
7    old_stack_limit: Option<usize>,
8}
9
10impl StackRestoreGuard {
11    pub fn new(requested_size: usize) -> StackRestoreGuard {
12        // For maximum portability we want to produce a stack that is aligned to a page and has
13        // a size that’s a multiple of page size. It is natural to use mmap to allocate
14        // these pages. Furthermore, we want to allocate two extras pages for the stack guard.
15        // To achieve that we do our calculations in number of pages and convert to bytes last.
16        let page_size = page_size();
17        let requested_pages = requested_size
18            .checked_add(page_size - 1)
19            .expect("unreasonably large stack requested")
20            / page_size;
21        let page_count_with_guard = std::cmp::max(1, requested_pages) + 2;
22        let size_with_guard = page_count_with_guard
23            .checked_mul(page_size)
24            .expect("unreasonably large stack requested");
25
26        unsafe {
27            let new_stack = libc::mmap(
28                std::ptr::null_mut(),
29                size_with_guard,
30                libc::PROT_NONE,
31                libc::MAP_PRIVATE | libc::MAP_ANON,
32                -1, // Some implementations assert fd = -1 if MAP_ANON is specified
33                0,
34            );
35            assert_ne!(
36                new_stack,
37                libc::MAP_FAILED,
38                "mmap failed to allocate stack: {}",
39                std::io::Error::last_os_error()
40            );
41            let guard = StackRestoreGuard {
42                mapping: new_stack as *mut u8,
43                page_size,
44                size_with_guard,
45                old_stack_limit: get_stack_limit(),
46            };
47            // We leave a guard page without read/write access in our allocation.
48            // There is one guard page below the stack and another above it.
49            let above_guard_page = new_stack.add(page_size);
50            #[cfg(not(target_os = "openbsd"))]
51            let result = libc::mprotect(
52                above_guard_page,
53                size_with_guard - page_size,
54                libc::PROT_READ | libc::PROT_WRITE,
55            );
56            #[cfg(target_os = "openbsd")]
57            let result = if libc::mmap(
58                above_guard_page,
59                size_with_guard - page_size,
60                libc::PROT_READ | libc::PROT_WRITE,
61                libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANON | libc::MAP_STACK,
62                -1,
63                0,
64            ) == above_guard_page
65            {
66                0
67            } else {
68                -1
69            };
70            assert_ne!(
71                result,
72                -1,
73                "mprotect/mmap failed: {}",
74                std::io::Error::last_os_error()
75            );
76            guard
77        }
78    }
79
80    // TODO this should return a *mut [u8], but pointer slices only got proper support with Rust 1.79.
81    pub fn stack_area(&self) -> (*mut u8, usize) {
82        unsafe {
83            (
84                self.mapping.add(self.page_size),
85                self.size_with_guard - self.page_size,
86            )
87        }
88    }
89}
90
91impl Drop for StackRestoreGuard {
92    fn drop(&mut self) {
93        unsafe {
94            // FIXME: check the error code and decide what to do with it.
95            // Perhaps a debug_assertion?
96            libc::munmap(self.mapping as *mut std::ffi::c_void, self.size_with_guard);
97        }
98        set_stack_limit(self.old_stack_limit);
99    }
100}
101
102fn page_size() -> usize {
103    // FIXME: consider caching the page size.
104    unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize }
105}