diff --git a/Cargo.lock b/Cargo.lock index 7466234..0727f20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "clap", "glob", "libc", + "nix", "seccompiler", "serde", "shlex", @@ -109,6 +110,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "clap" version = "4.6.7" @@ -255,6 +262,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "once_cell" version = "1.21.4" diff --git a/Cargo.toml b/Cargo.toml index 6c89a48..53e5c7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ blake3 = "1" clap = { version = "4", features = ["derive"] } glob = "0.3" libc = "0.2" +nix = { version = "0.31", default-features = false, features = ["signal", "process"] } seccompiler = "0.5" serde = { version = "1", features = ["derive"] } shlex = "2.0.1" diff --git a/src/lib.rs b/src/lib.rs index 043e72e..934b211 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,12 +11,15 @@ 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 crate::sandbox::Bwrap; @@ -90,15 +93,15 @@ fn resolve_run_user_from_proc() -> Option { None } -pub fn run(config: SandboxConfig) -> Result<(), SandboxError> { +pub fn run(config: SandboxConfig) -> Result { preflight::check(&config)?; let bwrap = Bwrap::build(&config)?; if config.dry_run { println!("{}", bwrap.shell_quoted()); - return Ok(()); + return Ok(ExitStatus::default()); } - Err(SandboxError::Io(bwrap.exec())) + Ok(bwrap.run()?) } diff --git a/src/main.rs b/src/main.rs index 37e097f..1bc8af4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,8 +17,9 @@ fn main() { let config = config::build(args, file_config).unwrap_or_else(|e| exit_err(&e)); - if let Err(e) = agent_sandbox::run(config) { - exit_err(&e); + match agent_sandbox::run(config) { + Ok(status) => agent_sandbox::exit_with_status(status), + Err(e) => exit_err(&e), } } diff --git a/src/sandbox.rs b/src/sandbox.rs index b728e0a..b93cb67 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -1,7 +1,6 @@ use std::io; -use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Command, ExitStatus}; use crate::agents; use crate::blacklist; @@ -23,8 +22,8 @@ impl Bwrap { Ok(bwrap) } - pub fn exec(mut self) -> io::Error { - self.command.exec() + pub fn run(mut self) -> io::Result { + self.command.status() } pub fn shell_quoted(&self) -> String { diff --git a/src/signals.rs b/src/signals.rs new file mode 100644 index 0000000..51820ba --- /dev/null +++ b/src/signals.rs @@ -0,0 +1,23 @@ +use std::os::unix::process::ExitStatusExt; +use std::process::{self, ExitStatus}; + +use nix::sys::prctl; +use nix::sys::signal::{self, SigHandler, SigSet, Signal}; + +pub fn exit_with_status(status: ExitStatus) -> ! { + match status.signal() { + Some(number) => kill_self_with(number), + None => process::exit(status.code().unwrap_or(1)), + } +} + +fn kill_self_with(number: i32) -> ! { + if let Ok(signal) = Signal::try_from(number) { + let _ = prctl::set_dumpable(false); + let _ = unsafe { signal::signal(signal, SigHandler::SigDfl) }; + let _ = SigSet::from(signal).thread_unblock(); + let _ = signal::raise(signal); + } + + process::exit(128 + number) +} diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index 4bfd3e8..d656e76 100644 --- a/tests/e2e/main.rs +++ b/tests/e2e/main.rs @@ -7,3 +7,4 @@ mod mounts; mod namespaces; mod sandbox_info; mod seccomp; +mod signals; diff --git a/tests/e2e/signals.rs b/tests/e2e/signals.rs new file mode 100644 index 0000000..bd64f14 --- /dev/null +++ b/tests/e2e/signals.rs @@ -0,0 +1,33 @@ +use std::os::unix::process::ExitStatusExt; + +use crate::common::*; + +#[test] +fn exit_code_of_entrypoint_is_returned() { + let status = Sandbox::new(&[]) + .args(["--", "sh", "-c", "exit 42"]) + .status() + .expect("agent-sandbox binary failed to execute"); + + assert_eq!(status.code(), Some(42)); +} + +#[test] +fn signal_death_of_entrypoint_becomes_exit_code() { + let status = Sandbox::new(&[]) + .args(["--", "sh", "-c", "kill -TERM $$"]) + .status() + .expect("agent-sandbox binary failed to execute"); + + assert_eq!(status.code(), Some(128 + libc::SIGTERM)); +} + +#[test] +fn signal_death_of_bwrap_is_mirrored() { + let status = Sandbox::new(&["--blacklist"]) + .args(["--", "sh", "-c", "kill -KILL $PPID; sleep 5"]) + .status() + .expect("agent-sandbox binary failed to execute"); + + assert_eq!(status.signal(), Some(libc::SIGKILL)); +}