{"record":{"id":"73e603f756cf39f3","repo":"openai/codex","slug":"failed-to-read-fd-flags-for-preserved-bubblewrap-f","errorCode":null,"errorMessage":"failed to read fd flags for preserved bubblewrap file descriptor {fd}: {err}","messagePattern":"failed to read fd flags for preserved bubblewrap file descriptor (.+?): (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"codex-rs/linux-sandbox/src/exec_util.rs","lineNumber":27,"sourceCode":"            Ok(value) => cstrings.push(value),\n            Err(err) => panic!(\"failed to convert argv to CString: {err}\"),\n        }\n    }\n    cstrings\n}\n\npub(crate) fn make_files_inheritable(files: &[File]) {\n    for file in files {\n        clear_cloexec(file.as_raw_fd());\n    }\n}\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;","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/linux-sandbox/src/exec_util.rs#L9-L45","documentation":"File descriptors passed as preserved_files must survive execv into the sandbox, so make_files_inheritable clears FD_CLOEXEC on each. clear_cloexec first reads the descriptor flags with fcntl(fd, F_GETFD); a negative return (typically EBADF, meaning the fd is already closed) panics before any flags can be changed.","triggerScenarios":"A File in preserved_files was dropped or closed before the sandbox exec, so its fd number is invalid when F_GETFD runs; a double close of the same handle; another thread closing shared descriptors concurrently.","commonSituations":"RAII scope of a preserved File ending before the launch call; cloning and dropping File handles across task or thread boundaries; ownership bugs where the caller believes it still holds the descriptor.","solutions":["Keep every preserved File owned and in scope through the sandbox launch call (exec does not return on success, so ownership must outlive the call).","Audit for early drops or double closes of the preserved handles (RAII review, fd counting with lsof).","Ensure no other thread closes the same descriptors during launch."],"exampleFix":"// before: both handles dropped before launch, fd invalid by exec time\nlet preserved = {\n    let file = File::open(&path)?;\n    vec![file.try_clone()?, file]\n}; // dropped here -> EBADF panic in clear_cloexec\nlauncher.exec(argv, preserved);\n\n// after: keep ownership until the call\nlet file = File::open(&path)?;\nlauncher.exec(argv, vec![file]);","handlingStrategy":"validation","validationCode":"use std::os::fd::AsRawFd;\nfn fds_valid(files: &[std::fs::File]) -> bool {\n    files.iter().all(|f| {\n        let fd = f.as_raw_fd();\n        // SAFETY: read-only descriptor flag query on an fd we own\n        unsafe { libc::fcntl(fd, libc::F_GETFD) } >= 0\n    })\n}\nassert!(fds_valid(&preserved));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Move (do not clone-and-drop) preserved Files into the launch call so they stay open until exec.","Never share preserved descriptors with code that may drop or close them concurrently.","Treat EBADF on your own fd list as an ownership bug, not a transient error."],"tags":["rust","linux","file-descriptor","fcntl","sandbox","panic"],"backgroundTag":"bad-file-descriptor","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}