151 lines
4.6 KiB
Rust
151 lines
4.6 KiB
Rust
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
|
use std::path::Path;
|
|
use std::process::{Child, Stdio};
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use tempfile::TempDir;
|
|
|
|
use crate::common::*;
|
|
|
|
#[test]
|
|
fn sigterm_lets_entrypoint_clean_up_in_blacklist_mode() {
|
|
assert_sigterm_lets_entrypoint_clean_up("--blacklist");
|
|
}
|
|
|
|
#[test]
|
|
fn sigterm_lets_entrypoint_clean_up_in_whitelist_mode() {
|
|
assert_sigterm_lets_entrypoint_clean_up("--whitelist");
|
|
}
|
|
|
|
fn assert_sigterm_lets_entrypoint_clean_up(mode: &str) {
|
|
let dir = TempDir::new().expect("failed to create temp dir");
|
|
let script = "trap 'sleep 0.5; touch cleaned; exit 0' TERM
|
|
touch ready
|
|
sleep 60 & wait";
|
|
|
|
let (_sandbox, mut child) = spawn_sandbox(&[mode], dir.path(), script);
|
|
wait_for_file(&dir.path().join("ready"));
|
|
send_signal(&child, libc::SIGTERM);
|
|
let status = child.wait().unwrap();
|
|
|
|
assert!(
|
|
dir.path().join("cleaned").exists(),
|
|
"cleanup did not finish"
|
|
);
|
|
assert_eq!(status.code(), Some(0));
|
|
}
|
|
|
|
#[test]
|
|
fn forwarded_signal_death_of_entrypoint_is_mirrored() {
|
|
let dir = TempDir::new().expect("failed to create temp dir");
|
|
let script = "touch ready
|
|
sleep 60 & wait";
|
|
|
|
let (_sandbox, mut child) = spawn_sandbox(&["--blacklist"], dir.path(), script);
|
|
wait_for_file(&dir.path().join("ready"));
|
|
send_signal(&child, libc::SIGTERM);
|
|
let status = child.wait().unwrap();
|
|
|
|
assert_eq!(status.signal(), Some(libc::SIGTERM));
|
|
}
|
|
|
|
#[test]
|
|
fn user_and_realtime_signals_reach_entrypoint() {
|
|
let dir = TempDir::new().expect("failed to create temp dir");
|
|
let realtime = libc::SIGRTMIN() + 1;
|
|
let script = format!(
|
|
"trap 'touch usr1' USR1
|
|
trap 'touch realtime' {realtime}
|
|
trap 'exit 0' TERM
|
|
touch ready
|
|
while :; do sleep 0.05; done"
|
|
);
|
|
|
|
let (_sandbox, mut child) = spawn_sandbox(&["--blacklist"], dir.path(), &script);
|
|
wait_for_file(&dir.path().join("ready"));
|
|
send_signal(&child, libc::SIGUSR1);
|
|
wait_for_file(&dir.path().join("usr1"));
|
|
send_signal(&child, realtime);
|
|
wait_for_file(&dir.path().join("realtime"));
|
|
send_signal(&child, libc::SIGTERM);
|
|
|
|
assert_eq!(child.wait().unwrap().code(), Some(0));
|
|
}
|
|
|
|
#[test]
|
|
fn entrypoint_starts_with_default_dispositions_except_inherited_ignores() {
|
|
let mut sandbox = Sandbox::new(&["--blacklist"]);
|
|
sandbox.args(["--", "cat", "/proc/self/status"]);
|
|
unsafe {
|
|
sandbox.pre_exec(|| {
|
|
libc::signal(libc::SIGHUP, libc::SIG_IGN);
|
|
Ok(())
|
|
});
|
|
}
|
|
|
|
let output = sandbox
|
|
.output()
|
|
.expect("agent-sandbox binary failed to execute");
|
|
let status = String::from_utf8_lossy(&output.stdout);
|
|
let ignored = read_signal_mask(&status, "SigIgn:");
|
|
let blocked = read_signal_mask(&status, "SigBlk:");
|
|
|
|
assert!(
|
|
has_signal(ignored, libc::SIGHUP),
|
|
"inherited SIGHUP ignore was lost"
|
|
);
|
|
for signal in [libc::SIGINT, libc::SIGTERM, libc::SIGUSR1, libc::SIGPIPE] {
|
|
assert!(!has_signal(ignored, signal), "signal {signal} is ignored");
|
|
}
|
|
assert_eq!(blocked, 0, "entrypoint starts with blocked signals");
|
|
}
|
|
|
|
#[test]
|
|
fn entrypoint_inherits_no_signalfd() {
|
|
let output = Sandbox::new(&["--blacklist"])
|
|
.args(["--", "ls", "-l", "/proc/self/fd"])
|
|
.output()
|
|
.expect("agent-sandbox binary failed to execute");
|
|
let fds = String::from_utf8_lossy(&output.stdout);
|
|
|
|
assert!(
|
|
!fds.contains("signalfd"),
|
|
"entrypoint inherits a signalfd: {fds}"
|
|
);
|
|
}
|
|
|
|
fn spawn_sandbox(args: &[&str], dir: &Path, script: &str) -> (Sandbox, Child) {
|
|
let mut sandbox = Sandbox::new(args);
|
|
let child = sandbox
|
|
.args(["--chdir", dir.to_str().unwrap(), "--", "sh", "-c", script])
|
|
.stdout(Stdio::null())
|
|
.spawn()
|
|
.expect("agent-sandbox binary failed to execute");
|
|
(sandbox, child)
|
|
}
|
|
|
|
fn wait_for_file(path: &Path) {
|
|
let deadline = Instant::now() + Duration::from_secs(10);
|
|
while !path.exists() {
|
|
assert!(Instant::now() < deadline, "timed out waiting for {path:?}");
|
|
thread::sleep(Duration::from_millis(10));
|
|
}
|
|
}
|
|
|
|
fn send_signal(child: &Child, signal: i32) {
|
|
unsafe { libc::kill(child.id() as i32, signal) };
|
|
}
|
|
|
|
fn read_signal_mask(proc_status: &str, field: &str) -> u64 {
|
|
let line = proc_status
|
|
.lines()
|
|
.find_map(|line| line.strip_prefix(field))
|
|
.unwrap_or_else(|| panic!("{field} missing in {proc_status}"));
|
|
u64::from_str_radix(line.trim(), 16).unwrap()
|
|
}
|
|
|
|
fn has_signal(mask: u64, signal: i32) -> bool {
|
|
mask & (1 << (signal - 1)) != 0
|
|
}
|