{"record":{"id":"408b088034f0e286","repo":"astrid-runtime/astrid","slug":"xattr-name-has-nul","errorCode":null,"errorMessage":"xattr name has NUL","messagePattern":"xattr name has NUL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-vfs/src/workspace_cow/overlayfs.rs","lineNumber":466,"sourceCode":"                     instead of committing an incorrect tree.\",\n                    path.display()\n                )));\n            }\n        }\n        ensure_no_opaque_markers(&path)?;\n    }\n    Ok(())\n}\n\n/// Presence check for a single extended attribute via `lgetxattr(2)`.\n/// `Ok(true)` = the attribute exists; `ENODATA`/`ENOTSUP` → `Ok(false)` (no such\n/// marker, or a filesystem without xattrs); any other error propagates so the\n/// caller fails closed.\nfn has_xattr(path: &Path, name: &str) -> io::Result<bool> {\n    let path_c = CString::new(path.as_os_str().as_bytes())\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"xattr path has NUL\"))?;\n    let name_c = CString::new(name)\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"xattr name has NUL\"))?;\n    // SAFETY: both C strings are NUL-terminated and outlive the call; a null\n    // value pointer with size 0 asks only for the current value size and writes\n    // nothing. `lgetxattr` does not follow a final symlink.\n    let rc = unsafe { libc::lgetxattr(path_c.as_ptr(), name_c.as_ptr(), std::ptr::null_mut(), 0) };\n    if rc >= 0 {\n        return Ok(true);\n    }\n    let err = io::Error::last_os_error();\n    match err.raw_os_error() {\n        Some(libc::ENODATA | libc::ENOTSUP) => Ok(false),\n        _ => Err(err),\n    }\n}\n\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};","sourceCodeStart":448,"sourceCodeEnd":484,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-vfs/src/workspace_cow/overlayfs.rs#L448-L484","documentation":"has_xattr probes an extended attribute via libc::lgetxattr, which requires NUL-terminated C strings. The xattr name passed as a Rust &str contained an embedded NUL byte, so it cannot be converted to a CString; the function fails closed with InvalidInput rather than calling the FFI with a truncated name.","triggerScenarios":"Calling ensure_no_opaque_markers (which calls has_xattr) on overlayfs directories when a derived xattr name contains an interior '\\0' — e.g. a name built from untrusted or corrupted metadata rather than a trusted constant like 'trusted.overlay.opaque'.","commonSituations":"Corrupted or attacker-influenced overlay metadata producing xattr names with embedded NULs; a bug in code that assembles xattr names from byte buffers instead of static strings.","solutions":["Verify the xattr name passed to has_xattr contains no interior NUL bytes before the call","Use only static, trusted xattr name constants (e.g. 'trusted.overlay.opaque') instead of dynamically built names","If names come from external data, sanitize/reject any byte string containing 0x00 before use","Log the offending name (hex-escaped) to identify where the NUL originates"],"exampleFix":"// before\nlet name = format!(\"trusted.overlay.{}\", user_blob);\nhas_xattr(path, &name)?;\n// after\nif name.bytes().any(|b| b == 0) { return Err(...); }\nhas_xattr(path, \"trusted.overlay.opaque\")?;","handlingStrategy":"validation","validationCode":"fn ensure_no_nul(s: &str) -> io::Result<()> { if s.bytes().any(|b| b == 0) { Err(io::Error::new(io::ErrorKind::InvalidInput, \"xattr name contains NUL\")) } else { Ok(()) } }","typeGuard":"fn is_nul_free(s: &str) -> bool { !s.as_bytes().contains(&0) }","tryCatchPattern":"match has_xattr(path, name) { Err(e) if e.kind() == io::ErrorKind::InvalidInput => { /* skip/reject this xattr name */ }, Err(e) => return Err(e), Ok(has) => /* proceed */, }","preventionTips":["Use static string literals for xattr names","Never build xattr names from raw byte buffers","Validate any externally-sourced name for NUL before use"],"tags":["ffi","xattr","invalid-input","cstring"],"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"}