use std::fs; use std::ops::{Deref, DerefMut}; use std::process::Command; use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tempfile::TempDir; // Blacklist mode globs the host filesystem at sandbox startup to find // sensitive paths to mask. A matching host file that vanishes mid-startup // makes bwrap fail in any concurrent blacklist sandbox. pub struct HostGlobsLock; impl HostGlobsLock { /// Acquire shared access. Hold while running a blacklist sandbox. pub fn for_scan() -> RwLockReadGuard<'static, ()> { LOCK.read().unwrap() } /// Acquire exclusive access. Hold while creating or deleting a host /// path that may match a blacklist glob (e.g. `/tmp/ssh-*`). pub fn for_mutation() -> RwLockWriteGuard<'static, ()> { LOCK.write().unwrap() } } static LOCK: RwLock<()> = RwLock::new(()); #[allow(dead_code)] enum HostGlobsGuard { Scan(RwLockReadGuard<'static, ()>), Mutation(RwLockWriteGuard<'static, ()>), } pub struct Sandbox { cmd: Command, _guard: Option, } impl Sandbox { pub fn new(extra_args: &[&str]) -> Self { Self::build(&["--no-config"], extra_args, scan_guard_for(extra_args)) } pub fn new_with_config(extra_args: &[&str]) -> Self { Self::build(&[], extra_args, scan_guard_for(extra_args)) } pub fn new_for_host_mutation(extra_args: &[&str]) -> Self { debug_assert!( extra_args.contains(&"--blacklist"), "new_for_host_mutation is only meaningful for blacklist sandboxes" ); Self::build( &["--no-config"], extra_args, Some(HostGlobsGuard::Mutation(HostGlobsLock::for_mutation())), ) } fn build(prefix: &[&str], extra_args: &[&str], guard: Option) -> Self { let mut cmd = Command::new(env!("CARGO_BIN_EXE_agent-sandbox")); cmd.args(prefix); cmd.args(extra_args); Self { cmd, _guard: guard } } } fn scan_guard_for(extra_args: &[&str]) -> Option { extra_args .contains(&"--blacklist") .then(|| HostGlobsGuard::Scan(HostGlobsLock::for_scan())) } impl Deref for Sandbox { type Target = Command; fn deref(&self) -> &Command { &self.cmd } } impl DerefMut for Sandbox { fn deref_mut(&mut self) -> &mut Command { &mut self.cmd } } pub struct ConfigFile { _dir: TempDir, path: String, } impl ConfigFile { pub fn new(content: &str) -> Self { let dir = TempDir::new().unwrap(); let path = dir.path().join("config.toml"); fs::write(&path, content).expect("failed to write config"); Self { _dir: dir, path: path.to_str().unwrap().to_string(), } } } impl std::ops::Deref for ConfigFile { type Target = str; fn deref(&self) -> &str { &self.path } }