Expose the bwrap command line at /run/agent-sandbox inside every sandbox

This commit is contained in:
2026-09-02 16:09:46 +02:00
parent 9185d1fb26
commit c8f2d62b64
9 changed files with 193 additions and 16 deletions
+2
View File
@@ -4,9 +4,11 @@ pub mod cli;
pub mod config;
mod env;
mod errors;
mod memfd;
mod persistent_tmp;
mod preflight;
mod sandbox;
mod sandbox_info;
mod seccomp;
mod session_key;
+15
View File
@@ -0,0 +1,15 @@
use std::ffi::CStr;
use std::fs::File;
use std::os::fd::FromRawFd;
use crate::SandboxError;
pub fn create_inheritable(name: &CStr) -> Result<File, SandboxError> {
// flags=0 omits MFD_CLOEXEC so the fd survives exec into bwrap
let raw_fd = unsafe { libc::memfd_create(name.as_ptr(), 0) };
if raw_fd < 0 {
return Err(SandboxError::Io(std::io::Error::last_os_error()));
}
Ok(unsafe { File::from_raw_fd(raw_fd) })
}
+9 -4
View File
@@ -7,6 +7,7 @@ use crate::agents;
use crate::blacklist;
use crate::env;
use crate::persistent_tmp::PersistentTmpDirs;
use crate::sandbox_info::BwrapCommandLine;
use crate::seccomp;
use crate::{BindSpec, EnvEntry, SandboxConfig, SandboxError, SandboxMode};
@@ -17,7 +18,9 @@ pub struct Bwrap {
impl Bwrap {
pub fn build(config: &SandboxConfig) -> Result<Self, SandboxError> {
build_command(config)
let (bwrap, command_line) = build_command(config)?;
command_line.fill(&bwrap.shell_quoted())?;
Ok(bwrap)
}
pub fn exec(mut self) -> io::Error {
@@ -32,7 +35,7 @@ impl Bwrap {
}
}
fn build_command(config: &SandboxConfig) -> Result<Bwrap, SandboxError> {
fn build_command(config: &SandboxConfig) -> Result<(Bwrap, BwrapCommandLine), SandboxError> {
let mut cmd = Command::new("bwrap");
let hardened = config.hardened || matches!(config.mode, SandboxMode::Whitelist);
@@ -54,6 +57,7 @@ fn build_command(config: &SandboxConfig) -> Result<Bwrap, SandboxError> {
persistent
}
};
let command_line = BwrapCommandLine::add_to(&mut cmd)?;
for path in agents::agent_rw_paths() {
cmd.arg("--bind-try").arg(&path).arg(&path);
@@ -86,10 +90,11 @@ fn build_command(config: &SandboxConfig) -> Result<Bwrap, SandboxError> {
.arg(&config.command)
.args(&config.command_args);
Ok(Bwrap {
let bwrap = Bwrap {
command: cmd,
_persistent: persistent,
})
};
Ok((bwrap, command_line))
}
fn add_env_policy(cmd: &mut Command, config: &SandboxConfig) {
+65
View File
@@ -0,0 +1,65 @@
use std::fs::File;
use std::io::{Seek, SeekFrom, Write};
use std::os::fd::{AsRawFd, IntoRawFd};
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::SandboxError;
use crate::memfd;
pub const DIR: &str = "/run/agent-sandbox";
pub fn bwrap_command_line_path() -> PathBuf {
Path::new(DIR).join("bwrap-args")
}
/// Created empty so its fd number can appear in the invocation it later holds.
pub struct BwrapCommandLine {
file: File,
}
impl BwrapCommandLine {
pub fn add_to(cmd: &mut Command) -> Result<Self, SandboxError> {
let file = memfd::create_inheritable(c"agent-sandbox-bwrap-args")?;
cmd.arg("--dir").arg(DIR);
cmd.arg("--ro-bind-data")
.arg(file.as_raw_fd().to_string())
.arg(bwrap_command_line_path());
Ok(Self { file })
}
pub fn fill(mut self, invocation: &str) -> Result<(), SandboxError> {
self.file.write_all(invocation.as_bytes())?;
self.file.write_all(b"\n")?;
self.file.seek(SeekFrom::Start(0))?;
let _inherited_by_bwrap = self.file.into_raw_fd();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binds_memfd_read_only_and_fills_it_with_invocation() {
let mut cmd = Command::new("bwrap");
let command_line = BwrapCommandLine::add_to(&mut cmd).unwrap();
let args: Vec<String> = cmd
.get_args()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert_eq!(args[0..2], ["--dir", DIR]);
assert_eq!(args[2], "--ro-bind-data");
assert_eq!(args[4], "/run/agent-sandbox/bwrap-args");
let fd: i32 = args[3].parse().unwrap();
command_line.fill("bwrap --dir /run/agent-sandbox").unwrap();
let content = std::fs::read_to_string(format!("/proc/self/fd/{fd}")).unwrap();
assert_eq!(content, "bwrap --dir /run/agent-sandbox\n");
}
}
+3 -12
View File
@@ -32,7 +32,7 @@
use std::collections::BTreeMap;
use std::io::{Seek, SeekFrom, Write};
use std::os::fd::{FromRawFd, IntoRawFd, RawFd};
use std::os::fd::{IntoRawFd, RawFd};
use std::str::FromStr;
use seccompiler::{
@@ -42,6 +42,7 @@ use seccompiler::{
use syscalls::Sysno;
use crate::SandboxError;
use crate::memfd;
/// Syscall allowlist. Includes Podman's unconditional allow set (minus syscalls
/// we deny on top, see module docs) plus arch-specific syscalls for the targets
@@ -55,17 +56,7 @@ const ALLOWED_SYSCALLS: &[&str] = &include!("seccomp_allowlist.in");
pub fn write_program_to_memfd() -> Result<RawFd, SandboxError> {
let bytes = build_program_bytes()?;
// Safety: memfd_create is a normal Linux syscall. We pass a valid C string
// and flags=0, so the fd is created without MFD_CLOEXEC and survives exec
// into bwrap. The name is only a debugging label (shows up as the symlink
// target in /proc/<pid>/fd/<n>).
let raw_fd = unsafe { libc::memfd_create(c"agent-sandbox-seccomp".as_ptr(), 0) };
if raw_fd < 0 {
return Err(SandboxError::Io(std::io::Error::last_os_error()));
}
// Safety: raw_fd is owned by us and currently uniquely held.
let mut file = unsafe { std::fs::File::from_raw_fd(raw_fd) };
let mut file = memfd::create_inheritable(c"agent-sandbox-seccomp")?;
file.write_all(&bytes)?;
file.seek(SeekFrom::Start(0))?;
// into_raw_fd consumes the File without closing the underlying fd.