From 9950b5e265dbf94ae8b605c8ceee714875637e9d Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Thu, 5 Mar 2026 13:57:40 -0800 Subject: [PATCH] fix(linux-sandbox): always unshare bwrap userns (#13624) ## Summary - always pass `--unshare-user` in the Linux bubblewrap argv builders - stop relying on bubblewrap's auto-userns behavior, which is skipped for `uid 0` - update argv expectations in tests and document the explicit user namespace behavior The installed Codex binary reproduced the same issue with: - `codex -c features.use_linux_sandbox_bwrap=true sandbox linux -- true` - `bwrap: Creating new namespace failed: Operation not permitted` This happens because Codex asked bubblewrap for mount/pid/network namespaces without explicitly asking for a user namespace. In a root-inside-container environment without ambient `CAP_SYS_ADMIN`, that fails. Adding `--unshare-user` makes bubblewrap create the user namespace first and then the remaining namespaces succeed. --- codex-rs/linux-sandbox/README.md | 3 ++- codex-rs/linux-sandbox/src/bwrap.rs | 7 +++++++ codex-rs/linux-sandbox/src/linux_run_main_tests.rs | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/codex-rs/linux-sandbox/README.md b/codex-rs/linux-sandbox/README.md index b03919c96..32d8d99f0 100644 --- a/codex-rs/linux-sandbox/README.md +++ b/codex-rs/linux-sandbox/README.md @@ -25,7 +25,8 @@ into this binary. - When enabled, symlink-in-path and non-existent protected paths inside writable roots are blocked by mounting `/dev/null` on the symlink or first missing component. -- When enabled, the helper isolates the PID namespace via `--unshare-pid`. +- When enabled, the helper explicitly isolates the user namespace via + `--unshare-user` and the PID namespace via `--unshare-pid`. - When enabled and network is restricted without proxy routing, the helper also isolates the network namespace via `--unshare-net`. - In managed proxy mode, the helper uses `--unshare-net` plus an internal diff --git a/codex-rs/linux-sandbox/src/bwrap.rs b/codex-rs/linux-sandbox/src/bwrap.rs index 56c4ce70d..783c3a428 100644 --- a/codex-rs/linux-sandbox/src/bwrap.rs +++ b/codex-rs/linux-sandbox/src/bwrap.rs @@ -107,6 +107,9 @@ fn create_bwrap_flags_full_filesystem(command: Vec, options: BwrapOption "--bind".to_string(), "/".to_string(), "/".to_string(), + // Always enter a fresh user namespace so root inside a container does + // not need ambient CAP_SYS_ADMIN to create the remaining namespaces. + "--unshare-user".to_string(), "--unshare-pid".to_string(), ]; if options.network_mode.should_unshare_network() { @@ -132,6 +135,9 @@ fn create_bwrap_flags( args.push("--new-session".to_string()); args.push("--die-with-parent".to_string()); args.extend(create_filesystem_args(sandbox_policy, cwd)?); + // Request a user namespace explicitly rather than relying on bubblewrap's + // auto-enable behavior, which is skipped when the caller runs as uid 0. + args.push("--unshare-user".to_string()); // Isolate the PID namespace. args.push("--unshare-pid".to_string()); if options.network_mode.should_unshare_network() { @@ -425,6 +431,7 @@ mod tests { "--bind".to_string(), "/".to_string(), "/".to_string(), + "--unshare-user".to_string(), "--unshare-pid".to_string(), "--unshare-net".to_string(), "--proc".to_string(), diff --git a/codex-rs/linux-sandbox/src/linux_run_main_tests.rs b/codex-rs/linux-sandbox/src/linux_run_main_tests.rs index 11471afbe..cda509030 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main_tests.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main_tests.rs @@ -49,6 +49,7 @@ fn inserts_bwrap_argv0_before_command_separator() { "/".to_string(), "--dev".to_string(), "/dev".to_string(), + "--unshare-user".to_string(), "--unshare-pid".to_string(), "--proc".to_string(), "/proc".to_string(),