Files
agent-sandbox/src/lib.rs
T

120 lines
2.9 KiB
Rust

mod agents;
mod blacklist;
pub mod cli;
pub mod config;
mod env;
mod errors;
mod memfd;
mod persistent_tmp;
mod preflight;
mod process_tree;
mod sandbox;
mod sandbox_info;
mod seccomp;
mod session_key;
mod signals;
pub use errors::SandboxError;
pub use signals::exit_with_status;
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use std::process::ExitStatus;
use std::time::Duration;
use crate::sandbox::Bwrap;
use crate::signals::ForwardedSignals;
const CLEANUP_GRACE_PERIOD: Duration = Duration::from_secs(5);
pub enum SandboxMode {
Blacklist,
Whitelist,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EnvEntry {
Keep(String),
Set(String, String),
}
impl EnvEntry {
pub fn key(&self) -> &str {
match self {
Self::Keep(k) => k,
Self::Set(k, _) => k,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BindSpec {
pub source: PathBuf,
pub target: PathBuf,
}
pub struct SandboxConfig {
pub mode: SandboxMode,
pub hardened: bool,
pub unshare_net: bool,
pub seccomp: bool,
pub env_filter: bool,
pub extra_rw: Vec<BindSpec>,
pub extra_ro: Vec<BindSpec>,
pub mask: Vec<PathBuf>,
pub env: Vec<EnvEntry>,
pub unsetenv: Vec<String>,
pub bwrap_args: Vec<String>,
pub command: PathBuf,
pub command_args: Vec<OsString>,
pub chdir: PathBuf,
pub dry_run: bool,
pub persistent_tmp_key: Option<String>,
}
pub fn require_home() -> Result<String, SandboxError> {
std::env::var("HOME")
.ok()
.filter(|h| !h.is_empty())
.ok_or(SandboxError::HomeNotSet)
}
pub fn require_run_user() -> Result<String, SandboxError> {
std::env::var("XDG_RUNTIME_DIR")
.ok()
.or_else(resolve_run_user_from_proc)
.ok_or(SandboxError::RunUserNotFound)
}
fn resolve_run_user_from_proc() -> Option<String> {
let status = fs::read_to_string("/proc/self/status").ok()?;
for line in status.lines() {
if let Some(rest) = line.strip_prefix("Uid:") {
let uid = rest.split_whitespace().next()?;
return Some(format!("/run/user/{uid}"));
}
}
None
}
pub fn run(config: SandboxConfig) -> Result<ExitStatus, SandboxError> {
preflight::check(&config)?;
let forwarded_signals = ForwardedSignals::detect();
let mut bwrap = Bwrap::build(&config, &forwarded_signals.build_signal_reset_prefix())?;
if config.dry_run {
println!("{}", bwrap.shell_quoted());
return Ok(ExitStatus::default());
}
process_tree::become_subreaper()?;
let signal_forwarder = forwarded_signals.capture()?;
// bwrap ignores them: its death would SIGKILL the entrypoint through --die-with-parent.
let bwrap_process = bwrap.spawn(signal_forwarder.ignore_forwarded_signals())?;
let status = signal_forwarder.forward_until_exit(&bwrap_process)?;
process_tree::terminate_and_reap_all(CLEANUP_GRACE_PERIOD, signals::has_pending_interrupt)?;
Ok(status)
}