Mark inherited descriptors close-on-exec so none reach the sandbox
This commit is contained in:
@@ -35,6 +35,7 @@ pub enum SandboxError {
|
|||||||
SeccompUnsupportedArch(String),
|
SeccompUnsupportedArch(String),
|
||||||
PersistentTmpDir(PathBuf, String),
|
PersistentTmpDir(PathBuf, String),
|
||||||
ProcChildrenMissing,
|
ProcChildrenMissing,
|
||||||
|
InheritedFdsCloexec(std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SandboxError {
|
impl std::fmt::Display for SandboxError {
|
||||||
@@ -116,6 +117,10 @@ impl std::fmt::Display for SandboxError {
|
|||||||
f,
|
f,
|
||||||
"the kernel does not provide /proc/<pid>/task/<tid>/children (CONFIG_PROC_CHILDREN); agent-sandbox needs it to find the processes it must clean up"
|
"the kernel does not provide /proc/<pid>/task/<tid>/children (CONFIG_PROC_CHILDREN); agent-sandbox needs it to find the processes it must clean up"
|
||||||
),
|
),
|
||||||
|
Self::InheritedFdsCloexec(e) => write!(
|
||||||
|
f,
|
||||||
|
"cannot keep inherited file descriptors out of the sandbox (close_range with CLOSE_RANGE_CLOEXEC needs Linux 5.11+): {e}"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,6 +133,7 @@ impl std::error::Error for SandboxError {
|
|||||||
Self::Io(e) => Some(e),
|
Self::Io(e) => Some(e),
|
||||||
Self::ConfigRead { source, .. } => Some(source),
|
Self::ConfigRead { source, .. } => Some(source),
|
||||||
Self::ConfigParse { source, .. } => Some(source),
|
Self::ConfigParse { source, .. } => Some(source),
|
||||||
|
Self::InheritedFdsCloexec(e) => Some(e),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
use crate::SandboxError;
|
||||||
|
|
||||||
|
const FIRST_FD_AFTER_STDIO: libc::c_uint = 3;
|
||||||
|
|
||||||
|
pub fn keep_out_of_sandbox() -> Result<(), SandboxError> {
|
||||||
|
let result = unsafe {
|
||||||
|
libc::syscall(
|
||||||
|
libc::SYS_close_range,
|
||||||
|
FIRST_FD_AFTER_STDIO,
|
||||||
|
libc::c_uint::MAX,
|
||||||
|
libc::CLOSE_RANGE_CLOEXEC,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if result < 0 {
|
||||||
|
return Err(SandboxError::InheritedFdsCloexec(io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ pub mod cli;
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
mod env;
|
mod env;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod inherited_fds;
|
||||||
mod memfd;
|
mod memfd;
|
||||||
mod persistent_tmp;
|
mod persistent_tmp;
|
||||||
mod preflight;
|
mod preflight;
|
||||||
@@ -99,6 +100,7 @@ fn resolve_run_user_from_proc() -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(config: SandboxConfig) -> Result<ExitStatus, SandboxError> {
|
pub fn run(config: SandboxConfig) -> Result<ExitStatus, SandboxError> {
|
||||||
|
inherited_fds::keep_out_of_sandbox()?;
|
||||||
preflight::check(&config)?;
|
preflight::check(&config)?;
|
||||||
|
|
||||||
let forwarded_signals = ForwardedSignals::detect();
|
let forwarded_signals = ForwardedSignals::detect();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
@@ -496,6 +498,42 @@ 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([
|
||||||
|
"--",
|
||||||
|
"bash",
|
||||||
|
"-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(()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let output = sandbox
|
||||||
|
.output()
|
||||||
|
.expect("agent-sandbox binary failed to execute");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"stderr: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!String::from_utf8_lossy(&output.stdout).contains("host-secret"),
|
||||||
|
"a descriptor inherited by agent-sandbox reaches the sandboxed command"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persistent_tmp_is_noop_in_blacklist_mode() {
|
fn persistent_tmp_is_noop_in_blacklist_mode() {
|
||||||
let label = format!("e2e-bl-noop-{}-{}", std::process::id(), rand_suffix());
|
let label = format!("e2e-bl-noop-{}-{}", std::process::id(), rand_suffix());
|
||||||
|
|||||||
Reference in New Issue
Block a user