Skip to main content

memlink_runtime/
sandbox.rs

1//! Module sandboxing and resource isolation.
2//!
3//! Provides resource limits and security constraints for module execution.
4
5use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
6use std::time::Duration;
7
8/// Sandbox configuration.
9#[derive(Debug, Clone)]
10pub struct SandboxConfig {
11    /// Maximum memory usage (bytes).
12    pub max_memory_bytes: usize,
13    /// Maximum CPU time (seconds).
14    pub max_cpu_time_secs: u64,
15    /// Maximum execution time per call.
16    pub max_call_duration: Duration,
17    /// Whether network access is allowed.
18    pub allow_network: bool,
19    /// Whether file system access is allowed.
20    pub allow_filesystem: bool,
21    /// Allowed syscalls (empty = all allowed).
22    pub allowed_syscalls: Vec<u32>,
23}
24
25impl Default for SandboxConfig {
26    fn default() -> Self {
27        Self {
28            max_memory_bytes: 128 * 1024 * 1024, // 128 MB
29            max_cpu_time_secs: 60,
30            max_call_duration: Duration::from_secs(30),
31            allow_network: false,
32            allow_filesystem: true,
33            allowed_syscalls: Vec::new(),
34        }
35    }
36}
37
38/// Resource usage statistics.
39#[derive(Debug, Clone)]
40pub struct ResourceUsage {
41    /// Current memory usage (bytes).
42    pub memory_bytes: usize,
43    /// Peak memory usage (bytes).
44    pub peak_memory_bytes: usize,
45    /// CPU time used (milliseconds).
46    pub cpu_time_ms: u64,
47    /// Total calls executed.
48    pub total_calls: u64,
49    /// Total syscalls made.
50    pub total_syscalls: u64,
51}
52
53/// Resource tracker for a module.
54#[derive(Debug)]
55pub struct ResourceTracker {
56    /// Current memory usage.
57    memory_bytes: AtomicUsize,
58    /// Peak memory usage.
59    peak_memory_bytes: AtomicUsize,
60    /// CPU time used (milliseconds).
61    cpu_time_ms: AtomicU64,
62    /// Total calls.
63    total_calls: AtomicU64,
64    /// Total syscalls.
65    total_syscalls: AtomicU64,
66}
67
68impl ResourceTracker {
69    /// Creates a new resource tracker.
70    pub fn new() -> Self {
71        Self {
72            memory_bytes: AtomicUsize::new(0),
73            peak_memory_bytes: AtomicUsize::new(0),
74            cpu_time_ms: AtomicU64::new(0),
75            total_calls: AtomicU64::new(0),
76            total_syscalls: AtomicU64::new(0),
77        }
78    }
79
80    /// Updates memory usage.
81    pub fn update_memory(&self, bytes: usize) {
82        self.memory_bytes.store(bytes, Ordering::Release);
83
84        // Update peak
85        let mut peak = self.peak_memory_bytes.load(Ordering::Acquire);
86        while bytes > peak {
87            match self.peak_memory_bytes.compare_exchange_weak(
88                peak,
89                bytes,
90                Ordering::Relaxed,
91                Ordering::Relaxed,
92            ) {
93                Ok(_) => break,
94                Err(x) => peak = x,
95            }
96        }
97    }
98
99    /// Records a call.
100    pub fn record_call(&self) {
101        self.total_calls.fetch_add(1, Ordering::Relaxed);
102    }
103
104    /// Records CPU time.
105    pub fn record_cpu_time(&self, ms: u64) {
106        self.cpu_time_ms.fetch_add(ms, Ordering::Relaxed);
107    }
108
109    /// Records a syscall.
110    pub fn record_syscall(&self) {
111        self.total_syscalls.fetch_add(1, Ordering::Relaxed);
112    }
113
114    /// Returns current usage.
115    pub fn usage(&self) -> ResourceUsage {
116        ResourceUsage {
117            memory_bytes: self.memory_bytes.load(Ordering::Acquire),
118            peak_memory_bytes: self.peak_memory_bytes.load(Ordering::Acquire),
119            cpu_time_ms: self.cpu_time_ms.load(Ordering::Acquire),
120            total_calls: self.total_calls.load(Ordering::Acquire),
121            total_syscalls: self.total_syscalls.load(Ordering::Acquire),
122        }
123    }
124
125    /// Checks if resource limits are exceeded.
126    pub fn exceeds_limits(&self, config: &SandboxConfig) -> bool {
127        self.memory_bytes.load(Ordering::Acquire) > config.max_memory_bytes
128    }
129
130    /// Resets all counters.
131    pub fn reset(&self) {
132        self.memory_bytes.store(0, Ordering::Release);
133        self.cpu_time_ms.store(0, Ordering::Relaxed);
134        self.total_calls.store(0, Ordering::Relaxed);
135        self.total_syscalls.store(0, Ordering::Relaxed);
136    }
137}
138
139impl Default for ResourceTracker {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145/// Permission flags for module operations.
146#[derive(Debug, Clone, Copy)]
147pub struct Permissions {
148    /// Can allocate memory.
149    pub can_allocate: bool,
150    /// Can make network calls.
151    pub can_network: bool,
152    /// Can access filesystem.
153    pub can_filesystem: bool,
154    /// Can spawn threads.
155    pub can_thread: bool,
156    /// Can access environment variables.
157    pub can_env: bool,
158}
159
160impl Default for Permissions {
161    fn default() -> Self {
162        Self {
163            can_allocate: true,
164            can_network: false,
165            can_filesystem: true,
166            can_thread: true,
167            can_env: false,
168        }
169    }
170}
171
172impl Permissions {
173    /// Creates restrictive permissions (minimal access).
174    pub fn restrictive() -> Self {
175        Self {
176            can_allocate: true,
177            can_network: false,
178            can_filesystem: false,
179            can_thread: false,
180            can_env: false,
181        }
182    }
183
184    /// Creates permissive permissions (full access).
185    pub fn permissive() -> Self {
186        Self {
187            can_allocate: true,
188            can_network: true,
189            can_filesystem: true,
190            can_thread: true,
191            can_env: true,
192        }
193    }
194
195    /// Checks if a permission is granted.
196    pub fn check(&self, permission: Permission) -> bool {
197        match permission {
198            Permission::Allocate => self.can_allocate,
199            Permission::Network => self.can_network,
200            Permission::Filesystem => self.can_filesystem,
201            Permission::Thread => self.can_thread,
202            Permission::Env => self.can_env,
203        }
204    }
205}
206
207/// Individual permission types.
208#[derive(Debug, Clone, Copy)]
209pub enum Permission {
210    Allocate,
211    Network,
212    Filesystem,
213    Thread,
214    Env,
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_sandbox_config() {
223        let config = SandboxConfig::default();
224        assert_eq!(config.max_memory_bytes, 128 * 1024 * 1024);
225        assert!(!config.allow_network);
226    }
227
228    #[test]
229    fn test_resource_tracker() {
230        let tracker = ResourceTracker::new();
231
232        tracker.update_memory(1000);
233        tracker.record_call();
234
235        let usage = tracker.usage();
236        assert_eq!(usage.memory_bytes, 1000);
237        assert_eq!(usage.total_calls, 1);
238    }
239
240    #[test]
241    fn test_permissions() {
242        let perms = Permissions::restrictive();
243        assert!(perms.can_allocate);
244        assert!(!perms.can_network);
245        assert!(!perms.can_filesystem);
246    }
247}