use std::path::Path; use std::process::Command; use crate::agents; use crate::blacklist; use crate::{SandboxConfig, SandboxError, SandboxMode}; pub fn build_command(config: &SandboxConfig) -> Result { let mut cmd = Command::new("bwrap"); let hardened = config.hardened || matches!(config.mode, SandboxMode::Whitelist); if hardened { cmd.args(["--unshare-ipc", "--unshare-pid", "--unshare-uts"]); cmd.args(["--hostname", "sandbox"]); } if config.no_net { cmd.arg("--unshare-net"); } match config.mode { SandboxMode::Blacklist => add_blacklist_mode(&mut cmd)?, SandboxMode::Whitelist => add_whitelist_mode(&mut cmd)?, } if matches!(config.mode, SandboxMode::Whitelist) { cmd.args(["--tmpfs", "/tmp"]); cmd.args(["--tmpfs", "/var/tmp"]); cmd.args(["--dev", "/dev"]); cmd.args(["--tmpfs", "/dev/shm"]); cmd.args(["--tmpfs", "/run"]); cmd.args(["--proc", "/proc"]); } for path in agents::agent_rw_paths() { cmd.arg("--bind-try").arg(&path).arg(&path); } add_rw_bind(&mut cmd, &config.chdir)?; for path in &config.extra_rw { add_rw_bind(&mut cmd, path)?; } for path in &config.extra_ro { add_ro_bind(&mut cmd, path)?; } cmd.arg("--die-with-parent"); cmd.arg("--chdir").arg(&config.chdir); cmd.arg("--") .arg(&config.command) .args(&config.command_args); Ok(cmd) } fn add_blacklist_mode(cmd: &mut Command) -> Result<(), SandboxError> { let ctx = blacklist::resolve_path_context()?; cmd.args(["--ro-bind", "/", "/"]); let overlays = blacklist::resolve_overlays(&ctx)?; for dir in &overlays.tmpfs_dirs { cmd.arg("--tmpfs").arg(dir); } for file in &overlays.null_bind_files { cmd.arg("--ro-bind").arg("/dev/null").arg(file); } cmd.args(["--dev-bind", "/dev", "/dev"]); cmd.args(["--proc", "/proc"]); cmd.args(["--bind", "/tmp", "/tmp"]); cmd.args(["--bind", "/var/tmp", "/var/tmp"]); cmd.args(["--bind", "/run", "/run"]); Ok(()) } fn add_whitelist_mode(cmd: &mut Command) -> Result<(), SandboxError> { let home = std::env::var("HOME").map_err(|_| SandboxError::HomeNotSet)?; cmd.args(["--ro-bind", "/usr", "/usr"]); for path in ["/lib", "/lib64", "/lib32", "/bin", "/sbin"] { cmd.args(["--ro-bind-try", path, path]); } for path in [ "/etc/ld.so.cache", "/etc/ld.so.conf", "/etc/ld.so.conf.d", "/etc/alternatives", ] { cmd.args(["--ro-bind-try", path, path]); } cmd.args(["--ro-bind-try", "/etc/ssl", "/etc/ssl"]); cmd.args([ "--ro-bind-try", "/etc/ca-certificates", "/etc/ca-certificates", ]); cmd.args(["--ro-bind-try", "/etc/resolv.conf", "/etc/resolv.conf"]); cmd.args(["--ro-bind-try", "/etc/nsswitch.conf", "/etc/nsswitch.conf"]); cmd.args(["--ro-bind-try", "/etc/passwd", "/etc/passwd"]); cmd.args(["--ro-bind-try", "/etc/group", "/etc/group"]); for path in [ "/etc/hosts", "/etc/gai.conf", "/etc/services", "/etc/protocols", ] { cmd.args(["--ro-bind-try", path, path]); } for path in ["/etc/hostname", "/etc/localtime", "/etc/machine-id"] { cmd.args(["--ro-bind-try", path, path]); } let local_bin = format!("{home}/.local/bin"); cmd.arg("--ro-bind-try").arg(&local_bin).arg(&local_bin); let cache_dir = format!("{home}/.cache"); cmd.arg("--tmpfs").arg(&cache_dir); Ok(()) } fn add_rw_bind(cmd: &mut Command, path: &Path) -> Result<(), SandboxError> { if !path.exists() { return Err(SandboxError::RwPathMissing(path.to_path_buf())); } cmd.arg("--bind").arg(path).arg(path); Ok(()) } fn add_ro_bind(cmd: &mut Command, path: &Path) -> Result<(), SandboxError> { if !path.exists() { return Err(SandboxError::RoPathMissing(path.to_path_buf())); } cmd.arg("--ro-bind").arg(path).arg(path); Ok(()) }