Add --keep-fd to pass chosen inherited descriptors into the sandbox
This commit is contained in:
+47
-8
@@ -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());
|
||||
|
||||
@@ -1255,6 +1255,43 @@ fn build_mask_accumulates() {
|
||||
assert_eq!(config.mask.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_keep_fd_accumulates_globals_then_profile_then_cli() {
|
||||
let file_config = FileConfig {
|
||||
options: Options {
|
||||
keep_fd: vec![3],
|
||||
..Options::default()
|
||||
},
|
||||
profiles: HashMap::from([(
|
||||
"extra".into(),
|
||||
Options {
|
||||
keep_fd: vec![4],
|
||||
..Options::default()
|
||||
},
|
||||
)]),
|
||||
..FileConfig::default()
|
||||
};
|
||||
let args = Args {
|
||||
profile: vec!["extra".into()],
|
||||
keep_fd: vec![5],
|
||||
..args_with_command()
|
||||
};
|
||||
let config = build(args, Some(file_config)).unwrap();
|
||||
assert_eq!(config.keep_fds, vec![3, 4, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_fd_parsed_from_config() {
|
||||
let config = FileConfig::parse("keep-fd = [3, 4]").unwrap();
|
||||
assert_eq!(config.options.keep_fd, vec![3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_fd_parsed_from_repeated_cli_flag() {
|
||||
let args = Args::try_parse_from(["agent-sandbox", "--keep-fd", "3", "--keep-fd", "4"]).unwrap();
|
||||
assert_eq!(args.keep_fd, vec![3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_option_rejected() {
|
||||
let toml = r#"
|
||||
@@ -1460,16 +1497,19 @@ fn merge_options_lists_append_base_then_extra() {
|
||||
let base = Options {
|
||||
rw: vec!["/a".into()],
|
||||
env: vec!["A=1".into()],
|
||||
keep_fd: vec![3],
|
||||
..Options::default()
|
||||
};
|
||||
let extra = Options {
|
||||
rw: vec!["/b".into()],
|
||||
env: vec!["B=2".into()],
|
||||
keep_fd: vec![4],
|
||||
..Options::default()
|
||||
};
|
||||
let merged = base.merge_with(extra);
|
||||
assert_eq!(merged.rw, vec!["/a", "/b"]);
|
||||
assert_eq!(merged.env, vec!["A=1", "B=2"]);
|
||||
assert_eq!(merged.keep_fd, vec![3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user