denoland/deno · error

cwd has NUL

Error message

cwd has NUL

What it means

The last NUL check in `flatten()`: when `Command::get_current_dir()` is set, the cwd path must also convert to a C string for `posix_chdir` inside spawn. An embedded 0x00 makes `CString::new` fail and the spawn is rejected with `io::ErrorKind::InvalidInput` ("cwd has NUL").

Source

Thrown at cli/tools/desktop.rs:5774

      .into_iter()
      .map(|(k, v)| {
        let mut s =
          Vec::with_capacity(k.as_bytes().len() + 1 + v.as_bytes().len());
        s.extend_from_slice(k.as_bytes());
        s.push(b'=');
        s.extend_from_slice(v.as_bytes());
        CString::new(s).map_err(|_| {
          std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "env contains NUL",
          )
        })
      })
      .collect::<std::io::Result<_>>()?;
    let cwd = match cmd.get_current_dir() {
      Some(p) => {
        Some(CString::new(p.as_os_str().as_bytes()).map_err(|_| {
          std::io::Error::new(std::io::ErrorKind::InvalidInput, "cwd has NUL")
        })?)
      }
      None => None,
    };
    Ok((program, argv, envp, cwd))
  }

  pub fn spawn(cmd: &std::process::Command) -> std::io::Result<Child> {
    let (program, argv, envp, cwd) = flatten(cmd)?;
    let mut argv_ptrs: Vec<*mut libc::c_char> =
      argv.iter().map(|c| c.as_ptr() as *mut _).collect();
    argv_ptrs.push(std::ptr::null_mut());
    let mut envp_ptrs: Vec<*mut libc::c_char> =
      envp.iter().map(|c| c.as_ptr() as *mut _).collect();
    envp_ptrs.push(std::ptr::null_mut());

    // SAFETY: posix_spawn FFI. We initialize attrs/actions before use,
    // destroy them on every exit path, and keep argv/envp CString backing

View on GitHub (pinned to f7822238ca)

Solutions

  1. NUL-check the cwd path before calling `current_dir`, same as for the program path.
  2. Reject directory names with control bytes at creation time so they never become cwd candidates.
  3. If the cwd comes from untrusted archive metadata, canonicalize and validate it is a real directory first.

Example fix

// before
let mut cmd = std::process::Command::new(prog);
cmd.current_dir(&user_dir); // user_dir contains 0x00

// after
if user_dir.as_os_str().as_bytes().contains(&0) {
  return Err(std::io::Error::new(
    std::io::ErrorKind::InvalidInput,
    "cwd has NUL",
  ));
}
let mut cmd = std::process::Command::new(prog);
cmd.current_dir(&user_dir);
Defensive patterns

Strategy: validation

Validate before calling

if let Some(cwd) = cmd.get_current_dir() {
  if cwd.as_os_str().as_bytes().contains(&0) {
    return Err(std::io::Error::new(
      std::io::ErrorKind::InvalidInput,
      "cwd has NUL",
    ));
  }
}

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 => {
    // cwd was invalid — fall back to spawning in the current directory
  }
  other => other,
}

Prevention

When it happens

Trigger: Calling `Command::current_dir(p)` where `p` contains a NUL byte — user-controlled directory names, decoded buffers, or path joins with corrupt components — before the posix_spawn call.

Common situations: Sandbox/workspace tools that chdir into user-named directories; zip/tar extraction tools where entry names feed cwd; string truncation bugs producing trailing NULs.

Related errors


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