redoubt_buffer/
page_buffer.rs

1// Copyright (c) 2025-2026 Federico Hoerth <memparanoid@gmail.com>
2// SPDX-License-Identifier: GPL-3.0-only
3// See LICENSE in the repository root for full license text.
4
5//! PageBuffer - High-level buffer over a protected Page.
6//!
7//! Provides open/open_mut access pattern with automatic protect/unprotect.
8//! On protection errors, the page is disposed and the process aborts.
9
10use crate::error::{BufferError, PageError};
11use crate::page::Page;
12use crate::traits::Buffer;
13
14/// Memory protection strategy for the buffer.
15#[derive(Debug, Clone, Copy, Eq, PartialEq)]
16pub enum ProtectionStrategy {
17    /// mlock + mprotect toggling (full protection)
18    MemProtected,
19    /// mlock only (no mprotect toggling)
20    MemNonProtected,
21}
22
23/// A buffer backed by a memory-locked page with optional memory protection.
24pub struct PageBuffer {
25    page: Page,
26    len: usize,
27    strategy: ProtectionStrategy,
28}
29
30impl PageBuffer {
31    fn abort(error: PageError) -> ! {
32        // Use libc::_exit to avoid any cleanup that might need blocked syscalls
33        #[cfg(test)]
34        std::process::exit(error as i32);
35
36        #[cfg(not(test))]
37        {
38            let _ = error;
39            unsafe { libc::abort() }
40        }
41    }
42
43    /// Creates a new PageBuffer with the specified protection strategy and length.
44    pub fn new(strategy: ProtectionStrategy, len: usize) -> Result<Self, PageError> {
45        let page = Page::new()?;
46
47        page.lock()?;
48        page.mark_dontdump()?;
49
50        if strategy == ProtectionStrategy::MemProtected {
51            page.protect()?;
52        }
53
54        Ok(Self {
55            page,
56            len,
57            strategy,
58        })
59    }
60
61    fn maybe_unprotect(&mut self) -> Result<(), PageError> {
62        if self.strategy == ProtectionStrategy::MemProtected {
63            self.page.unprotect()?;
64        }
65
66        Ok(())
67    }
68
69    fn maybe_protect(&mut self) -> Result<(), PageError> {
70        if self.strategy == ProtectionStrategy::MemProtected {
71            self.page.protect()?;
72        }
73
74        Ok(())
75    }
76
77    fn try_open(
78        &mut self,
79        f: &mut dyn FnMut(&[u8]) -> Result<(), BufferError>,
80    ) -> Result<(), BufferError> {
81        self.maybe_unprotect()?;
82
83        let slice = unsafe { self.page.as_slice() };
84        f(&slice[..self.len])?;
85
86        self.maybe_protect()?;
87
88        Ok(())
89    }
90
91    fn try_open_mut(
92        &mut self,
93        f: &mut dyn FnMut(&mut [u8]) -> Result<(), BufferError>,
94    ) -> Result<(), BufferError> {
95        self.maybe_unprotect()?;
96
97        let slice = unsafe { self.page.as_mut_slice() };
98        f(&mut slice[..self.len])?;
99
100        self.maybe_protect()?;
101
102        Ok(())
103    }
104
105    /// Returns true if the buffer has zero length.
106    pub fn is_empty(&self) -> bool {
107        self.len == 0
108    }
109
110    /// Disposes of the underlying page, releasing all resources.
111    pub fn dispose(&mut self) {
112        self.page.dispose();
113    }
114}
115
116// Safety: PageBuffer can be shared between threads (though mutation requires &mut)
117unsafe impl Sync for PageBuffer {}
118
119impl core::fmt::Debug for PageBuffer {
120    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121        f.debug_struct("PageBuffer")
122            .field("len", &self.len)
123            .field("strategy", &self.strategy)
124            .finish_non_exhaustive()
125    }
126}
127
128impl Buffer for PageBuffer {
129    #[inline(always)]
130    fn open(
131        &mut self,
132        f: &mut dyn FnMut(&[u8]) -> Result<(), BufferError>,
133    ) -> Result<(), BufferError> {
134        let result = self.try_open(f);
135
136        if let Err(BufferError::Page(e)) = &result {
137            self.page.dispose();
138            Self::abort(*e);
139        }
140
141        result
142    }
143
144    #[inline(always)]
145    fn open_mut(
146        &mut self,
147        f: &mut dyn FnMut(&mut [u8]) -> Result<(), BufferError>,
148    ) -> Result<(), BufferError> {
149        let result = self.try_open_mut(f);
150
151        if let Err(BufferError::Page(e)) = &result {
152            self.page.dispose();
153            Self::abort(*e);
154        }
155
156        result
157    }
158
159    fn len(&self) -> usize {
160        self.len
161    }
162}