diff --git a/Cargo.lock b/Cargo.lock index c6b6480de1d..65bbb4cdc44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4593,9 +4593,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" dependencies = [ "filetime", "libc", diff --git a/Cargo.toml b/Cargo.toml index e408e8e39bd..4257b4e08a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ similar = "2.7.0" supports-hyperlinks = "3.2.0" supports-unicode = "3.0.0" snapbox = { version = "1.0.0", features = ["diff", "dir", "term-svg", "regex", "json"] } -tar = { version = "0.4.44", default-features = false } +tar = { version = "0.4.45", default-features = false } tempfile = "3.25.0" thiserror = "2.0.18" time = { version = "0.3.47", features = ["parsing", "formatting", "serde"] } diff --git a/crates/cargo-test-support/src/registry.rs b/crates/cargo-test-support/src/registry.rs index bc3a6301848..c9e48ebce2b 100644 --- a/crates/cargo-test-support/src/registry.rs +++ b/crates/cargo-test-support/src/registry.rs @@ -607,6 +607,7 @@ pub struct Dependency { enum EntryData { Regular(String), Symlink(PathBuf), + Directory, } /// A file to be created in a package. @@ -1330,6 +1331,17 @@ impl Package { self } + /// Adds an empty directory at the given path. + pub fn directory(&mut self, path: &str) -> &mut Package { + self.files.push(PackageFile { + path: path.to_string(), + contents: EntryData::Directory, + mode: DEFAULT_MODE, + extra: false, + }); + self + } + /// Adds an "extra" file that is not rooted within the package. /// /// Normal files are automatically placed within a directory named @@ -1742,6 +1754,10 @@ impl Package { t!(header.set_link_name(src)); "" // Symlink has no contents. } + EntryData::Directory => { + header.set_entry_type(tar::EntryType::Directory); + "" + } }; header.set_size(contents.len() as u64); t!(header.set_path(path)); diff --git a/tests/testsuite/registry.rs b/tests/testsuite/registry.rs index 3efecf1c896..705823ddacd 100644 --- a/tests/testsuite/registry.rs +++ b/tests/testsuite/registry.rs @@ -4695,3 +4695,77 @@ fn deterministic_mtime() { assert_deterministic_mtime(pkg_root.join("Cargo.toml")); assert_deterministic_mtime(pkg_root.join(".cargo_vcs_info.json")); } + +#[cargo_test] +fn symlink_and_directory() { + // Tests for symlink and directory entry in a tar file. The tar crate + // would incorrectly change the permissions of the symlink destination, + // which could be anywhere on the filesystem. + let victim = paths::root().join("victim"); + fs::create_dir(&victim).unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let perm = fs::Permissions::from_mode(0o700); + fs::set_permissions(&victim, perm).unwrap(); + assert_eq!( + victim.metadata().unwrap().permissions().mode() & 0o777, + 0o700 + ); + } + + Package::new("bar", "1.0.0") + .file("src/lib.rs", "") + .symlink("smuggled", victim.to_str().unwrap()) + .directory("smuggled") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + edition = "2015" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("fetch") + .with_status(101) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[ERROR] failed to download replaced source registry `crates-io` + +Caused by: + failed to unpack package `bar v1.0.0 (registry `dummy-registry`)` + +Caused by: + failed to unpack entry at `bar-1.0.0/smuggled` + +Caused by: + failed to unpack `[ROOT]/home/.cargo/registry/src/-[HASH]/bar-1.0.0/smuggled` + +Caused by: + [..] when creating dir [ROOT]/home/.cargo/registry/src/-[HASH]/bar-1.0.0/smuggled + +"#]]) + .run(); + + #[cfg(unix)] + { + // Permissions should not change. + use std::os::unix::fs::PermissionsExt; + assert_eq!( + victim.metadata().unwrap().permissions().mode() & 0o777, + 0o700 + ); + } +}