{"record":{"id":"f2fb2d858e7b7b0e","repo":"openai/codex","slug":"failed-to-clear-cloexec-for-preserved-bubblewrap-f","errorCode":null,"errorMessage":"failed to clear CLOEXEC for preserved bubblewrap file descriptor {fd}: {err}","messagePattern":"failed to clear CLOEXEC for preserved bubblewrap file descriptor (.+?): (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"codex-rs/linux-sandbox/src/exec_util.rs","lineNumber":38,"sourceCode":"}\n\nfn clear_cloexec(fd: libc::c_int) {\n    // SAFETY: `fd` is an owned descriptor kept alive by `files`.\n    let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };\n    if flags < 0 {\n        let err = std::io::Error::last_os_error();\n        panic!(\"failed to read fd flags for preserved bubblewrap file descriptor {fd}: {err}\");\n    }\n    let cleared_flags = flags & !libc::FD_CLOEXEC;\n    if cleared_flags == flags {\n        return;\n    }\n\n    // SAFETY: `fd` is valid and we are only clearing FD_CLOEXEC.\n    let result = unsafe { libc::fcntl(fd, libc::F_SETFD, cleared_flags) };\n    if result < 0 {\n        let err = std::io::Error::last_os_error();\n        panic!(\"failed to clear CLOEXEC for preserved bubblewrap file descriptor {fd}: {err}\");\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use pretty_assertions::assert_eq;\n    use tempfile::NamedTempFile;\n\n    #[test]\n    fn preserved_files_are_made_inheritable() {\n        let file = NamedTempFile::new().expect(\"temp file\");\n        set_cloexec(file.as_file().as_raw_fd());\n\n        make_files_inheritable(std::slice::from_ref(file.as_file()));\n\n        assert_eq!(fd_flags(file.as_file().as_raw_fd()) & libc::FD_CLOEXEC, 0);\n    }","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/linux-sandbox/src/exec_util.rs#L20-L56","documentation":"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).","triggerScenarios":"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).","commonSituations":"Concurrent shutdown paths closing shared descriptors while a sandbox launch is in flight; hardened containers with aggressive seccomp policies; otherwise extremely rare.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use std::os::fd::AsRawFd;\nfn cloexec_cleared(files: &[std::fs::File]) -> bool {\n    files.iter().all(|f| {\n        let fd = f.as_raw_fd();\n        // SAFETY: query then clear FD_CLOEXEC on fds owned exclusively by this thread\n        let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };\n        flags >= 0 && unsafe { libc::fcntl(fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC) } >= 0\n    })\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["rust","linux","file-descriptor","fcntl","cloexec","race-condition","panic"],"backgroundTag":"bad-file-descriptor","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}