sh_layer4/plugin_loader/
sandbox.rs1use super::capabilities::{Capability, CapabilitySet};
6use crate::types::Layer4Result;
7use anyhow::anyhow;
8use std::path::Path;
9use std::time::Instant;
10
11#[derive(Clone)]
13pub struct PluginSandbox {
14 capabilities: CapabilitySet,
16 start_time: Option<Instant>,
18 memory_used: u64,
20}
21
22impl PluginSandbox {
23 pub fn new(capabilities: CapabilitySet) -> Self {
25 Self {
26 capabilities,
27 start_time: None,
28 memory_used: 0,
29 }
30 }
31
32 pub fn unrestricted() -> Self {
34 Self::new(CapabilitySet::unrestricted())
35 }
36
37 pub fn sandboxed() -> Self {
39 Self::new(CapabilitySet::sandboxed())
40 }
41
42 pub fn start_execution(&mut self) {
44 self.start_time = Some(Instant::now());
45 }
46
47 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 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 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 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 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 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 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 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 pub fn reset(&mut self) {
141 self.start_time = None;
142 self.memory_used = 0;
143 }
144
145 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 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}