denoland/deno · error
program path contains NUL
Error message
program path contains NUL
What it means
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.
Source
Thrown at cli/tools/desktop.rs:5728
if !self.exited {
// SAFETY: `kill(2)` with a pid we spawned is always safe to call; a
// stale pid simply returns ESRCH, which we ignore.
unsafe {
libc::kill(self.pid, libc::SIGKILL);
}
}
}
}
/// Flattened `(program, argv, envp, cwd)` for `posix_spawn`.
type SpawnArgs = (CString, Vec<CString>, Vec<CString>, Option<CString>);
/// Convert a std::process::Command into the argv/envp/cwd tuple posix_spawn
/// needs. Inherits the parent's env, then applies Command::env() overrides
/// (matching what std::process::Command does internally).
fn flatten(cmd: &std::process::Command) -> std::io::Result<SpawnArgs> {
let program = CString::new(cmd.get_program().as_bytes()).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"program path contains NUL",
)
})?;
let mut argv: Vec<CString> = Vec::with_capacity(cmd.get_args().len() + 1);
argv.push(program.clone());
for a in cmd.get_args() {
argv.push(CString::new(a.as_bytes()).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"argv contains NUL",
)
})?);
}
let mut env_map: std::collections::BTreeMap<OsString, OsString> =
std::env::vars_os().collect();
for (k, v) in cmd.get_envs() {
match v {View on GitHub (pinned to f7822238ca)
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.
Example fix
// before
let mut cmd = std::process::Command::new(&program_path); // program_path contains 0x00
// after
fn reject_nul(bytes: &[u8], what: &str) -> std::io::Result<()> {
if bytes.contains(&0) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{what} contains NUL"),
));
}
Ok(())
}
reject_nul(program_path.as_bytes(), "program path")?;
let mut cmd = std::process::Command::new(&program_path); Defensive patterns
Strategy: validation
Validate before calling
fn reject_nul(bytes: &[u8], what: &str) -> std::io::Result<()> {
if bytes.contains(&0) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{what} contains NUL"),
));
}
Ok(())
}
// before spawning
reject_nul(cmd.get_program().as_bytes(), "program path")?; Type guard
fn is_nul_free(s: &std::ffi::OsStr) -> bool {
!s.as_bytes().contains(&0)
} Try / catch
match spawn(cmd) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {
// reject the input upstream; do not retry unchanged
}
other => other,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- argv contains NUL
- env contains NUL
- cwd has NUL
- nul byte found in provided data
- Windows file names may not contain `"` or end with `\`
AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20).
Data as JSON: /api/errors/b533d22e15c5757a.
Report an issue: GitHub.