use crate::error::{StatorError, StatorResult};
pub const DEFAULT_SANDBOX_SIZE: usize = 1 << 30;
pub struct Sandbox {
base: *mut u8,
size: usize,
}
unsafe impl Send for Sandbox {}
unsafe impl Sync for Sandbox {}
impl Sandbox {
pub fn new(size: usize) -> StatorResult<Self> {
assert!(size > 0, "sandbox size must be non-zero");
let base = reserve(size)?;
Ok(Self { base, size })
}
pub fn new_default() -> StatorResult<Self> {
Self::new(DEFAULT_SANDBOX_SIZE)
}
#[inline]
pub fn base(&self) -> *mut u8 {
self.base
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
#[inline]
pub fn contains(&self, ptr: *const u8) -> bool {
let addr = ptr as usize;
let base = self.base as usize;
addr >= base && addr < base.saturating_add(self.size)
}
#[inline]
pub fn check_in_bounds(&self, ptr: *const u8) -> StatorResult<()> {
if self.contains(ptr) {
Ok(())
} else {
Err(StatorError::SandboxViolation {
address: ptr as usize,
sandbox_base: self.base as usize,
sandbox_size: self.size,
})
}
}
}
impl Drop for Sandbox {
fn drop(&mut self) {
if !self.base.is_null() && self.size > 0 {
unsafe { release(self.base, self.size) };
}
}
}
#[cfg(unix)]
fn reserve(size: usize) -> StatorResult<*mut u8> {
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_NONE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
if ptr == libc::MAP_FAILED || ptr.is_null() {
Err(StatorError::OutOfMemory)
} else {
Ok(ptr as *mut u8)
}
}
#[cfg(not(unix))]
fn reserve(size: usize) -> StatorResult<*mut u8> {
use std::alloc::{Layout, alloc};
let layout = Layout::from_size_align(size, 8).map_err(|_| StatorError::OutOfMemory)?;
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
Err(StatorError::OutOfMemory)
} else {
Ok(ptr)
}
}
#[cfg(unix)]
unsafe fn release(base: *mut u8, size: usize) {
unsafe { libc::munmap(base as *mut libc::c_void, size) };
}
#[cfg(not(unix))]
unsafe fn release(base: *mut u8, size: usize) {
use std::alloc::{Layout, dealloc};
let layout = Layout::from_size_align(size, 8).expect("valid layout");
unsafe { dealloc(base, layout) };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExternalPointerHandle(u32);
impl ExternalPointerHandle {
pub const NULL: Self = Self(0);
#[inline]
pub fn is_null(self) -> bool {
self.0 == 0
}
}
pub struct ExternalPointerTable {
entries: Vec<Option<*mut ()>>,
free_list: Vec<u32>,
}
unsafe impl Send for ExternalPointerTable {}
impl ExternalPointerTable {
pub fn new() -> Self {
Self {
entries: vec![None],
free_list: Vec::new(),
}
}
pub fn insert(&mut self, ptr: *mut ()) -> ExternalPointerHandle {
let idx = if let Some(recycled) = self.free_list.pop() {
self.entries[recycled as usize] = Some(ptr);
recycled
} else {
let next = self.entries.len();
assert!(
next <= u32::MAX as usize,
"ExternalPointerTable overflow: cannot allocate more than u32::MAX entries"
);
self.entries.push(Some(ptr));
next as u32
};
ExternalPointerHandle(idx)
}
pub fn get(&self, handle: ExternalPointerHandle) -> Option<*mut ()> {
if handle.is_null() {
return None;
}
self.entries.get(handle.0 as usize).and_then(|slot| *slot)
}
pub fn remove(&mut self, handle: ExternalPointerHandle) -> Option<*mut ()> {
if handle.is_null() {
return None;
}
let slot = self.entries.get_mut(handle.0 as usize)?;
let ptr = slot.take();
if ptr.is_some() {
self.free_list.push(handle.0);
}
ptr
}
pub fn len(&self) -> usize {
self.entries.iter().skip(1).filter(|s| s.is_some()).count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Default for ExternalPointerTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_SANDBOX_SIZE: usize = 4 * 1024 * 1024;
#[test]
fn test_sandbox_contains_base_address() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
assert!(sb.contains(sb.base()));
}
#[test]
fn test_sandbox_contains_last_byte() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
let last = unsafe { sb.base().add(sb.size() - 1) };
assert!(sb.contains(last));
}
#[test]
fn test_sandbox_does_not_contain_one_past_end() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
let one_past = unsafe { sb.base().add(sb.size()) };
assert!(!sb.contains(one_past));
}
#[test]
fn test_sandbox_does_not_contain_null() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
assert!(!sb.contains(std::ptr::null()));
}
#[test]
fn test_check_in_bounds_ok_for_base() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
assert!(sb.check_in_bounds(sb.base()).is_ok());
}
#[test]
fn test_check_in_bounds_err_for_out_of_range() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
let result = sb.check_in_bounds(std::ptr::null());
assert!(result.is_err(), "expected SandboxViolation, got Ok");
let err = result.unwrap_err();
assert!(
matches!(err, StatorError::SandboxViolation { .. }),
"unexpected error variant: {err:?}"
);
}
#[test]
fn test_check_in_bounds_err_one_past_end() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
let one_past = unsafe { sb.base().add(sb.size()) };
let result = sb.check_in_bounds(one_past);
assert!(result.is_err(), "one-past-end must be out of bounds");
assert!(matches!(
result.unwrap_err(),
StatorError::SandboxViolation { .. }
));
}
#[test]
fn test_sandbox_violation_error_message() {
let sb = Sandbox::new(TEST_SANDBOX_SIZE).expect("sandbox creation");
let err = sb.check_in_bounds(std::ptr::null()).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("sandbox violation"),
"error message should mention 'sandbox violation': {msg}"
);
}
#[test]
fn test_null_handle_is_null() {
assert!(ExternalPointerHandle::NULL.is_null());
}
#[test]
fn test_non_null_handle_is_not_null() {
let mut table = ExternalPointerTable::new();
let handle = table.insert(std::ptr::null_mut());
assert!(!handle.is_null());
}
#[test]
fn test_table_insert_and_get() {
let mut table = ExternalPointerTable::new();
let value: u64 = 0xDEAD_BEEF;
let ptr = &raw const value as *mut ();
let handle = table.insert(ptr);
assert_eq!(table.get(handle), Some(ptr));
}
#[test]
fn test_table_null_handle_returns_none() {
let table = ExternalPointerTable::new();
assert_eq!(table.get(ExternalPointerHandle::NULL), None);
}
#[test]
fn test_table_remove_returns_ptr() {
let mut table = ExternalPointerTable::new();
let value: u64 = 42;
let ptr = &raw const value as *mut ();
let handle = table.insert(ptr);
assert_eq!(table.remove(handle), Some(ptr));
}
#[test]
fn test_table_get_after_remove_returns_none() {
let mut table = ExternalPointerTable::new();
let value: u64 = 42;
let ptr = &raw const value as *mut ();
let handle = table.insert(ptr);
table.remove(handle);
assert_eq!(table.get(handle), None);
}
#[test]
fn test_table_remove_twice_returns_none_second_time() {
let mut table = ExternalPointerTable::new();
let value: u64 = 1;
let ptr = &raw const value as *mut ();
let handle = table.insert(ptr);
table.remove(handle);
assert_eq!(table.remove(handle), None);
}
#[test]
fn test_table_len_and_is_empty() {
let mut table = ExternalPointerTable::new();
assert!(table.is_empty());
assert_eq!(table.len(), 0);
let value: u64 = 1;
let ptr = &raw const value as *mut ();
let h1 = table.insert(ptr);
let h2 = table.insert(ptr);
assert_eq!(table.len(), 2);
assert!(!table.is_empty());
table.remove(h1);
assert_eq!(table.len(), 1);
table.remove(h2);
assert!(table.is_empty());
}
#[test]
fn test_table_slot_recycled_after_remove() {
let mut table = ExternalPointerTable::new();
let value: u64 = 99;
let ptr = &raw const value as *mut ();
let h1 = table.insert(ptr);
table.remove(h1);
let value2: u64 = 100;
let ptr2 = &raw const value2 as *mut ();
let h2 = table.insert(ptr2);
assert_eq!(h1, h2, "recycled slot should produce same handle index");
assert_eq!(table.get(h2), Some(ptr2));
}
#[test]
fn test_table_many_entries() {
let mut table = ExternalPointerTable::new();
let values: Vec<u64> = (0..1_000).collect();
let handles: Vec<ExternalPointerHandle> = values
.iter()
.map(|v| table.insert(v as *const u64 as *mut ()))
.collect();
assert_eq!(table.len(), 1_000);
for (i, handle) in handles.iter().enumerate() {
assert_eq!(
table.get(*handle),
Some(&values[i] as *const u64 as *mut ()),
"entry {i} mismatch"
);
}
}
}