{"record":{"id":"0fcfd27410f894c7","repo":"astrid-runtime/astrid","slug":"mount-target-has-nul","errorCode":null,"errorMessage":"mount target has NUL","messagePattern":"mount target has NUL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-vfs/src/workspace_cow/overlayfs.rs","lineNumber":498,"sourceCode":"\n/// A short, deterministic hex digest of a path, used only as a directory name.\nfn path_hash(path: &Path) -> String {\n    use std::collections::hash_map::DefaultHasher;\n    use std::hash::{Hash, Hasher};\n    let key = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());\n    let mut hasher = DefaultHasher::new();\n    key.hash(&mut hasher);\n    format!(\"{:016x}\", hasher.finish())\n}\n\n/// `mount(\"overlay\", target, \"overlay\", 0, data)`.\nfn mount_overlay(target: &Path, data: &str) -> io::Result<()> {\n    let src = CString::new(\"overlay\")\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"mount source has NUL\"))?;\n    let fstype = CString::new(\"overlay\")\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"mount fstype has NUL\"))?;\n    let target_c = CString::new(target.as_os_str().as_bytes())\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"mount target has NUL\"))?;\n    let data_c = CString::new(data)\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"mount data has NUL\"))?;\n    // SAFETY: all four pointers are valid, NUL-terminated C strings that outlive\n    // the call; `mount` reads them and returns a status code, retaining no\n    // pointers. `data` is the overlayfs option string.\n    let rc = unsafe {\n        libc::mount(\n            src.as_ptr(),\n            target_c.as_ptr(),\n            fstype.as_ptr(),\n            0,\n            data_c.as_ptr().cast(),\n        )\n    };\n    if rc != 0 {\n        return Err(io::Error::last_os_error());\n    }\n    Ok(())","sourceCodeStart":480,"sourceCodeEnd":516,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-vfs/src/workspace_cow/overlayfs.rs#L480-L516","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check where the target Path is built and strip/reject interior NUL bytes","Validate the target with target.as_os_str().as_bytes().contains(&0) before calling mount","Ensure path buffers are truncated at the first NUL before conversion to Path","Use only paths from Rust string APIs (fs APIs, clap, env), which cannot contain NUL"],"exampleFix":"// before\nmount_overlay(&Path::from_raw_bytes(buf), &data)?;\n// after\nlet bytes = buf.split(|b| *b == 0).next().unwrap();\nlet target = Path::new(std::str::from_utf8(bytes)?);\nmount_overlay(target, &data)?;","handlingStrategy":"validation","validationCode":"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(()) } }","typeGuard":"fn nul_free_path(p: &Path) -> bool { !p.as_os_str().as_bytes().contains(&0) }","tryCatchPattern":"if let Err(e) = mount(target, &layers) { if e.kind() == io::ErrorKind::InvalidInput { /* reject the corrupted target path */ } return Err(e); }","preventionTips":["Source paths from Rust std APIs, which forbid interior NULs","Truncate byte buffers at the first NUL before converting to Path","Validate paths at the configuration boundary"],"tags":["ffi","mount","path","invalid-input"],"backgroundTag":"invalid-argument-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}