Allow host runtimes to bypass the native builder sandbox
This commit is contained in:
@@ -29,9 +29,14 @@ It also owns a registry and kache cache.
|
||||
These directories live under `<dir>/host/builds/`.
|
||||
|
||||
Cargo fetches dependencies with network access.
|
||||
The command then runs offline inside bubblewrap namespaces.
|
||||
By default, the command then runs offline inside bubblewrap namespaces.
|
||||
The host-only `sausage run --no-builder-sandbox WORLD` switch runs Cargo directly instead.
|
||||
Tests use `RuntimeOptions.no_builder_sandbox` and select it with `SAUSAGE_TEST_NO_BUILDER_SANDBOX=1`.
|
||||
The switch is not a world config setting.
|
||||
Direct mode uses no user, mount, network, or PID namespaces.
|
||||
It retains the source copy, working directory, private registry, kache settings, timeout, output, and artifact publication.
|
||||
|
||||
The source copy and target directory permit writes.
|
||||
In the default sandboxed mode, the source copy and target directory permit writes.
|
||||
The private kache cache also permits writes.
|
||||
The registry and Nix store remain read-only.
|
||||
Host home directories and the host network are absent.
|
||||
|
||||
+64
-44
@@ -47,15 +47,23 @@ pub struct Builder {
|
||||
root: Arc<dyn Layer>,
|
||||
directory: PathBuf,
|
||||
lock: Mutex<()>,
|
||||
no_sandbox: bool,
|
||||
}
|
||||
impl Builder {
|
||||
pub fn new(root: Arc<dyn Layer>, host: &Path) -> Result<Self> {
|
||||
Self::new_with_options(root, host, false)
|
||||
}
|
||||
pub fn new_with_options(root: Arc<dyn Layer>, host: &Path, no_sandbox: bool) -> Result<Self> {
|
||||
Ok(Self {
|
||||
root,
|
||||
directory: std::fs::canonicalize(host)?.join("builds"),
|
||||
lock: Mutex::new(()),
|
||||
no_sandbox,
|
||||
})
|
||||
}
|
||||
fn command(&self) -> Command {
|
||||
Command::new(if self.no_sandbox { "cargo" } else { "bwrap" })
|
||||
}
|
||||
pub async fn execute(&self, request: Request, tx: &mpsc::Sender<Event>) -> Result<()> {
|
||||
let _guard = self.lock.lock().await;
|
||||
let action = request
|
||||
@@ -221,60 +229,72 @@ impl Builder {
|
||||
if action == "run" && run_path.exists() {
|
||||
std::fs::remove_file(&run_path)?;
|
||||
}
|
||||
let mut cmd = Command::new("bwrap");
|
||||
cmd.args([
|
||||
"--unshare-user",
|
||||
"--unshare-pid",
|
||||
"--unshare-net",
|
||||
"--die-with-parent",
|
||||
"--ro-bind",
|
||||
"/nix/store",
|
||||
"/nix/store",
|
||||
])
|
||||
.arg("--bind")
|
||||
.arg(&temporary)
|
||||
.arg("/tmp")
|
||||
.arg("--ro-bind")
|
||||
.arg(executable("sh")?)
|
||||
.arg("/bin/sh");
|
||||
for path in [&source, &target_mount, &cache] {
|
||||
cmd.arg("--bind").arg(path).arg(path);
|
||||
let mut cmd = self.command();
|
||||
if !self.no_sandbox {
|
||||
cmd.args([
|
||||
"--unshare-user",
|
||||
"--unshare-pid",
|
||||
"--unshare-net",
|
||||
"--die-with-parent",
|
||||
"--ro-bind",
|
||||
"/nix/store",
|
||||
"/nix/store",
|
||||
])
|
||||
.arg("--bind")
|
||||
.arg(&temporary)
|
||||
.arg("/tmp")
|
||||
.arg("--ro-bind")
|
||||
.arg(executable("sh")?)
|
||||
.arg("/bin/sh");
|
||||
for path in [&source, &target_mount, &cache] {
|
||||
cmd.arg("--bind").arg(path).arg(path);
|
||||
}
|
||||
for path in [®istry, &tools] {
|
||||
cmd.arg("--ro-bind").arg(path).arg(path);
|
||||
}
|
||||
cmd.args([
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--remount-ro",
|
||||
"/",
|
||||
"--chdir",
|
||||
])
|
||||
.arg(&cwd)
|
||||
.arg("--clearenv");
|
||||
} else {
|
||||
cmd.current_dir(&cwd);
|
||||
}
|
||||
for path in [®istry, &tools] {
|
||||
cmd.arg("--ro-bind").arg(path).arg(path);
|
||||
}
|
||||
cmd.args([
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--remount-ro",
|
||||
"/",
|
||||
"--chdir",
|
||||
])
|
||||
.arg(&cwd)
|
||||
.args(["--clearenv", "--setenv", "PATH"])
|
||||
.arg(std::env::var("PATH")?)
|
||||
.args(["--setenv", "HOME", "/nonexistent", "--setenv", "CARGO_HOME"])
|
||||
.arg(®istry)
|
||||
.args(["--setenv", "CARGO_TARGET_DIR"])
|
||||
.arg(&target)
|
||||
.args(["--setenv", "KACHE_CACHE_DIR"])
|
||||
.arg(&cache)
|
||||
.args(["--setenv", "SAUSAGE_CWD_INIT"])
|
||||
.arg(tools.join("init.o"))
|
||||
.args(["--setenv", "SAUSAGE_RUN_PATH"])
|
||||
.arg(&run_path);
|
||||
let mut environment = vec![
|
||||
("PATH", std::env::var_os("PATH").context("PATH missing")?),
|
||||
("HOME", "/nonexistent".into()),
|
||||
("CARGO_HOME", registry.clone().into_os_string()),
|
||||
("CARGO_TARGET_DIR", target.clone().into_os_string()),
|
||||
("KACHE_CACHE_DIR", cache.clone().into_os_string()),
|
||||
("SAUSAGE_CWD_INIT", tools.join("init.o").into_os_string()),
|
||||
("SAUSAGE_RUN_PATH", run_path.clone().into_os_string()),
|
||||
];
|
||||
for name in [
|
||||
"CC_wasm32_wasip2",
|
||||
"AR_wasm32_wasip2",
|
||||
"CFLAGS_wasm32_wasip2",
|
||||
] {
|
||||
if let Ok(value) = std::env::var(name) {
|
||||
environment.push((name, value.into()));
|
||||
}
|
||||
}
|
||||
for (name, value) in environment {
|
||||
if self.no_sandbox {
|
||||
cmd.env(name, value);
|
||||
} else {
|
||||
cmd.args(["--setenv", name]).arg(value);
|
||||
}
|
||||
}
|
||||
cmd.args(["cargo", "--offline", "--config"])
|
||||
if !self.no_sandbox {
|
||||
cmd.arg("cargo");
|
||||
}
|
||||
cmd.args(["--offline", "--config"])
|
||||
.arg(format!("build.rustc-wrapper={:?}", kache))
|
||||
.arg("--config")
|
||||
.arg(format!(
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
//! Focused tests for source discovery, argument forwarding, eviction, and process cancellation.
|
||||
use super::*;
|
||||
#[test]
|
||||
fn native_command_defaults_to_bwrap_and_can_bypass_it() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let world = crate::world::World::open(temp.path())?;
|
||||
assert!(!crate::process::RuntimeOptions::default().no_builder_sandbox);
|
||||
let default = Builder::new(world.root.clone(), &world.host_dir)?;
|
||||
assert_eq!(default.command().as_std().get_program(), "bwrap");
|
||||
let direct = Builder::new_with_options(world.root, &world.host_dir, true)?;
|
||||
assert_eq!(direct.command().as_std().get_program(), "cargo");
|
||||
Ok(())
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn source_links_survive_round_trip() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
|
||||
Reference in New Issue
Block a user