Optionally back /tmp and /var/tmp with stable host directories
This commit is contained in:
+188
-5
@@ -5,16 +5,29 @@ use tempfile::TempDir;
|
||||
|
||||
use crate::common::*;
|
||||
|
||||
struct CleanupFile(&'static str);
|
||||
struct CleanupPath(String);
|
||||
|
||||
impl Drop for CleanupFile {
|
||||
impl CleanupPath {
|
||||
fn new(path: impl Into<String>) -> Self {
|
||||
Self(path.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CleanupPath {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_file(self.0);
|
||||
match std::path::Path::new(&self.0) {
|
||||
p if p.is_dir() => {
|
||||
let _ = fs::remove_dir_all(p);
|
||||
}
|
||||
p => {
|
||||
let _ = fs::remove_file(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn cwd_is_writable() {
|
||||
let _cleanup = CleanupFile("./sandbox_canary");
|
||||
let _cleanup = CleanupPath::new("./sandbox_canary");
|
||||
|
||||
let output = Sandbox::new(&[])
|
||||
.args(["--", "bash", "-c", "touch ./sandbox_canary && echo ok"])
|
||||
@@ -166,7 +179,7 @@ fn rw_refines_ro_parent() {
|
||||
fn blacklist_overlays_survive_tmp_bind() {
|
||||
let mut sandbox = Sandbox::new_for_host_mutation(&["--blacklist"]);
|
||||
fs::write("/tmp/ssh-sandbox-test", "secret").expect("failed to write sentinel");
|
||||
let _cleanup = CleanupFile("/tmp/ssh-sandbox-test");
|
||||
let _cleanup = CleanupPath::new("/tmp/ssh-sandbox-test");
|
||||
|
||||
let output = sandbox
|
||||
.args([
|
||||
@@ -385,6 +398,176 @@ fn build_bwrap_command(sandbox_args: &[&str]) -> Vec<String> {
|
||||
parsed
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persistent_tmp_dry_run_uses_session_path_and_creates_no_dirs() {
|
||||
let label = format!("e2e-dry-{}-{}", std::process::id(), rand_suffix());
|
||||
let output = Sandbox::new(&["--persistent-tmp", "--persistent-key", &label, "--dry-run"])
|
||||
.args(["--", "true"])
|
||||
.output()
|
||||
.expect("agent-sandbox binary failed to execute");
|
||||
assert!(output.status.success());
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(
|
||||
stdout.contains(&format!("/tmp/agent-sandbox-{label}")),
|
||||
"dry-run output should embed session /tmp path, got: {stdout}"
|
||||
);
|
||||
assert!(
|
||||
stdout.contains(&format!("/var/tmp/agent-sandbox-{label}")),
|
||||
"dry-run output should embed session /var/tmp path, got: {stdout}"
|
||||
);
|
||||
assert!(
|
||||
!std::path::Path::new(&format!("/tmp/agent-sandbox-{label}")).exists(),
|
||||
"--dry-run must not create the host directory"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persistent_tmp_survives_across_invocations() {
|
||||
let label = format!("e2e-persist-{}-{}", std::process::id(), rand_suffix());
|
||||
let _cleanup_tmp = CleanupPath::new(format!("/tmp/agent-sandbox-{label}"));
|
||||
let _cleanup_var = CleanupPath::new(format!("/var/tmp/agent-sandbox-{label}"));
|
||||
|
||||
let writer = Sandbox::new(&["--persistent-tmp", "--persistent-key", &label])
|
||||
.args([
|
||||
"--",
|
||||
"bash",
|
||||
"-c",
|
||||
"echo persisted > /tmp/canary && echo also-persisted > /var/tmp/canary",
|
||||
])
|
||||
.output()
|
||||
.expect("write run failed");
|
||||
assert!(
|
||||
writer.status.success(),
|
||||
"stderr: {}",
|
||||
String::from_utf8_lossy(&writer.stderr)
|
||||
);
|
||||
|
||||
let reader = Sandbox::new(&["--persistent-tmp", "--persistent-key", &label])
|
||||
.args(["--", "bash", "-c", "cat /tmp/canary /var/tmp/canary"])
|
||||
.output()
|
||||
.expect("read run failed");
|
||||
assert!(
|
||||
reader.status.success(),
|
||||
"stderr: {}",
|
||||
String::from_utf8_lossy(&reader.stderr)
|
||||
);
|
||||
let out = String::from_utf8_lossy(&reader.stdout);
|
||||
assert!(
|
||||
out.contains("persisted"),
|
||||
"expected /tmp canary, got: {out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("also-persisted"),
|
||||
"expected /var/tmp canary, got: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persistent_tmp_is_noop_in_blacklist_mode() {
|
||||
let label = format!("e2e-bl-noop-{}-{}", std::process::id(), rand_suffix());
|
||||
let canary_name = format!(
|
||||
"agent-sandbox-bl-canary-{}-{}",
|
||||
std::process::id(),
|
||||
rand_suffix()
|
||||
);
|
||||
let canary_host = format!("/tmp/{canary_name}");
|
||||
let _cleanup_canary = CleanupPath::new(&canary_host);
|
||||
|
||||
let output = Sandbox::new(&[
|
||||
"--blacklist",
|
||||
"--persistent-tmp",
|
||||
"--persistent-key",
|
||||
&label,
|
||||
])
|
||||
.args([
|
||||
"--",
|
||||
"bash",
|
||||
"-c",
|
||||
&format!("echo bl-shared > /tmp/{canary_name}"),
|
||||
])
|
||||
.output()
|
||||
.expect("write run failed");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stderr: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
assert!(
|
||||
std::path::Path::new(&canary_host).exists(),
|
||||
"blacklist /tmp should be host /tmp; expected canary at {canary_host}"
|
||||
);
|
||||
assert!(
|
||||
!std::path::Path::new(&format!("/tmp/agent-sandbox-{label}")).exists(),
|
||||
"--persistent-tmp must be a no-op in blacklist mode, but session dir was created"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persistent_tmp_resumes_with_explicit_key_matching_derived_hash() {
|
||||
let chdir = TempDir::new().unwrap();
|
||||
let chdir_str = chdir.path().to_str().unwrap();
|
||||
|
||||
let dry = Sandbox::new(&["--persistent-tmp", "--dry-run", "--chdir", chdir_str])
|
||||
.args(["--", "true"])
|
||||
.output()
|
||||
.expect("dry-run failed");
|
||||
assert!(dry.status.success());
|
||||
let key = extract_persistent_key(&String::from_utf8_lossy(&dry.stdout));
|
||||
let _cleanup_tmp = CleanupPath::new(format!("/tmp/agent-sandbox-{key}"));
|
||||
let _cleanup_var = CleanupPath::new(format!("/var/tmp/agent-sandbox-{key}"));
|
||||
|
||||
let writer = Sandbox::new(&["--persistent-tmp", "--chdir", chdir_str])
|
||||
.args(["--", "bash", "-c", "echo persisted > /tmp/canary"])
|
||||
.output()
|
||||
.expect("write run failed");
|
||||
assert!(
|
||||
writer.status.success(),
|
||||
"stderr: {}",
|
||||
String::from_utf8_lossy(&writer.stderr)
|
||||
);
|
||||
|
||||
// Second run: different chdir (would have derived a different hash) plus an
|
||||
// extra flag, but an explicit key pinning the same host directory.
|
||||
let other_chdir = TempDir::new().unwrap();
|
||||
let reader = Sandbox::new(&[
|
||||
"--persistent-tmp",
|
||||
"--persistent-key",
|
||||
&key,
|
||||
"--chdir",
|
||||
other_chdir.path().to_str().unwrap(),
|
||||
"--unshare-net",
|
||||
])
|
||||
.args(["--", "cat", "/tmp/canary"])
|
||||
.output()
|
||||
.expect("read run failed");
|
||||
assert!(
|
||||
reader.status.success(),
|
||||
"stderr: {}",
|
||||
String::from_utf8_lossy(&reader.stderr)
|
||||
);
|
||||
assert!(String::from_utf8_lossy(&reader.stdout).contains("persisted"));
|
||||
}
|
||||
|
||||
fn extract_persistent_key(dryrun_stdout: &str) -> String {
|
||||
let prefix = "/tmp/agent-sandbox-";
|
||||
let idx = dryrun_stdout
|
||||
.find(prefix)
|
||||
.expect("dry-run output should mention /tmp/agent-sandbox-<key>");
|
||||
dryrun_stdout[idx + prefix.len()..]
|
||||
.chars()
|
||||
.take_while(|c| c.is_ascii_hexdigit())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn rand_suffix() -> String {
|
||||
let nanos = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.subsec_nanos();
|
||||
format!("{nanos:08x}")
|
||||
}
|
||||
|
||||
fn inject_absolute_var_run_symlink(bwrap_args: &mut Vec<String>) {
|
||||
assert_eq!(bwrap_args[1], "--ro-bind");
|
||||
assert_eq!(bwrap_args[2], "/");
|
||||
|
||||
Reference in New Issue
Block a user