{"record":{"id":"dfd5543db40c949b","repo":"astrid-runtime/astrid","slug":"umount-target-has-nul","errorCode":null,"errorMessage":"umount target has NUL","messagePattern":"umount target has NUL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-vfs/src/workspace_cow/overlayfs.rs","lineNumber":523,"sourceCode":"        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(())\n}\n\n/// `umount2(target, MNT_DETACH)` — lazy unmount so a busy mountpoint still\n/// detaches.\nfn umount(target: &Path) -> io::Result<()> {\n    let target_c = CString::new(target.as_os_str().as_bytes())\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"umount target has NUL\"))?;\n    // SAFETY: `target_c` is a valid, NUL-terminated C string outliving the call.\n    let rc = unsafe { libc::umount2(target_c.as_ptr(), libc::MNT_DETACH) };\n    if rc != 0 {\n        return Err(io::Error::last_os_error());\n    }\n    Ok(())\n}\n","sourceCodeStart":505,"sourceCodeEnd":531,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-vfs/src/workspace_cow/overlayfs.rs#L505-L531","documentation":"umount converts the mountpoint path to a CString for umount2(2) with MNT_DETACH. If the target path contains an embedded NUL byte, CString::new fails and the function returns InvalidInput 'umount target has NUL' rather than calling the syscall with a truncated path.","triggerScenarios":"Calling unmount_path (→ umount) with a mountpoint Path whose OsStr bytes contain an interior '\\0', typically from untrimmed byte buffers or FFI-derived data.","commonSituations":"Mountpoint paths reconstructed from binary IPC or config blobs; bookkeeping bugs where a stored path kept trailing NUL padding.","solutions":["Validate the stored mountpoint for NUL bytes before calling unmount_path","Ensure paths are truncated at the first NUL when converted from byte buffers","Prefer paths sourced from Rust std APIs, which forbid interior NULs","Fix the record-keeping that saved the corrupted mountpoint path"],"exampleFix":"// before\nlet mp = Path::new(std::ffi::OsStr::from_bytes(&padded_buf));\nunmount_path(mp)?;\n// after\nlet trimmed: &[u8] = padded_buf.split(|b| *b == 0).next().unwrap();\nlet mp = Path::new(std::ffi::OsStr::from_bytes(trimmed));\nunmount_path(mp)?;","handlingStrategy":"validation","validationCode":"fn assert_umount_target(p: &Path) -> io::Result<()> { if p.as_os_str().as_bytes().contains(&0) { Err(io::Error::new(io::ErrorKind::InvalidInput, \"mountpoint contains NUL\")) } else { Ok(()) } }","typeGuard":"fn mountpoint_ok(p: &Path) -> bool { !p.as_os_str().as_bytes().contains(&0) }","tryCatchPattern":"if let Err(e) = unmount_path(mp) { if e.kind() == io::ErrorKind::InvalidInput { /* repair/re-derive the stored mountpoint */ } return Err(e.into()); }","preventionTips":["Store mountpoints as PathBuf from Rust APIs, not raw byte arrays","Trim byte buffers at the first NUL before path conversion","Re-derive mountpoints from the mount table instead of persisted blobs when possible"],"tags":["ffi","umount","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"}