Add --keep-fd to pass chosen inherited descriptors into the sandbox

This commit is contained in:
2026-11-04 18:18:24 +01:00
parent f9ac595d63
commit c7d88d87d9
10 changed files with 141 additions and 25 deletions
+47 -8
View File
@@ -500,11 +500,9 @@ fn persistent_tmp_leaks_no_descriptor_into_host_tmp() {
#[test]
fn inherited_host_descriptor_does_not_reach_sandbox() {
const PASSED_FD: i32 = 9;
let host_dir = TempDir::new().unwrap();
fs::write(host_dir.path().join("canary"), "host-secret").unwrap();
let host_dir_handle = fs::File::open(host_dir.path()).unwrap();
let host_dir_fd = host_dir_handle.as_raw_fd();
let mut sandbox = Sandbox::new(&[]);
sandbox.args([
@@ -513,12 +511,7 @@ fn inherited_host_descriptor_does_not_reach_sandbox() {
"-c",
&format!("cat /proc/self/fd/{PASSED_FD}/canary 2>/dev/null; true"),
]);
unsafe {
sandbox.pre_exec(move || match libc::dup2(host_dir_fd, PASSED_FD) {
-1 => Err(std::io::Error::last_os_error()),
_ => Ok(()),
});
}
pass_as_fd(&mut sandbox, &host_dir_handle, PASSED_FD);
let output = sandbox
.output()
.expect("agent-sandbox binary failed to execute");
@@ -534,6 +527,52 @@ fn inherited_host_descriptor_does_not_reach_sandbox() {
);
}
#[test]
fn kept_descriptor_reaches_sandbox() {
let scratch = TempDir::new().unwrap();
let kept_file = scratch.path().join("kept");
fs::write(&kept_file, "kept-content").unwrap();
let kept_handle = fs::File::open(&kept_file).unwrap();
let mut sandbox = Sandbox::new(&["--keep-fd", &PASSED_FD.to_string()]);
sandbox.args(["--", "bash", "-c", &format!("cat <&{PASSED_FD}")]);
pass_as_fd(&mut sandbox, &kept_handle, PASSED_FD);
let output = sandbox
.output()
.expect("agent-sandbox binary failed to execute");
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(String::from_utf8_lossy(&output.stdout), "kept-content");
}
#[test]
fn keep_fd_rejects_descriptor_that_is_not_open() {
let output = Sandbox::new(&["--keep-fd", "999", "--dry-run"])
.args(["--", "true"])
.output()
.expect("agent-sandbox binary failed to execute");
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("999"), "stderr: {stderr}");
}
const PASSED_FD: i32 = 9;
fn pass_as_fd(sandbox: &mut Sandbox, file: &fs::File, target_fd: i32) {
let source_fd = file.as_raw_fd();
unsafe {
sandbox.pre_exec(move || match libc::dup2(source_fd, target_fd) {
-1 => Err(std::io::Error::last_os_error()),
_ => Ok(()),
});
}
}
#[test]
fn persistent_tmp_is_noop_in_blacklist_mode() {
let label = format!("e2e-bl-noop-{}-{}", std::process::id(), rand_suffix());