Linux sandbox regression in v0.81: workspace-write fails with EPERM writing /proc/self/uid_map
What version of Codex is running?
codex-cli 0.81.0
What subscription do you have?
Pro
Which model were you using?
5.2 codex
What platform is your computer?
Linux 6.8.0-90-generic x86_64 x86_64
What issue are you seeing?
After upgrading to v0.81, every command prompts “command failed; retry without sandbox?”.
Running the sandbox directly fails:
thread 'main' panicked at linux-sandbox/src/linux_run_main.rs:30:9:
error running landlock: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" })
What steps can reproduce the bug?
1) Run the Linux sandbox directly:
codex-linux-sandbox --sandbox-policy-cwd "$PWD" --sandbox-policy '{"type":"workspace-write"}' -- true
2) Observe it fails with EPERM as above.
3) Alternatively, run Codex normally with the default workspace-write sandbox and execute any command; it repeatedly prompts “command failed; retry without sandbox?”.
What is the expected behavior?
Sandbox should initialize successfully and commands should run without asking to retry outside the sandbox.
Additional information
- This was not happening on v0.80.
- Root cause on this host: after unshare, codex-linux-sandbox writes uid_map using getuid()/getgid() from inside the user namespace, which become 65534 (overflow UID). Writing “0 65534 1” to /proc/self/uid_map fails with EPERM.
- Fix that resolves it here: capture original UID/GID before unshare and use those for uid_map/gid_map in codex-rs/linux-sandbox/src/mounts.rs.
- Sysctls permit user namespaces and Landlock is loaded.
The patch that fixes the issue (verified locally):
diff --git a/codex-rs/linux-sandbox/src/mounts.rs b/codex-rs/linux-sandbox/src/mounts.rs
index f84afad8d..713d50108 100644
--- a/codex-rs/linux-sandbox/src/mounts.rs
+++ b/codex-rs/linux-sandbox/src/mounts.rs
@@ -20,13 +20,16 @@ pub(crate) fn apply_read_only_mounts(sandbox_policy: &SandboxPolicy, cwd: &Path)
return Ok(());
}
+ let original_uid = unsafe { libc::getuid() };
+ let original_gid = unsafe { libc::getgid() };
+
// Root can unshare the mount namespace directly; non-root needs a user
// namespace to gain capabilities for remounting.
if is_running_as_root() {
unshare_mount_namespace()?;
} else {
unshare_user_and_mount_namespaces()?;
- write_user_namespace_maps()?;
+ write_user_namespace_maps(original_uid, original_gid)?;
}
make_mounts_private()?;
@@ -153,11 +156,9 @@ struct CapUserData {
const LINUX_CAPABILITY_VERSION_3: u32 = 0x2008_0522;
/// Map the current uid/gid to root inside the user namespace.
-fn write_user_namespace_maps() -> Result<()> {
+fn write_user_namespace_maps(uid: libc::uid_t, gid: libc::gid_t) -> Result<()> {
write_proc_file("/proc/self/setgroups", "deny\n")?;
- let uid = unsafe { libc::getuid() };
- let gid = unsafe { libc::getgid() };
write_proc_file("/proc/self/uid_map", format!("0 {uid} 1\n"))?;
write_proc_file("/proc/self/gid_map", format!("0 {gid} 1\n"))?;
Ok(())
(The above is generated with codex.)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗