From 89104eb0a211dd50e067566389e70cc896fb66af Mon Sep 17 00:00:00 2001 From: Jobdori Date: Fri, 3 Apr 2026 16:24:02 +0900 Subject: [PATCH] fix(sandbox): probe unshare capability instead of binary existence On GitHub Actions runners, `unshare` binary exists at /usr/bin/unshare but user namespaces (CLONE_NEWUSER) are restricted, causing `unshare --user --map-root-user` to silently fail. This produced empty stdout in the bash_stdout_roundtrip parity test (mock_parity_harness.rs:533). Replace the simple `command_exists("unshare")` check with `unshare_user_namespace_works()` that actually probes whether `unshare --user --map-root-user true` succeeds. Result is cached via OnceLock so the probe runs at most once per process. Fixes: CI red on main@85c5b0e (Rust CI run 23933274144) --- rust/crates/runtime/src/sandbox.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/rust/crates/runtime/src/sandbox.rs b/rust/crates/runtime/src/sandbox.rs index 3d834ed..45f118a 100644 --- a/rust/crates/runtime/src/sandbox.rs +++ b/rust/crates/runtime/src/sandbox.rs @@ -161,7 +161,7 @@ pub fn resolve_sandbox_status(config: &SandboxConfig, cwd: &Path) -> SandboxStat #[must_use] pub fn resolve_sandbox_status_for_request(request: &SandboxRequest, cwd: &Path) -> SandboxStatus { let container = detect_container_environment(); - let namespace_supported = cfg!(target_os = "linux") && command_exists("unshare"); + let namespace_supported = cfg!(target_os = "linux") && unshare_user_namespace_works(); let network_supported = namespace_supported; let filesystem_active = request.enabled && request.filesystem_mode != FilesystemIsolationMode::Off; @@ -282,6 +282,27 @@ fn command_exists(command: &str) -> bool { .is_some_and(|paths| env::split_paths(&paths).any(|path| path.join(command).exists())) } +/// Check whether `unshare --user` actually works on this system. +/// On some CI environments (e.g. GitHub Actions), the binary exists but +/// user namespaces are restricted, causing silent failures. +fn unshare_user_namespace_works() -> bool { + use std::sync::OnceLock; + static RESULT: OnceLock = OnceLock::new(); + *RESULT.get_or_init(|| { + if !command_exists("unshare") { + return false; + } + std::process::Command::new("unshare") + .args(["--user", "--map-root-user", "true"]) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) + }) +} + #[cfg(test)] mod tests { use super::{