astrid-runtime/astrid · error

mount target has NUL

Error message

mount target has NUL

What it means

mount_overlay converts the mount target directory path to a CString for mount(2). Paths on Unix may contain any byte except NUL; if the target path contains an embedded NUL byte, CString::new fails and the function returns InvalidInput 'mount target has NUL' instead of calling mount with a truncated path.

Solutions

  1. Check where the target Path is built and strip/reject interior NUL bytes
  2. Validate the target with target.as_os_str().as_bytes().contains(&0) before calling mount
  3. Ensure path buffers are truncated at the first NUL before conversion to Path
  4. Use only paths from Rust string APIs (fs APIs, clap, env), which cannot contain NUL

Example fix

// before
mount_overlay(&Path::from_raw_bytes(buf), &data)?;
// after
let bytes = buf.split(|b| *b == 0).next().unwrap();
let target = Path::new(std::str::from_utf8(bytes)?);
mount_overlay(target, &data)?;
Defensive patterns

Strategy: validation

Validate before calling

fn assert_nul_free(p: &Path) -> io::Result<()> { if p.as_os_str().as_bytes().contains(&0) { Err(io::Error::new(io::ErrorKind::InvalidInput, "mount target contains NUL")) } else { Ok(()) } }

Type guard

fn nul_free_path(p: &Path) -> bool { !p.as_os_str().as_bytes().contains(&0) }

Try / catch

if let Err(e) = mount(target, &layers) { if e.kind() == io::ErrorKind::InvalidInput { /* reject the corrupted target path */ } return Err(e); }

Prevention

When it happens

Trigger: Calling mount (→ mount_overlay) with a target directory Path whose OsStr bytes include an interior '\0' — e.g. a path assembled from raw byte buffers or FFI-originated data.

Common situations: Paths constructed from C-FFI data or binary protocols; a bug where a path buffer was not trimmed at its terminator, keeping trailing/garbage NUL bytes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/0fcfd27410f894c7. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-vfs/src/workspace_cow/overlayfs.rs:498

/// A short, deterministic hex digest of a path, used only as a directory name.
fn path_hash(path: &Path) -> String {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};
    let key = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
    let mut hasher = DefaultHasher::new();
    key.hash(&mut hasher);
    format!("{:016x}", hasher.finish())
}

/// `mount("overlay", target, "overlay", 0, data)`.
fn mount_overlay(target: &Path, data: &str) -> io::Result<()> {
    let src = CString::new("overlay")
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "mount source has NUL"))?;
    let fstype = CString::new("overlay")
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "mount fstype has NUL"))?;
    let target_c = CString::new(target.as_os_str().as_bytes())
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "mount target has NUL"))?;
    let data_c = CString::new(data)
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "mount data has NUL"))?;
    // SAFETY: all four pointers are valid, NUL-terminated C strings that outlive
    // the call; `mount` reads them and returns a status code, retaining no
    // pointers. `data` is the overlayfs option string.
    let rc = unsafe {
        libc::mount(
            src.as_ptr(),
            target_c.as_ptr(),
            fstype.as_ptr(),
            0,
            data_c.as_ptr().cast(),
        )
    };
    if rc != 0 {
        return Err(io::Error::last_os_error());
    }
    Ok(())

View on GitHub (pinned to affd8760f4)