From c66717136fa8e38ee0aac8c5ba7b1e3acd74e7c3 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 23 Sep 2026 05:16:50 +0200 Subject: [PATCH] Fix two fixture bugs in the R-I21 xattr coverage tests, found by running them for real Measured on Yggdrasil against a2b8057, both real container-level facts, not code-under-test bugs: - digest_artifact_differs_when_only_a_directorys_extended_attribute_differs set the xattr on the artifact root itself. digest_tree(root, root, ...) never hashes the root's own metadata, only entries it reads out of some directory's read_dir (the same fact the pre-existing directory-mode/gid tests already build around), so the two digests were identical. Moved the xattr onto a child directory, matching those tests' own shape. - digest_artifact_differs_when_only_a_symlinks_extended_attribute_differs used the `user` namespace. `lsetxattr(..., "user.*", ...)` on a symlink fails EPERM even as root: the kernel restricts `user` to regular files and directories regardless of privilege. Switched to `security.capability`, the same setuid-equivalent value the frozen-source xattr tests already use, which has no such restriction. Co-Authored-By: Claude Sonnet 5 --- src/drivers.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/drivers.rs b/src/drivers.rs index 3bcabcc..5f916ed 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -12960,13 +12960,22 @@ mod tests { } /// R-I21: the artifact digest's directory-arm xattr term. + /// + /// `digest_tree(root, root, ...)` only ever hashes an entry it reads out + /// of some directory's `read_dir`, never the digest root itself (see the + /// comment on the directory-mode test above), so the differing + /// directory must be a child of the artifact root -- measured directly: + /// setting the xattr on `capped` itself here first left the two digests + /// equal. #[cfg(unix)] #[test] fn digest_artifact_differs_when_only_a_directorys_extended_attribute_differs() -> Result<()> { fn build(temp: &Path, name: &str) -> Result { let root = temp.join(name); fs::create_dir(&root)?; - fs::write(root.join("f"), b"same content\n")?; + let child = root.join("d"); + fs::create_dir(&child)?; + fs::write(child.join("f"), b"same content\n")?; Ok(root) } @@ -12975,7 +12984,7 @@ mod tests { let clean_digest = digest_artifact(&clean)?.0; let capped = build(temp.path(), "capped")?; - setxattr_or_bail(&capped, "user.idunn.soul_probe", b"present")?; + setxattr_or_bail(&capped.join("d"), "user.idunn.soul_probe", b"present")?; let capped_digest = digest_artifact(&capped)?.0; assert_ne!( @@ -12989,6 +12998,13 @@ mod tests { /// the underlying syscall `read_sorted_xattrs` uses (via `llistxattr`/ /// `lgetxattr`, which do not follow a final symlink), matching how a /// symlink's own xattrs, not its target's, are read. + /// + /// Uses the `security` namespace, not `user`: measured directly, + /// `lsetxattr(..., "user.idunn.soul_probe", ...)` on a symlink fails + /// `EPERM` even as root -- the kernel restricts the `user` namespace to + /// regular files and directories, symlinks included in the refusal + /// regardless of privilege. `security.capability` is the same + /// setuid-equivalent value the frozen-source xattr tests already use. #[cfg(unix)] #[test] fn digest_artifact_differs_when_only_a_symlinks_extended_attribute_differs() -> Result<()> { @@ -13008,11 +13024,13 @@ mod tests { let capped = build(temp.path(), "capped")?; let cpath = std::ffi::CString::new(capped.join("link").as_os_str().as_bytes())?; - let value = b"present"; + let value: [u8; 20] = [ + 0x01, 0x00, 0x00, 0x02, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; let written = unsafe { libc::lsetxattr( cpath.as_ptr(), - c"user.idunn.soul_probe".as_ptr(), + c"security.capability".as_ptr(), value.as_ptr() as *const libc::c_void, value.len(), 0,