Canonicalize blacklist overlay paths to skirt /var/run symlink

This commit is contained in:
2026-04-23 18:47:27 +02:00
parent 862feada05
commit 7c9375cd94
2 changed files with 55 additions and 10 deletions
+14 -10
View File
@@ -14,8 +14,8 @@ pub struct BlacklistOverlays {
} }
pub fn resolve_overlays(ctx: &PathContext) -> Result<BlacklistOverlays, SandboxError> { pub fn resolve_overlays(ctx: &PathContext) -> Result<BlacklistOverlays, SandboxError> {
let mut tmpfs_dirs = Vec::new(); let mut tmpfs_dirs: Vec<PathBuf> = Vec::new();
let mut null_bind_files = Vec::new(); let mut null_bind_files: Vec<PathBuf> = Vec::new();
for raw in SENSITIVE_PATHS { for raw in SENSITIVE_PATHS {
let expanded = expand_path(raw, ctx); let expanded = expand_path(raw, ctx);
@@ -23,9 +23,13 @@ pub fn resolve_overlays(ctx: &PathContext) -> Result<BlacklistOverlays, SandboxE
paths.sort_by_key(|p| !p.is_dir()); paths.sort_by_key(|p| !p.is_dir());
for path in paths { for path in paths {
match classify_path(&path) { match classify_path(&path) {
PathKind::Dir => tmpfs_dirs.push(path), PathKind::Dir(path) => {
PathKind::File => { if !tmpfs_dirs.contains(&path) {
if !is_under_tmpfs_dir(&path, &tmpfs_dirs) { tmpfs_dirs.push(path);
}
}
PathKind::File(path) => {
if !is_under_tmpfs_dir(&path, &tmpfs_dirs) && !null_bind_files.contains(&path) {
null_bind_files.push(path); null_bind_files.push(path);
} }
} }
@@ -47,15 +51,15 @@ pub fn resolve_path_context() -> Result<PathContext, SandboxError> {
} }
enum PathKind { enum PathKind {
Dir, Dir(PathBuf),
File, File(PathBuf),
Missing, Missing,
} }
fn classify_path(path: &Path) -> PathKind { fn classify_path(path: &Path) -> PathKind {
match fs::metadata(path) { match fs::canonicalize(path) {
Ok(m) if m.is_dir() => PathKind::Dir, Ok(path) if path.is_dir() => PathKind::Dir(path),
Ok(_) => PathKind::File, Ok(path) => PathKind::File(path),
Err(_) => PathKind::Missing, Err(_) => PathKind::Missing,
} }
} }
+41
View File
@@ -1393,6 +1393,47 @@ fn seccomp_bash_pthread_fallback_works() {
); );
} }
#[test]
fn blacklist_overlays_survive_absolute_var_run_symlink() {
// On Debian/Ubuntu, /var/run -> /run is an absolute symlink; overlays
// like --tmpfs /var/run/dbus trip bwrap's re-rooted symlink resolution.
// Arch ships /var/run -> ../run (relative) so we synthesize the absolute
// layout inside the sandbox to reproduce on any host.
let mut bwrap_args = build_bwrap_command(&["--no-seccomp", "--", "true"]);
inject_absolute_var_run_symlink(&mut bwrap_args);
let output = Command::new(&bwrap_args[0])
.args(&bwrap_args[1..])
.output()
.expect("failed to invoke bwrap directly");
assert!(
output.status.success(),
"bwrap failed — an overlay target traverses an absolute /var/run symlink.\n\
stderr: {}",
String::from_utf8_lossy(&output.stderr),
);
}
fn build_bwrap_command(sandbox_args: &[&str]) -> Vec<String> {
let output = sandbox(&["--dry-run"])
.args(sandbox_args)
.output()
.expect("agent-sandbox binary failed to execute");
let cmd = String::from_utf8_lossy(&output.stdout);
let parsed = shlex::split(cmd.trim()).expect("dry-run output is not valid shell");
assert_eq!(parsed[0], "bwrap");
parsed
}
fn inject_absolute_var_run_symlink(bwrap_args: &mut Vec<String>) {
assert_eq!(bwrap_args[1], "--ro-bind");
assert_eq!(bwrap_args[2], "/");
assert_eq!(bwrap_args[3], "/");
let flags = ["--tmpfs", "/var", "--symlink", "/run", "/var/run"].map(String::from);
bwrap_args.splice(4..4, flags);
}
#[test] #[test]
fn seccomp_blocks_tiocsti() { fn seccomp_blocks_tiocsti() {
// TIOCSTI (0x5412) injects keystrokes into the terminal input queue. // TIOCSTI (0x5412) injects keystrokes into the terminal input queue.