Skip to main content

memlink_shm/
safety.rs

1//! Memory safety utilities: bounds checking, panic guards, and safe access wrappers.
2
3use std::cell::Cell;
4use std::panic::{self, AssertUnwindSafe};
5use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
6use std::sync::Arc;
7
8pub struct BoundsChecker {
9    size: usize,
10    base: usize,
11}
12
13impl BoundsChecker {
14    pub fn new(base: *const u8, size: usize) -> Self {
15        Self {
16            size,
17            base: base as usize,
18        }
19    }
20
21    pub fn is_valid_offset(&self, offset: usize) -> bool {
22        offset < self.size
23    }
24
25    pub fn is_valid_pointer(&self, ptr: *const u8) -> bool {
26        let addr = ptr as usize;
27        addr >= self.base && addr < self.base + self.size
28    }
29
30    pub fn validate_offset(&self, offset: usize) -> Result<usize, BoundsError> {
31        if !self.is_valid_offset(offset) {
32            return Err(BoundsError::OutOfBounds {
33                offset,
34                size: self.size,
35            });
36        }
37        Ok(offset)
38    }
39
40    pub fn validate_range(
41        &self,
42        offset: usize,
43        len: usize,
44    ) -> Result<(), BoundsError> {
45        if offset >= self.size {
46            return Err(BoundsError::OutOfBounds {
47                offset,
48                size: self.size,
49            });
50        }
51
52        let end = offset.checked_add(len).ok_or(BoundsError::Overflow)?;
53        if end > self.size {
54            return Err(BoundsError::OutOfBounds {
55                offset: end,
56                size: self.size,
57            });
58        }
59
60        Ok(())
61    }
62
63    pub fn size(&self) -> usize {
64        self.size
65    }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum BoundsError {
70    OutOfBounds { offset: usize, size: usize },
71    Overflow,
72}
73
74impl std::fmt::Display for BoundsError {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        match self {
77            BoundsError::OutOfBounds { offset, size } => {
78                write!(f, "Offset {} is out of bounds (size {})", offset, size)
79            }
80            BoundsError::Overflow => write!(f, "Arithmetic overflow in offset calculation"),
81        }
82    }
83}
84
85impl std::error::Error for BoundsError {}
86
87pub struct PoisonGuard {
88    poisoned: Arc<AtomicBool>,
89    disarmed: Cell<bool>,
90}
91
92impl PoisonGuard {
93    pub fn new(poisoned: Arc<AtomicBool>) -> Self {
94        poisoned.store(true, Ordering::Release);
95        Self {
96            poisoned,
97            disarmed: Cell::new(false),
98        }
99    }
100
101    pub fn disarm(&self) {
102        self.disarmed.set(true);
103        self.poisoned.store(false, Ordering::Release);
104    }
105
106    pub fn is_poisoned(&self) -> bool {
107        self.poisoned.load(Ordering::Acquire)
108    }
109}
110
111impl Drop for PoisonGuard {
112    fn drop(&mut self) {
113        if !self.disarmed.get() {
114        }
115    }
116}
117
118pub struct PoisonState {
119    poisoned: Arc<AtomicBool>,
120}
121
122impl PoisonState {
123    pub fn new() -> Self {
124        Self {
125            poisoned: Arc::new(AtomicBool::new(false)),
126        }
127    }
128
129    pub fn is_poisoned(&self) -> bool {
130        self.poisoned.load(Ordering::Acquire)
131    }
132
133    pub fn set_poisoned(&self) {
134        self.poisoned.store(true, Ordering::Release);
135    }
136
137    pub fn clear_poisoned(&self) {
138        self.poisoned.store(false, Ordering::Release);
139    }
140
141    pub fn guard(&self) -> PoisonGuard {
142        PoisonGuard::new(Arc::clone(&self.poisoned))
143    }
144}
145
146impl Default for PoisonState {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152pub fn with_panic_protection<T, F>(
153    poison: &PoisonState,
154    f: F,
155) -> Result<T, Box<dyn std::any::Any + Send>>
156where
157    F: FnOnce() -> T,
158{
159    let guard = poison.guard();
160    let result = panic::catch_unwind(AssertUnwindSafe(f));
161
162    match result {
163        Ok(val) => {
164            guard.disarm();
165            Ok(val)
166        }
167        Err(e) => {
168            poison.set_poisoned();
169            Err(e)
170        }
171    }
172}
173
174pub struct RobustFutex {
175    value: AtomicUsize,
176    poisoned: AtomicBool,
177}
178
179impl RobustFutex {
180    pub fn new(initial: usize) -> Self {
181        Self {
182            value: AtomicUsize::new(initial),
183            poisoned: AtomicBool::new(false),
184        }
185    }
186
187    pub fn try_lock(&self) -> Result<bool, FutexError> {
188        if self.poisoned.load(Ordering::Acquire) {
189            return Err(FutexError::Poisoned);
190        }
191
192        let current = self.value.load(Ordering::Acquire);
193        if current == 0 {
194            match self.value.compare_exchange(
195                0,
196                1,
197                Ordering::AcqRel,
198                Ordering::Acquire,
199            ) {
200                Ok(_) => Ok(true),
201                Err(_) => Ok(false),
202            }
203        } else {
204            Ok(false)
205        }
206    }
207
208    pub fn unlock(&self) {
209        self.value.store(0, Ordering::Release);
210    }
211
212    pub fn is_poisoned(&self) -> bool {
213        self.poisoned.load(Ordering::Acquire)
214    }
215
216    pub fn mark_poisoned(&self) {
217        self.poisoned.store(true, Ordering::Release);
218    }
219
220    pub fn clear_poisoned(&self) {
221        self.poisoned.store(false, Ordering::Release);
222        self.value.store(0, Ordering::Release);
223    }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub enum FutexError {
228    Poisoned,
229    WouldBlock,
230    Other,
231}
232
233impl std::fmt::Display for FutexError {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        match self {
236            FutexError::Poisoned => write!(f, "Futex is poisoned (owner died)"),
237            FutexError::WouldBlock => write!(f, "Operation would block"),
238            FutexError::Other => write!(f, "Futex error"),
239        }
240    }
241}
242
243impl std::error::Error for FutexError {}
244
245pub struct SafeShmAccess {
246    bounds: BoundsChecker,
247    poison: PoisonState,
248}
249
250impl SafeShmAccess {
251    pub fn new(base: *const u8, size: usize) -> Self {
252        Self {
253            bounds: BoundsChecker::new(base, size),
254            poison: PoisonState::new(),
255        }
256    }
257
258    pub fn is_poisoned(&self) -> bool {
259        self.poison.is_poisoned()
260    }
261
262    pub fn validate_offset(&self, offset: usize) -> Result<(), BoundsError> {
263        self.bounds.validate_offset(offset).map(|_| ())
264    }
265
266    pub fn validate_range(&self, offset: usize, len: usize) -> Result<(), BoundsError> {
267        self.bounds.validate_range(offset, len)
268    }
269
270    pub fn with_safe_access<T, F>(&self, offset: usize, len: usize, f: F) -> Result<T, SafeAccessError>
271    where
272        F: FnOnce() -> T,
273    {
274        self.bounds.validate_range(offset, len)?;
275
276        if self.poison.is_poisoned() {
277            return Err(SafeAccessError::Poisoned);
278        }
279
280        match with_panic_protection(&self.poison, f) {
281            Ok(val) => Ok(val),
282            Err(_) => Err(SafeAccessError::Panicked),
283        }
284    }
285
286    pub fn bounds(&self) -> &BoundsChecker {
287        &self.bounds
288    }
289
290    pub fn poison(&self) -> &PoisonState {
291        &self.poison
292    }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum SafeAccessError {
297    OutOfBounds,
298    Poisoned,
299    Panicked,
300}
301
302impl std::fmt::Display for SafeAccessError {
303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304        match self {
305            SafeAccessError::OutOfBounds => write!(f, "Access out of bounds"),
306            SafeAccessError::Poisoned => write!(f, "Access is poisoned"),
307            SafeAccessError::Panicked => write!(f, "Operation panicked"),
308        }
309    }
310}
311
312impl std::error::Error for SafeAccessError {}
313
314impl From<BoundsError> for SafeAccessError {
315    fn from(_: BoundsError) -> Self {
316        SafeAccessError::OutOfBounds
317    }
318}