denoland/deno · error

argv contains NUL

Error message

argv contains NUL

What it means

Same `flatten()` conversion for posix_spawn, applied to each argument in `cmd.get_args()`: any argv entry containing an interior NUL byte makes `CString::new` fail, mapped to `io::ErrorKind::InvalidInput` ("argv contains NUL"). C argv entries are NUL-terminated strings, so a 0x00 inside an argument is unrepresentable.

Source

Thrown at cli/tools/desktop.rs:5737

  /// 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 {
        Some(v) => {
          env_map.insert(k.to_os_string(), v.to_os_string());
        }
        None => {
          env_map.remove(k);
        }
      }
    }
    let envp: Vec<CString> = env_map

View on GitHub (pinned to f7822238ca)

Solutions

  1. Run every externally sourced argument through a NUL check before adding it to the Command.
  2. Reject (preferred) or strip 0x00 at the input boundary — argument values containing NUL are almost always corrupt input.
  3. Add a unit test that feeds control bytes into your spawn wrapper to prove it fails closed.

Example fix

// before
for arg in user_args { cmd.arg(arg); }

// 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(())
}
for arg in &user_args {
  reject_nul(arg.as_bytes(), "argv")?;
  cmd.arg(arg);
}
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(())
}
for a in cmd.get_args() {
  reject_nul(a.as_bytes(), "argv")?;
}

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 => {
    // identify which argument had 0x00 and reject the request
  }
  other => other,
}

Prevention

When it happens

Trigger: A `Command::arg(...)` value containing 0x00 — commonly a string built from user input, a network payload, or a mis-decoded buffer — reaching the posix_spawn flattening path.

Common situations: CLI wrappers forwarding web/form input as arguments; data pipelines where a length-prefixed buffer was decoded as a plain string leaving trailing NULs; test fixtures with embedded control bytes.

Related errors


AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20). Data as JSON: /api/errors/d3e26d260deb1ec5. Report an issue: GitHub.