Bind persistent tmp dirs with --bind-fd so the sandbox inherits no host descriptor
This commit is contained in:
+31
-12
@@ -3,15 +3,14 @@ use std::io;
|
|||||||
use std::os::fd::{AsRawFd, OwnedFd};
|
use std::os::fd::{AsRawFd, OwnedFd};
|
||||||
use std::os::unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt};
|
use std::os::unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::SandboxConfig;
|
use crate::SandboxConfig;
|
||||||
use crate::errors::SandboxError;
|
use crate::errors::SandboxError;
|
||||||
|
|
||||||
pub struct PersistentTmpDirs {
|
pub struct PersistentTmpDirs {
|
||||||
pub tmp: PathBuf,
|
tmp: BindSource,
|
||||||
pub var_tmp: PathBuf,
|
var_tmp: BindSource,
|
||||||
// Held to keep `/proc/self/fd/<n>` bind sources valid through `exec()`.
|
|
||||||
_keep_fds: Vec<OwnedFd>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PersistentTmpDirs {
|
impl PersistentTmpDirs {
|
||||||
@@ -25,20 +24,40 @@ impl PersistentTmpDirs {
|
|||||||
|
|
||||||
if config.dry_run {
|
if config.dry_run {
|
||||||
return Ok(Some(Self {
|
return Ok(Some(Self {
|
||||||
tmp: tmp.host_path,
|
tmp: BindSource::HostPath(tmp.host_path),
|
||||||
var_tmp: var_tmp.host_path,
|
var_tmp: BindSource::HostPath(var_tmp.host_path),
|
||||||
_keep_fds: Vec::new(),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let tmp_fd = tmp.open()?;
|
|
||||||
let var_tmp_fd = var_tmp.open()?;
|
|
||||||
Ok(Some(Self {
|
Ok(Some(Self {
|
||||||
tmp: format!("/proc/self/fd/{}", tmp_fd.as_raw_fd()).into(),
|
tmp: BindSource::OpenDir(tmp.open()?),
|
||||||
var_tmp: format!("/proc/self/fd/{}", var_tmp_fd.as_raw_fd()).into(),
|
var_tmp: BindSource::OpenDir(var_tmp.open()?),
|
||||||
_keep_fds: vec![tmp_fd, var_tmp_fd],
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_binds(&self, cmd: &mut Command) {
|
||||||
|
self.tmp.add_bind(cmd, "/tmp");
|
||||||
|
self.var_tmp.add_bind(cmd, "/var/tmp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BindSource {
|
||||||
|
HostPath(PathBuf),
|
||||||
|
/// bwrap closes a `--bind-fd` descriptor once mounted, so the sandboxed
|
||||||
|
/// command never inherits this handle on the host filesystem.
|
||||||
|
OpenDir(OwnedFd),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BindSource {
|
||||||
|
fn add_bind(&self, cmd: &mut Command, target: &str) {
|
||||||
|
match self {
|
||||||
|
Self::HostPath(path) => cmd.arg("--bind").arg(path).arg(target),
|
||||||
|
Self::OpenDir(fd) => cmd
|
||||||
|
.arg("--bind-fd")
|
||||||
|
.arg(fd.as_raw_fd().to_string())
|
||||||
|
.arg(target),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PersistentTmpDir {
|
pub struct PersistentTmpDir {
|
||||||
|
|||||||
+1
-4
@@ -250,10 +250,7 @@ fn add_whitelist_mode(
|
|||||||
cmd.arg("--tmpfs").arg(&cache_dir);
|
cmd.arg("--tmpfs").arg(&cache_dir);
|
||||||
|
|
||||||
match persistent {
|
match persistent {
|
||||||
Some(p) => {
|
Some(p) => p.add_binds(cmd),
|
||||||
cmd.arg("--bind").arg(&p.tmp).arg("/tmp");
|
|
||||||
cmd.arg("--bind").arg(&p.var_tmp).arg("/var/tmp");
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
cmd.args(["--tmpfs", "/tmp"]);
|
cmd.args(["--tmpfs", "/tmp"]);
|
||||||
cmd.args(["--tmpfs", "/var/tmp"]);
|
cmd.args(["--tmpfs", "/var/tmp"]);
|
||||||
|
|||||||
@@ -463,6 +463,39 @@ fn persistent_tmp_survives_across_invocations() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn persistent_tmp_leaks_no_descriptor_into_host_tmp() {
|
||||||
|
let label = format!("e2e-fd-escape-{}-{}", 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 canary_name = format!("agent-sandbox-fd-canary-{label}");
|
||||||
|
let canary_host = format!("/tmp/{canary_name}");
|
||||||
|
let _cleanup_canary = CleanupPath::new(&canary_host);
|
||||||
|
fs::write(&canary_host, "host-secret").unwrap();
|
||||||
|
|
||||||
|
let output = Sandbox::new(&["--persistent-tmp", "--persistent-key", &label])
|
||||||
|
.args([
|
||||||
|
"--",
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
&format!(
|
||||||
|
"for fd in /proc/self/fd/*; do cat \"$fd/../{canary_name}\" 2>/dev/null; done; true"
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.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"),
|
||||||
|
"an inherited descriptor reaches host /tmp"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[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