From d68652a8f2ee5dc6e1c6d7f7552aeab2db07ebb1 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 11:43:11 +0000 Subject: [PATCH] fix(format): a replacement takes no inherited ACL the old file lacked --- crates/format/src/atomic.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/format/src/atomic.rs b/crates/format/src/atomic.rs index f79b0bd37..c208f1b52 100644 --- a/crates/format/src/atomic.rs +++ b/crates/format/src/atomic.rs @@ -288,7 +288,13 @@ fn keep_acl(target: &Path, tmp: &Path) -> bool { let mut buf = vec![0u8; 64 << 10]; match rustix::fs::getxattr(target, ACL, &mut buf[..]) { Ok(n) => buf.get(..n).is_some_and(|acl| rustix::fs::setxattr(tmp, ACL, acl, rustix::fs::XattrFlags::empty()).is_ok()), - Err(e) => e == Errno::NODATA || e == Errno::NOTSUP, + // None to keep: drop the one `tmp` may have inherited from the folder's default ACL, which + // the old file's group bits (its mask) would otherwise open to the people it names. + Err(e) if e == Errno::NODATA => match rustix::fs::removexattr(tmp, ACL) { + Ok(()) => true, + Err(e) => e == Errno::NODATA || e == Errno::NOTSUP, + }, + Err(e) => e == Errno::NOTSUP, } } @@ -313,6 +319,24 @@ mod tests { d } + /// A replaced file that had no access ACL gets none from the folder's default ACL, whose named + /// users the old file's group bits would let in. (Skipped where `setfacl` or ACLs are missing.) + #[cfg(target_os = "linux")] + #[test] + fn a_replacement_takes_no_inherited_acl_the_old_file_lacked() { + let d = dir("acl"); + let f = d.join("doc.vectorcraft"); + std::fs::write(&f, b"old").unwrap(); + let set = std::process::Command::new("setfacl").args(["-d", "-m", "u:nobody:rw"]).arg(&d).status(); + if !set.is_ok_and(|s| s.success()) { + return; + } + write_atomic(&f, b"new").unwrap(); + let mut buf = vec![0u8; 4096]; + assert_eq!(rustix::fs::getxattr(&f, "system.posix_acl_access", &mut buf[..]), Err(rustix::io::Errno::NODATA)); + let _ = std::fs::remove_dir_all(&d); + } + /// The other files in `d` besides `keep`. fn leftovers(d: &Path, keep: &str) -> Vec { std::fs::read_dir(d).unwrap().map(|e| e.unwrap().file_name().to_string_lossy().to_string()).filter(|n| n != keep).collect()