openai/codex · critical
failed to clear CLOEXEC for preserved bubblewrap file descri
Error message
failed to clear CLOEXEC for preserved bubblewrap file descriptor {fd}: {err} What it means
After reading flags, clear_cloexec writes them back without FD_CLOEXEC via fcntl(fd, F_SETFD) and panics on a negative return. The preceding flag read succeeded, so failure usually means the descriptor was closed between the two fcntl calls (a race), or a seccomp/container filter or exotic kernel denies F_SETFD (EBADF, EINVAL, or EPERM).
Source
Thrown at codex-rs/linux-sandbox/src/exec_util.rs:38
}
fn clear_cloexec(fd: libc::c_int) {
// SAFETY: `fd` is an owned descriptor kept alive by `files`.
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags < 0 {
let err = std::io::Error::last_os_error();
panic!("failed to read fd flags for preserved bubblewrap file descriptor {fd}: {err}");
}
let cleared_flags = flags & !libc::FD_CLOEXEC;
if cleared_flags == flags {
return;
}
// SAFETY: `fd` is valid and we are only clearing FD_CLOEXEC.
let result = unsafe { libc::fcntl(fd, libc::F_SETFD, cleared_flags) };
if result < 0 {
let err = std::io::Error::last_os_error();
panic!("failed to clear CLOEXEC for preserved bubblewrap file descriptor {fd}: {err}");
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use tempfile::NamedTempFile;
#[test]
fn preserved_files_are_made_inheritable() {
let file = NamedTempFile::new().expect("temp file");
set_cloexec(file.as_file().as_raw_fd());
make_files_inheritable(std::slice::from_ref(file.as_file()));
assert_eq!(fd_flags(file.as_file().as_raw_fd()) & libc::FD_CLOEXEC, 0);
}View on GitHub (pinned to 339751715c)
Solutions
- Remove concurrent close/drop of preserved files during launch: hand exclusive ownership to the launch path.
- Inspect seccomp profiles and LSM audit logs for denied fcntl(F_SETFD) and allow it for the launcher.
- If it persists with a clean ownership model, capture the errno and report upstream with the sandbox launch sequence.
Defensive patterns
Strategy: validation
Validate before calling
use std::os::fd::AsRawFd;
fn cloexec_cleared(files: &[std::fs::File]) -> bool {
files.iter().all(|f| {
let fd = f.as_raw_fd();
// SAFETY: query then clear FD_CLOEXEC on fds owned exclusively by this thread
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
flags >= 0 && unsafe { libc::fcntl(fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC) } >= 0
})
} Prevention
- Keep single-threaded ownership of preserved descriptors from creation until exec to remove the race window.
- In seccomp-filtered environments, whitelist fcntl with F_GETFD and F_SETFD for the launcher.
When it happens
Trigger: Another thread drops or closes a preserved File between the F_GETFD and F_SETFD calls during sandbox launch; a seccomp profile or LSM filtering fcntl on the process; kernel resource exhaustion (ENOMEM).
Common situations: Concurrent shutdown paths closing shared descriptors while a sandbox launch is in flight; hardened containers with aggressive seccomp policies; otherwise extremely rare.
Related errors
- failed to read fd flags for preserved bubblewrap file descri
- failed to open bundled bubblewrap {}: {err}
- invalid bundled bubblewrap fd path: {err}
- failed to exec bundled bubblewrap {} via {fd_path}: {err}
- failed to normalize bundled bubblewrap path {}: {err}
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/f2fb2d858e7b7b0e.
Report an issue: GitHub.