From 430ee4432a97a0d953215fedc6a43a57f346fdcd Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 10 Jun 2026 12:50:54 -0600 Subject: [PATCH] fix: Strip special mode bits from cache restore --- .../src/cache_archive/restore.rs | 76 +++++++++++++++++++ .../src/cache_archive/restore_directory.rs | 2 +- .../src/cache_archive/restore_manifest.rs | 4 +- .../src/cache_archive/restore_regular.rs | 12 ++- 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/crates/turborepo-cache/src/cache_archive/restore.rs b/crates/turborepo-cache/src/cache_archive/restore.rs index 03d41f80ccab1..01b7dde4940ee 100644 --- a/crates/turborepo-cache/src/cache_archive/restore.rs +++ b/crates/turborepo-cache/src/cache_archive/restore.rs @@ -219,9 +219,18 @@ mod tests { path: &'static str, body: Vec, }, + FileWithMode { + path: &'static str, + body: Vec, + mode: u32, + }, Directory { path: &'static str, }, + DirectoryWithMode { + path: &'static str, + mode: u32, + }, Symlink { link_path: &'static str, link_target: &'static str, @@ -241,6 +250,13 @@ mod tests { header.set_mode(0o644); builder.append_data(&mut header, path, &body[..]).unwrap(); } + RawTarEntry::FileWithMode { path, body, mode } => { + let mut header = Header::new_gnu(); + header.set_size(body.len() as u64); + header.set_entry_type(tar::EntryType::Regular); + header.set_mode(*mode); + builder.append_data(&mut header, path, &body[..]).unwrap(); + } RawTarEntry::Directory { path } => { let mut header = Header::new_gnu(); header.set_entry_type(tar::EntryType::Directory); @@ -248,6 +264,13 @@ mod tests { header.set_mode(0o755); builder.append_data(&mut header, path, empty()).unwrap(); } + RawTarEntry::DirectoryWithMode { path, mode } => { + let mut header = Header::new_gnu(); + header.set_entry_type(tar::EntryType::Directory); + header.set_size(0); + header.set_mode(*mode); + builder.append_data(&mut header, path, empty()).unwrap(); + } RawTarEntry::Symlink { link_path, link_target, @@ -1911,6 +1934,59 @@ mod tests { Ok(()) } + #[test] + fn test_restore_regular_file_strips_special_mode_bits() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let output_dir = tempdir()?; + let output_dir_path = output_dir.path().to_string_lossy().into_owned(); + let anchor = AbsoluteSystemPath::new(&output_dir_path)?; + let tar = generate_raw_tar(&[RawTarEntry::FileWithMode { + path: "helper", + body: b"payload".to_vec(), + mode: 0o7755, + }]); + + let mut reader = CacheReader::from_reader(&tar[..], false)?; + reader.restore(anchor, None)?; + + let mode = anchor + .join_component("helper") + .symlink_metadata()? + .permissions() + .mode() + & 0o7777; + assert_eq!(mode & 0o7000, 0); + + Ok(()) + } + + #[test] + fn test_restore_directory_strips_special_mode_bits() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let output_dir = tempdir()?; + let output_dir_path = output_dir.path().to_string_lossy().into_owned(); + let anchor = AbsoluteSystemPath::new(&output_dir_path)?; + let tar = generate_raw_tar(&[RawTarEntry::DirectoryWithMode { + path: "dist", + mode: 0o7755, + }]); + + let mut reader = CacheReader::from_reader(&tar[..], false)?; + reader.restore(anchor, None)?; + + let mode = anchor + .join_component("dist") + .symlink_metadata()? + .permissions() + .mode() + & 0o7777; + assert_eq!(mode & 0o7000, 0); + + Ok(()) + } + #[test] fn test_many_concurrent_restores() -> Result<()> { let output_dir = tempdir()?; diff --git a/crates/turborepo-cache/src/cache_archive/restore_directory.rs b/crates/turborepo-cache/src/cache_archive/restore_directory.rs index b1e5727b33b1a..043545b53d468 100644 --- a/crates/turborepo-cache/src/cache_archive/restore_directory.rs +++ b/crates/turborepo-cache/src/cache_archive/restore_directory.rs @@ -161,7 +161,7 @@ fn create_dir_all_with_mode(path: &AbsoluteSystemPath, mode: u32) -> io::Result< std::fs::DirBuilder::new() .recursive(true) - .mode(mode & 0o7777) + .mode(mode & 0o777) .create(path.as_path()) } diff --git a/crates/turborepo-cache/src/cache_archive/restore_manifest.rs b/crates/turborepo-cache/src/cache_archive/restore_manifest.rs index 2f5d0ece3cf72..07bfbd7110b14 100644 --- a/crates/turborepo-cache/src/cache_archive/restore_manifest.rs +++ b/crates/turborepo-cache/src/cache_archive/restore_manifest.rs @@ -60,7 +60,7 @@ impl RestoreManifest { #[cfg(unix)] { use std::os::unix::fs::MetadataExt; - if (meta.mode() & 0o7777) != expected.mode { + if (meta.mode() & 0o777) != expected.mode { return false; } } @@ -91,7 +91,7 @@ impl RestoreManifest { #[cfg(unix)] let mode = { use std::os::unix::fs::MetadataExt; - meta.mode() & 0o7777 + meta.mode() & 0o777 }; #[cfg(not(unix))] let mode = 0o644; diff --git a/crates/turborepo-cache/src/cache_archive/restore_regular.rs b/crates/turborepo-cache/src/cache_archive/restore_regular.rs index d932bcecee99c..2f44d0cd1e458 100644 --- a/crates/turborepo-cache/src/cache_archive/restore_regular.rs +++ b/crates/turborepo-cache/src/cache_archive/restore_regular.rs @@ -42,12 +42,22 @@ pub fn restore_regular( #[cfg(not(unix))] let mode = 0; - let mut file = open_for_restore(&resolved_path, mode)?; + let mut file = open_for_restore(&resolved_path, sanitized_mode(mode))?; io::copy(entry, &mut file)?; Ok((processed_name, false)) } +#[cfg(unix)] +fn sanitized_mode(mode: u32) -> u32 { + mode & 0o777 +} + +#[cfg(not(unix))] +fn sanitized_mode(mode: u32) -> u32 { + mode +} + fn open_for_restore( path: &AbsoluteSystemPath, #[cfg_attr(not(unix), allow(unused_variables))] mode: u32,