ai_sandbox/linux_sandbox/
bsd.rs1#![allow(dead_code)]
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
9pub enum CapsicumLevel {
10 #[default]
12 Disabled,
13 Basic,
15 Strict,
17}
18
19#[cfg(target_os = "freebsd")]
21extern "C" {
22 fn cap_enter() -> std::os::raw::c_int;
23 fn cap_rights_limit(
24 fd: std::os::raw::c_int,
25 rights: *const std::os::raw::c_void,
26 ) -> std::os::raw::c_int;
27}
28
29#[derive(Clone, Debug, Default)]
31pub struct PledgePromises {
32 pub stdio: bool,
33 pub rpath: bool,
34 pub wpath: bool,
35 pub cpath: bool,
36 pub dpath: bool,
37 pub fpath: bool,
38 pub inet: bool,
39 pub unix: bool,
40 pub dns: bool,
41 pub proc: bool,
42 pub exec: bool,
43 pub id: bool,
44 pub chown: bool,
45 pub flock: bool,
46 pub tmppath: bool,
47 pub error: bool,
48}
49
50impl PledgePromises {
51 pub fn default_safe() -> Self {
53 Self {
54 stdio: true,
55 rpath: true,
56 wpath: false,
57 cpath: false,
58 dpath: false,
59 fpath: false,
60 inet: false,
61 unix: false,
62 dns: false,
63 proc: false,
64 exec: false,
65 id: false,
66 chown: false,
67 flock: false,
68 tmppath: true,
69 error: true,
70 }
71 }
72
73 pub fn to_pledge_string(&self) -> String {
75 let mut promises = Vec::new();
76
77 if self.stdio {
78 promises.push("stdio");
79 }
80 if self.rpath {
81 promises.push("rpath");
82 }
83 if self.wpath {
84 promises.push("wpath");
85 }
86 if self.cpath {
87 promises.push("cpath");
88 }
89 if self.dpath {
90 promises.push("dpath");
91 }
92 if self.fpath {
93 promises.push("fpath");
94 }
95 if self.inet {
96 promises.push("inet");
97 }
98 if self.unix {
99 promises.push("unix");
100 }
101 if self.dns {
102 promises.push("dns");
103 }
104 if self.proc {
105 promises.push("proc");
106 }
107 if self.exec {
108 promises.push("exec");
109 }
110 if self.id {
111 promises.push("id");
112 }
113 if self.chown {
114 promises.push("chown");
115 }
116 if self.flock {
117 promises.push("flock");
118 }
119 if self.tmppath {
120 promises.push("tmppath");
121 }
122 if self.error {
123 promises.push("error");
124 }
125
126 promises.join(" ")
127 }
128}
129
130pub fn create_pledge_promises_from_policy(
132 file_system_policy: &crate::FileSystemSandboxPolicy,
133 network_policy: crate::NetworkSandboxPolicy,
134) -> PledgePromises {
135 let mut promises = PledgePromises::default_safe();
136
137 match file_system_policy {
139 crate::FileSystemSandboxPolicy::FullAccess => {
140 promises.rpath = true;
142 promises.wpath = true;
143 promises.cpath = true;
144 }
145 crate::FileSystemSandboxPolicy::ReadOnly => {
146 promises.rpath = true;
148 promises.wpath = false;
149 promises.cpath = false;
150 }
151 crate::FileSystemSandboxPolicy::WorkspaceWrite { .. } => {
152 promises.rpath = true;
154 promises.wpath = true;
155 promises.cpath = true;
156 }
157 crate::FileSystemSandboxPolicy::External => {
158 }
160 }
161
162 match network_policy {
164 crate::NetworkSandboxPolicy::FullAccess => {
165 promises.inet = true;
166 promises.dns = true;
167 }
168 crate::NetworkSandboxPolicy::Localhost => {
169 promises.inet = true;
171 }
172 crate::NetworkSandboxPolicy::NoAccess => {
173 promises.inet = false;
174 promises.dns = false;
175 }
176 crate::NetworkSandboxPolicy::Proxy => {
177 promises.inet = true;
178 promises.dns = true;
179 }
180 }
181
182 promises
183}
184
185pub fn create_freebsd_sandbox_args(argv: &[String], level: CapsicumLevel) -> Vec<String> {
187 let mut args = vec![];
188
189 match level {
190 CapsicumLevel::Disabled => {
191 }
193 CapsicumLevel::Basic => {
194 args.push("--capsicum".to_string());
195 args.push("basic".to_string());
196 }
197 CapsicumLevel::Strict => {
198 args.push("--capsicum".to_string());
199 args.push("strict".to_string());
200 }
201 }
202
203 args.extend(argv.iter().cloned());
204 args
205}
206
207pub fn is_capsicum_available() -> bool {
209 #[cfg(target_os = "freebsd")]
210 {
211 true
213 }
214 #[cfg(not(target_os = "freebsd"))]
215 {
216 false
217 }
218}
219
220pub fn is_pledge_available() -> bool {
222 #[cfg(target_os = "openbsd")]
223 {
224 true
226 }
227 #[cfg(not(target_os = "openbsd"))]
228 {
229 false
230 }
231}
232
233#[cfg(target_os = "freebsd")]
234mod freebsd_impl {
235 use std::process::{Command, Stdio};
236
237 pub fn execute_with_capsicum(
239 program: &str,
240 args: &[String],
241 level: super::CapsicumLevel,
242 ) -> std::io::Result<std::process::Child> {
243 if matches!(level, super::CapsicumLevel::Disabled) {
245 let mut cmd = Command::new(program);
246 cmd.args(args);
247 cmd.stdin(Stdio::inherit());
248 cmd.stdout(Stdio::inherit());
249 cmd.stderr(Stdio::inherit());
250 return cmd.spawn();
251 }
252
253 let capsicum_wrapper = format!("exec {}", args.join(" "));
256
257 let mut cmd = Command::new(program);
258 cmd.args(args);
259 cmd.stdin(Stdio::inherit());
260 cmd.stdout(Stdio::inherit());
261 cmd.stderr(Stdio::inherit());
262
263 cmd.env(
271 "CAPSICUM_ENABLED",
272 match level {
273 super::CapsicumLevel::Basic => "basic",
274 super::CapsicumLevel::Strict => "strict",
275 _ => "disabled",
276 },
277 );
278
279 cmd.spawn()
280 }
281}
282
283#[cfg(target_os = "openbsd")]
284mod openbsd_impl {
285 use std::process::{Command, Stdio};
286
287 extern "C" {
289 fn pledge(
290 promises: *const std::ffi::CStr,
291 execpromises: *const std::ffi::CStr,
292 ) -> std::os::raw::c_int;
293 }
294
295 pub fn execute_with_pledge(
297 program: &str,
298 args: &[String],
299 promises: &super::PledgePromises,
300 ) -> std::io::Result<std::process::Child> {
301 let promise_str = promises.to_pledge_string();
302 let promise_cstr = std::ffi::CString::new(promise_str).unwrap();
303 let empty_cstr = std::ffi::CString::new("").unwrap();
304
305 unsafe {
307 if pledge(promise_cstr.as_c_str(), empty_cstr.as_c_str()) != 0 {
308 return Err(std::io::Error::last_os_error());
309 }
310 }
311
312 let mut cmd = Command::new(program);
313 cmd.args(args);
314 cmd.stdin(Stdio::inherit());
315 cmd.stdout(Stdio::inherit());
316 cmd.stderr(Stdio::inherit());
317
318 cmd.spawn()
319 }
320}
321
322#[cfg(not(target_os = "freebsd"))]
323mod freebsd_impl {
324 use std::io;
325
326 pub fn execute_with_capsicum(
327 _program: &str,
328 _args: &[String],
329 _level: super::CapsicumLevel,
330 ) -> io::Result<std::process::Child> {
331 Err(io::Error::new(
332 io::ErrorKind::Unsupported,
333 "Capsicum not available on this platform",
334 ))
335 }
336}
337
338#[cfg(not(target_os = "openbsd"))]
339mod openbsd_impl {
340 use std::io;
341
342 pub fn execute_with_pledge(
343 _program: &str,
344 _args: &[String],
345 _promises: &super::PledgePromises,
346 ) -> io::Result<std::process::Child> {
347 Err(io::Error::new(
348 io::ErrorKind::Unsupported,
349 "pledge not available on this platform",
350 ))
351 }
352}
353
354pub use freebsd_impl::execute_with_capsicum;
355pub use openbsd_impl::execute_with_pledge;
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn test_pledge_promises() {
363 let promises = PledgePromises::default_safe();
364 let s = promises.to_pledge_string();
365 assert!(s.contains("stdio"));
366 assert!(s.contains("rpath"));
367 }
368
369 #[test]
374 fn test_create_pledge_promises_from_policy_full_access() {
375 let promises = create_pledge_promises_from_policy(
376 &crate::FileSystemSandboxPolicy::FullAccess,
377 crate::NetworkSandboxPolicy::FullAccess,
378 );
379 let s = promises.to_pledge_string();
380 assert!(s.contains("rpath"));
382 assert!(s.contains("wpath"));
383 assert!(s.contains("cpath"));
384 assert!(s.contains("inet"));
385 assert!(s.contains("dns"));
386 }
387
388 #[test]
389 fn test_create_pledge_promises_from_policy_readonly() {
390 let promises = create_pledge_promises_from_policy(
391 &crate::FileSystemSandboxPolicy::ReadOnly,
392 crate::NetworkSandboxPolicy::NoAccess,
393 );
394 let s = promises.to_pledge_string();
395 assert!(s.contains("rpath"));
397 assert!(!s.contains("wpath"));
398 assert!(!s.contains("cpath"));
399 assert!(!s.contains("inet"));
401 assert!(!s.contains("dns"));
402 }
403
404 #[test]
405 fn test_create_pledge_promises_from_policy_workspace() {
406 let promises = create_pledge_promises_from_policy(
407 &crate::FileSystemSandboxPolicy::WorkspaceWrite {
408 writable_roots: vec![std::path::PathBuf::from("/tmp")],
409 },
410 crate::NetworkSandboxPolicy::Localhost,
411 );
412 let s = promises.to_pledge_string();
413 assert!(s.contains("rpath"));
415 assert!(s.contains("wpath"));
416 assert!(s.contains("inet"));
418 assert!(!s.contains("dns"));
420 }
421
422 #[test]
423 fn test_create_pledge_promises_from_policy_external() {
424 let promises = create_pledge_promises_from_policy(
425 &crate::FileSystemSandboxPolicy::External,
426 crate::NetworkSandboxPolicy::Proxy,
427 );
428 let s = promises.to_pledge_string();
429 assert!(s.contains("inet"));
431 assert!(s.contains("dns"));
432 }
433
434 #[test]
435 fn test_create_pledge_promises_from_policy_no_network() {
436 let promises = create_pledge_promises_from_policy(
437 &crate::FileSystemSandboxPolicy::FullAccess,
438 crate::NetworkSandboxPolicy::NoAccess,
439 );
440 let s = promises.to_pledge_string();
441 assert!(!s.contains("inet"));
443 assert!(!s.contains("dns"));
444 }
445
446 #[test]
447 fn test_capsicum_level_variants() {
448 assert_eq!(CapsicumLevel::default(), CapsicumLevel::Disabled);
449 let _ = CapsicumLevel::Basic;
450 let _ = CapsicumLevel::Strict;
451 }
452
453 #[test]
454 fn test_pledge_promises_default_safe() {
455 let promises = PledgePromises::default_safe();
456 let s = promises.to_pledge_string();
457 assert!(s.contains("stdio"));
459 assert!(s.contains("rpath"));
460 assert!(!s.contains("wpath")); assert!(!s.contains("inet")); }
463}