{"record":{"id":"b533d22e15c5757a","repo":"denoland/deno","slug":"program-path-contains-nul","errorCode":null,"errorMessage":"program path contains NUL","messagePattern":"program path contains NUL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/tools/desktop.rs","lineNumber":5728,"sourceCode":"      if !self.exited {\n        // SAFETY: `kill(2)` with a pid we spawned is always safe to call; a\n        // stale pid simply returns ESRCH, which we ignore.\n        unsafe {\n          libc::kill(self.pid, libc::SIGKILL);\n        }\n      }\n    }\n  }\n\n  /// Flattened `(program, argv, envp, cwd)` for `posix_spawn`.\n  type SpawnArgs = (CString, Vec<CString>, Vec<CString>, Option<CString>);\n\n  /// Convert a std::process::Command into the argv/envp/cwd tuple posix_spawn\n  /// needs. Inherits the parent's env, then applies Command::env() overrides\n  /// (matching what std::process::Command does internally).\n  fn flatten(cmd: &std::process::Command) -> std::io::Result<SpawnArgs> {\n    let program = CString::new(cmd.get_program().as_bytes()).map_err(|_| {\n      std::io::Error::new(\n        std::io::ErrorKind::InvalidInput,\n        \"program path contains NUL\",\n      )\n    })?;\n    let mut argv: Vec<CString> = Vec::with_capacity(cmd.get_args().len() + 1);\n    argv.push(program.clone());\n    for a in cmd.get_args() {\n      argv.push(CString::new(a.as_bytes()).map_err(|_| {\n        std::io::Error::new(\n          std::io::ErrorKind::InvalidInput,\n          \"argv contains NUL\",\n        )\n      })?);\n    }\n    let mut env_map: std::collections::BTreeMap<OsString, OsString> =\n      std::env::vars_os().collect();\n    for (k, v) in cmd.get_envs() {\n      match v {","sourceCodeStart":5710,"sourceCodeEnd":5746,"githubUrl":"https://github.com/denoland/deno/blob/f7822238cab635a3a19f99f493f675fa81a7f9d8/cli/tools/desktop.rs#L5710-L5746","documentation":"The desktop tooling spawns subprocesses through raw `posix_spawn`, so `std::process::Command` values are flattened into C strings by `flatten()`. `CString::new` fails when the program path contains an interior NUL byte (0x00), because NUL terminates C strings and is illegal in OS paths; the failure is mapped to `io::ErrorKind::InvalidInput` with this message.","triggerScenarios":"Spawning a subprocess whose program path was assembled from data containing a NUL byte — e.g. user/web input, a truncated buffer read as a path, or bytes misinterpreted as UTF-8 — reaching `posix_spawn`-based Child::spawn in cli/tools/desktop.rs.","commonSituations":"Passing request-derived strings into a spawn call without sanitization; binary protocols feeding path fields; environment or config values with embedded NULs from a broken upstream encoder.","solutions":["Validate the program path rejects NUL before building the Command (a shared `reject_nul` helper).","Trace and fix the producer of the NUL byte — the path is corrupted upstream, not merely unwelcome here.","Sanitize at the trust boundary: strip or reject control bytes in user-supplied paths on ingestion."],"exampleFix":"// before\nlet mut cmd = std::process::Command::new(&program_path); // program_path contains 0x00\n\n// after\nfn reject_nul(bytes: &[u8], what: &str) -> std::io::Result<()> {\n  if bytes.contains(&0) {\n    return Err(std::io::Error::new(\n      std::io::ErrorKind::InvalidInput,\n      format!(\"{what} contains NUL\"),\n    ));\n  }\n  Ok(())\n}\nreject_nul(program_path.as_bytes(), \"program path\")?;\nlet mut cmd = std::process::Command::new(&program_path);","handlingStrategy":"validation","validationCode":"fn reject_nul(bytes: &[u8], what: &str) -> std::io::Result<()> {\n  if bytes.contains(&0) {\n    return Err(std::io::Error::new(\n      std::io::ErrorKind::InvalidInput,\n      format!(\"{what} contains NUL\"),\n    ));\n  }\n  Ok(())\n}\n// before spawning\nreject_nul(cmd.get_program().as_bytes(), \"program path\")?;","typeGuard":"fn is_nul_free(s: &std::ffi::OsStr) -> bool {\n  !s.as_bytes().contains(&0)\n}","tryCatchPattern":"match spawn(cmd) {\n  Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {\n    // reject the input upstream; do not retry unchanged\n  }\n  other => other,\n}","preventionTips":["Sanitize control bytes at the input boundary, not at spawn time","Treat NUL in a program path as corrupt input — investigate the producer","Add fuzz tests feeding control bytes into spawn wrappers"],"tags":["subprocess","posix-spawn","nul-byte","invalid-input","desktop"],"backgroundTag":"nul-byte-in-path","analyzedSha":"f7822238cab635a3a19f99f493f675fa81a7f9d8","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}