Spawn bwrap and mirror its exit status instead of exec'ing into it

This commit is contained in:
2026-10-31 01:55:36 +01:00
parent c17e8c7c5d
commit 1c2f51ed3b
8 changed files with 89 additions and 9 deletions
Generated
+19
View File
@@ -10,6 +10,7 @@ dependencies = [
"clap", "clap",
"glob", "glob",
"libc", "libc",
"nix",
"seccompiler", "seccompiler",
"serde", "serde",
"shlex", "shlex",
@@ -109,6 +110,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "cfg_aliases"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.6.7" version = "4.6.7"
@@ -255,6 +262,18 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 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]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.4" version = "1.21.4"
+1
View File
@@ -22,6 +22,7 @@ blake3 = "1"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
glob = "0.3" glob = "0.3"
libc = "0.2" libc = "0.2"
nix = { version = "0.31", default-features = false, features = ["signal", "process"] }
seccompiler = "0.5" seccompiler = "0.5"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
shlex = "2.0.1" shlex = "2.0.1"
+6 -3
View File
@@ -11,12 +11,15 @@ mod sandbox;
mod sandbox_info; mod sandbox_info;
mod seccomp; mod seccomp;
mod session_key; mod session_key;
mod signals;
pub use errors::SandboxError; pub use errors::SandboxError;
pub use signals::exit_with_status;
use std::ffi::OsString; use std::ffi::OsString;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::ExitStatus;
use crate::sandbox::Bwrap; use crate::sandbox::Bwrap;
@@ -90,15 +93,15 @@ fn resolve_run_user_from_proc() -> Option<String> {
None None
} }
pub fn run(config: SandboxConfig) -> Result<(), SandboxError> { pub fn run(config: SandboxConfig) -> Result<ExitStatus, SandboxError> {
preflight::check(&config)?; preflight::check(&config)?;
let bwrap = Bwrap::build(&config)?; let bwrap = Bwrap::build(&config)?;
if config.dry_run { if config.dry_run {
println!("{}", bwrap.shell_quoted()); println!("{}", bwrap.shell_quoted());
return Ok(()); return Ok(ExitStatus::default());
} }
Err(SandboxError::Io(bwrap.exec())) Ok(bwrap.run()?)
} }
+3 -2
View File
@@ -17,8 +17,9 @@ fn main() {
let config = config::build(args, file_config).unwrap_or_else(|e| exit_err(&e)); let config = config::build(args, file_config).unwrap_or_else(|e| exit_err(&e));
if let Err(e) = agent_sandbox::run(config) { match agent_sandbox::run(config) {
exit_err(&e); Ok(status) => agent_sandbox::exit_with_status(status),
Err(e) => exit_err(&e),
} }
} }
+3 -4
View File
@@ -1,7 +1,6 @@
use std::io; use std::io;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::{Command, ExitStatus};
use crate::agents; use crate::agents;
use crate::blacklist; use crate::blacklist;
@@ -23,8 +22,8 @@ impl Bwrap {
Ok(bwrap) Ok(bwrap)
} }
pub fn exec(mut self) -> io::Error { pub fn run(mut self) -> io::Result<ExitStatus> {
self.command.exec() self.command.status()
} }
pub fn shell_quoted(&self) -> String { pub fn shell_quoted(&self) -> String {
+23
View File
@@ -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)
}
+1
View File
@@ -7,3 +7,4 @@ mod mounts;
mod namespaces; mod namespaces;
mod sandbox_info; mod sandbox_info;
mod seccomp; mod seccomp;
mod signals;
+33
View File
@@ -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));
}