{"record":{"id":"3c33edca59394f1f","repo":"openai/codex","slug":"failed-to-convert-argv-to-cstring-err-3c33ed","errorCode":null,"errorMessage":"failed to convert argv to CString: {err}","messagePattern":"failed to convert argv to CString: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"codex-rs/linux-sandbox/src/exec_util.rs","lineNumber":10,"sourceCode":"use std::ffi::CString;\nuse std::fs::File;\nuse std::os::fd::AsRawFd;\n\npub(crate) fn argv_to_cstrings(argv: &[String]) -> Vec<CString> {\n    let mut cstrings: Vec<CString> = Vec::with_capacity(argv.len());\n    for arg in argv {\n        match CString::new(arg.as_str()) {\n            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    }","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/linux-sandbox/src/exec_util.rs#L1-L28","documentation":"Before exec'ing bwrap, argv_to_cstrings converts every argument to a C string. Rust Strings may contain interior NUL bytes, but C argv entries cannot, so CString::new fails on an argument holding a NUL byte and the helper panics with the NulError, which reports the offending byte position.","triggerScenarios":"Passing any argument to the sandboxed command that contains a NUL byte: a filename, env value, or flag built from unvalidated input; a marshalling bug that converts length-prefixed or NUL-separated buffers into one String; binary data leaking into argv.","commonSituations":"User-supplied filenames or commands containing NUL; IPC layers that join NUL-separated fields instead of splitting them; unchecked UTF-8 conversions of OS strings; fuzzing.","solutions":["Locate the offending argument using the NUL position in the panic message and sanitize the data at its source.","Reject or strip NUL bytes in user input before constructing the command line.","If the data was NUL-separated by design, split it into separate arguments instead of one embedded-NUL string."],"exampleFix":"// before\nlet argv = vec![program, user_input]; // user_input with a NUL byte panics in argv_to_cstrings\n\n// after: validate before the sandbox call\nfor (i, arg) in argv.iter().enumerate() {\n    if arg.contains('\\u{0}') {\n        return Err(format!(\"argument {i} contains a NUL byte\"));\n    }\n}","handlingStrategy":"validation","validationCode":"fn args_are_cstring_safe(argv: &[String]) -> Result<(), usize> {\n    argv.iter().position(|a| a.contains('\\u{0}')).map_or(Ok(()), Err)\n}\nargs_are_cstring_safe(&argv)?;","typeGuard":"fn is_cstring_safe(s: &str) -> bool { !s.contains('\\u{0}') }","tryCatchPattern":null,"preventionTips":["Validate command strings and filenames for NUL bytes at the trust boundary where user input enters.","Never build argv by concatenating NUL-separated buffers; split first.","The panic message includes the offending byte position; use it to pinpoint the bad argument."],"tags":["rust","c-string","argv","input-validation","panic"],"backgroundTag":"nul-byte-in-string","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}