Skip to main content

sh_layer4/plugin_loader/
sandbox.rs

1//! Plugin Security Sandbox
2//!
3//! Enforces capability restrictions on plugin operations.
4
5use super::capabilities::{Capability, CapabilitySet};
6use crate::types::Layer4Result;
7use anyhow::anyhow;
8use std::path::Path;
9use std::time::Instant;
10
11/// Security sandbox for plugin execution
12#[derive(Clone)]
13pub struct PluginSandbox {
14    /// Capability set for this sandbox
15    capabilities: CapabilitySet,
16    /// Execution start time (for CPU limit)
17    start_time: Option<Instant>,
18    /// Memory usage tracker
19    memory_used: u64,
20}
21
22impl PluginSandbox {
23    /// Create new sandbox with given capabilities
24    pub fn new(capabilities: CapabilitySet) -> Self {
25        Self {
26            capabilities,
27            start_time: None,
28            memory_used: 0,
29        }
30    }
31
32    /// Create unrestricted sandbox (use with caution)
33    pub fn unrestricted() -> Self {
34        Self::new(CapabilitySet::unrestricted())
35    }
36
37    /// Create sandboxed environment
38    pub fn sandboxed() -> Self {
39        Self::new(CapabilitySet::sandboxed())
40    }
41
42    /// Start execution timer
43    pub fn start_execution(&mut self) {
44        self.start_time = Some(Instant::now());
45    }
46
47    /// Check if execution is within CPU limit
48    pub fn check_cpu_limit(&self) -> Layer4Result<()> {
49        let cpu_limit = self.get_cpu_limit();
50        if cpu_limit == 0 {
51            return Ok(());
52        }
53
54        if let Some(start) = self.start_time {
55            let elapsed = start.elapsed().as_millis() as u64;
56            if elapsed > cpu_limit {
57                return Err(anyhow!(
58                    "CPU time limit exceeded: {}ms > {}ms",
59                    elapsed,
60                    cpu_limit
61                ));
62            }
63        }
64        Ok(())
65    }
66
67    /// Track memory allocation
68    pub fn track_memory(&mut self, size: u64) -> Layer4Result<()> {
69        let memory_limit = self.get_memory_limit();
70        if memory_limit == 0 {
71            self.memory_used += size;
72            return Ok(());
73        }
74
75        if self.memory_used + size > memory_limit {
76            return Err(anyhow!(
77                "Memory limit exceeded: {} + {} > {}",
78                self.memory_used,
79                size,
80                memory_limit
81            ));
82        }
83        self.memory_used += size;
84        Ok(())
85    }
86
87    /// Check file read permission
88    pub fn check_fs_read(&self, path: &Path) -> Layer4Result<()> {
89        if !self.capabilities.check(&Capability::FsRead) {
90            return Err(anyhow!("File read denied: {:?}", path));
91        }
92        Ok(())
93    }
94
95    /// Check file write permission
96    pub fn check_fs_write(&self, path: &Path) -> Layer4Result<()> {
97        if !self.capabilities.check(&Capability::FsWrite) {
98            return Err(anyhow!("File write denied: {:?}", path));
99        }
100        Ok(())
101    }
102
103    /// Check network access permission
104    pub fn check_network(&self, url: &str) -> Layer4Result<()> {
105        if !self.capabilities.check(&Capability::NetworkOut) {
106            return Err(anyhow!("Network access denied: {}", url));
107        }
108        Ok(())
109    }
110
111    /// Check process execution permission
112    pub fn check_process(&self, cmd: &str) -> Layer4Result<()> {
113        if !self.capabilities.check(&Capability::ProcessExec) {
114            return Err(anyhow!("Process execution denied: {}", cmd));
115        }
116        Ok(())
117    }
118
119    /// Get memory limit in bytes (0 = unlimited)
120    fn get_memory_limit(&self) -> u64 {
121        for cap in &self.capabilities.allowed {
122            if let Capability::MemoryLimit(limit) = cap {
123                return *limit;
124            }
125        }
126        0
127    }
128
129    /// Get CPU limit in milliseconds (0 = unlimited)
130    fn get_cpu_limit(&self) -> u64 {
131        for cap in &self.capabilities.allowed {
132            if let Capability::CpuLimit(limit) = cap {
133                return *limit;
134            }
135        }
136        0
137    }
138
139    /// Reset execution state
140    pub fn reset(&mut self) {
141        self.start_time = None;
142        self.memory_used = 0;
143    }
144
145    /// Get capabilities reference
146    pub fn capabilities(&self) -> &CapabilitySet {
147        &self.capabilities
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_sandbox_creation() {
157        let sandbox = PluginSandbox::sandboxed();
158        assert!(sandbox.capabilities().check(&Capability::Clock));
159    }
160
161    #[test]
162    fn test_fs_read_check() {
163        let sandbox = PluginSandbox::sandboxed();
164        let result = sandbox.check_fs_read(Path::new("/etc/passwd"));
165        assert!(result.is_err());
166    }
167
168    #[test]
169    fn test_unrestricted_sandbox() {
170        let sandbox = PluginSandbox::unrestricted();
171        assert!(sandbox.check_fs_read(Path::new("/tmp/test")).is_ok());
172        assert!(sandbox.check_fs_write(Path::new("/tmp/test")).is_ok());
173        assert!(sandbox.check_network("https://example.com").is_ok());
174    }
175
176    #[test]
177    fn test_cpu_limit_check() {
178        let mut sandbox = PluginSandbox::sandboxed();
179        sandbox.start_execution();
180        // Should pass immediately
181        assert!(sandbox.check_cpu_limit().is_ok());
182    }
183
184    #[test]
185    fn test_memory_tracking() {
186        let mut sandbox = PluginSandbox::sandboxed();
187        assert!(sandbox.track_memory(1024).is_ok());
188        assert!(sandbox.track_memory(1024 * 1024).is_ok());
189    }
190}