diff --git a/src/sandbox.rs b/src/sandbox.rs index e4ab81d..952b92f 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -34,6 +34,7 @@ pub fn build_command(config: &SandboxConfig) -> Result { add_ro_bind(&mut cmd, path)?; } + cmd.args(["--remount-ro", "/"]); cmd.arg("--new-session"); cmd.arg("--die-with-parent"); cmd.arg("--chdir").arg(&config.chdir); diff --git a/tests/integration.rs b/tests/integration.rs index 5c5d122..dc99566 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -420,6 +420,107 @@ fn blacklist_dev_input_hidden() { ); } +#[test] +fn blacklist_root_is_readonly() { + let output = sandbox(&[]) + .args([ + "--", + "bash", + "-c", + "touch /rootfile 2>&1 && echo WRITABLE || echo READONLY; \ + mkdir /newdir 2>&1 && echo MKDIR_OK || echo MKDIR_FAIL", + ]) + .output() + .expect("agent-sandbox binary failed to execute"); + + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("READONLY"), + "expected root to be read-only in blacklist mode, got: {stdout}" + ); + assert!( + stdout.contains("MKDIR_FAIL"), + "expected mkdir at root to fail in blacklist mode, got: {stdout}" + ); +} + +#[test] +fn whitelist_root_is_readonly() { + let output = sandbox(&["--whitelist"]) + .args([ + "--", + "bash", + "-c", + "touch /rootfile 2>&1 && echo WRITABLE || echo READONLY; \ + mkdir /newdir 2>&1 && echo MKDIR_OK || echo MKDIR_FAIL", + ]) + .output() + .expect("agent-sandbox binary failed to execute"); + + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("READONLY"), + "expected root to be read-only in whitelist mode, got: {stdout}" + ); + assert!( + stdout.contains("MKDIR_FAIL"), + "expected mkdir at root to fail in whitelist mode, got: {stdout}" + ); +} + +#[test] +fn whitelist_mountpoint_parents_are_readonly() { + let output = sandbox(&["--whitelist"]) + .args([ + "--", + "bash", + "-c", + "echo pwned > /home/testfile 2>&1 && echo HOME_WRITABLE || echo HOME_READONLY; \ + touch /etc/newfile 2>&1 && echo ETC_WRITABLE || echo ETC_READONLY; \ + touch /var/newfile 2>&1 && echo VAR_WRITABLE || echo VAR_READONLY", + ]) + .output() + .expect("agent-sandbox binary failed to execute"); + + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("HOME_READONLY"), + "expected /home to be read-only in whitelist mode, got: {stdout}" + ); + assert!( + stdout.contains("ETC_READONLY"), + "expected /etc to be read-only in whitelist mode, got: {stdout}" + ); + assert!( + stdout.contains("VAR_READONLY"), + "expected /var to be read-only in whitelist mode, got: {stdout}" + ); +} + +#[test] +fn whitelist_tmp_still_writable() { + let output = sandbox(&["--whitelist"]) + .args([ + "--", + "bash", + "-c", + "touch /tmp/ok 2>&1 && echo TMP_WRITABLE || echo TMP_READONLY; \ + touch /var/tmp/ok 2>&1 && echo VARTMP_WRITABLE || echo VARTMP_READONLY", + ]) + .output() + .expect("agent-sandbox binary failed to execute"); + + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("TMP_WRITABLE"), + "expected /tmp to remain writable in whitelist mode, got: {stdout}" + ); + assert!( + stdout.contains("VARTMP_WRITABLE"), + "expected /var/tmp to remain writable in whitelist mode, got: {stdout}" + ); +} + #[test] fn rw_missing_path_errors() { let output = sandbox(&["--rw", "/nonexistent/xyz"])