Skip to content

Commit 2dd0de2

Browse files
authored
fix: update mtime for generated files after unpacking (#16250)
### What does this PR try to resolve? This is the unpacking half of #16237 Updating mtime for all files might not be worthy as crate published after 1.54 should all have the deterministic mtime for non-generated files, except those did manual upload. This patch is aimed at fixing the "regression" of vendor direct extraction, rather than a complete fix of the non-deterministic mtime. Also there are workarounds, so the workflow is not completely blocked. Fixes #16237 ### How to test and review this PR? Since Cargo had a couple CVEs around tar and unpack, I separate the mtime update logic from the main unpack logic, so that each function's intent is clearer. Hope it won't introduce new vulnerability
2 parents bffbb60 + 2f6852f commit 2dd0de2

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

crates/cargo-test-support/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,3 +1735,21 @@ pub fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u
17351735
}
17361736
})
17371737
}
1738+
1739+
#[track_caller]
1740+
pub fn assert_deterministic_mtime(path: impl AsRef<Path>) {
1741+
// Hardcoded value be removed once alexcrichton/tar-rs#420 is merged and released.
1742+
// See also rust-lang/cargo#16237
1743+
const DETERMINISTIC_TIMESTAMP: u64 = 1153704088;
1744+
1745+
let path = path.as_ref();
1746+
let mtime = path.metadata().unwrap().modified().unwrap();
1747+
let timestamp = mtime
1748+
.duration_since(std::time::UNIX_EPOCH)
1749+
.unwrap()
1750+
.as_secs();
1751+
assert_eq!(
1752+
timestamp, DETERMINISTIC_TIMESTAMP,
1753+
"expected deterministic mtime for {path:?}, got {timestamp}"
1754+
);
1755+
}

src/cargo/sources/registry/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ impl<'gctx> RegistrySource<'gctx> {
635635
dst.create_dir()?;
636636

637637
let bytes_written = unpack(self.gctx, tarball, unpack_dir, &|_| true)?;
638+
update_mtime_for_generated_files(unpack_dir);
638639

639640
// Now that we've finished unpacking, create and write to the lock file to indicate that
640641
// unpacking was successful.
@@ -679,6 +680,7 @@ impl<'gctx> RegistrySource<'gctx> {
679680
let tarball =
680681
File::open(path).with_context(|| format!("failed to open {}", path.display()))?;
681682
unpack(self.gctx, &tarball, &dst, include)?;
683+
update_mtime_for_generated_files(&dst);
682684
Ok(dst)
683685
}
684686

@@ -1104,3 +1106,25 @@ fn unpack(
11041106

11051107
Ok(bytes_written)
11061108
}
1109+
1110+
/// Workaround for rust-lang/cargo#16237
1111+
///
1112+
/// Generated files should have the same deterministic mtime as other files.
1113+
/// However, since we forgot to set mtime for those files when uploading, they
1114+
/// always have older mtime (1973-11-29) that prevents zip from packing (requiring >1980)
1115+
///
1116+
/// This workaround updates mtime after we unpack the tarball at the destination.
1117+
fn update_mtime_for_generated_files(pkg_root: &Path) {
1118+
const GENERATED_FILES: &[&str] = &["Cargo.lock", "Cargo.toml", ".cargo_vcs_info.json"];
1119+
// Hardcoded value be removed once alexcrichton/tar-rs#420 is merged and released.
1120+
// See also rust-lang/cargo#16237
1121+
const DETERMINISTIC_TIMESTAMP: i64 = 1153704088;
1122+
1123+
for file in GENERATED_FILES {
1124+
let path = pkg_root.join(file);
1125+
let mtime = filetime::FileTime::from_unix_time(DETERMINISTIC_TIMESTAMP, 0);
1126+
if let Err(e) = filetime::set_file_mtime(&path, mtime) {
1127+
tracing::trace!("failed to set deterministic mtime for {path:?}: {e}");
1128+
}
1129+
}
1130+
}

tests/testsuite/registry.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::sync::Mutex;
99
use crate::prelude::*;
1010
use crate::utils::cargo_process;
1111
use cargo::core::SourceId;
12+
use cargo_test_support::assert_deterministic_mtime;
1213
use cargo_test_support::paths;
1314
use cargo_test_support::registry::{
1415
self, Dependency, Package, RegistryBuilder, Response, TestRegistry, registry_path,
@@ -4653,3 +4654,44 @@ required by package `foo v0.0.1 ([ROOT]/foo)`
46534654
"#]])
46544655
.run();
46554656
}
4657+
4658+
#[cargo_test]
4659+
fn deterministic_mtime() {
4660+
let registry = registry::init();
4661+
Package::new("foo", "0.1.0")
4662+
// content doesn't matter, we just want to check mtime
4663+
.file("Cargo.lock", "")
4664+
.file(".cargo_vcs_info.json", "")
4665+
.file("src/lib.rs", "")
4666+
.publish();
4667+
4668+
let p = project()
4669+
.file(
4670+
"Cargo.toml",
4671+
r#"
4672+
[package]
4673+
name = "a"
4674+
edition = "2015"
4675+
4676+
[dependencies]
4677+
foo = '0.1.0'
4678+
"#,
4679+
)
4680+
.file("src/lib.rs", "")
4681+
.build();
4682+
4683+
p.cargo("fetch").run();
4684+
4685+
let id = SourceId::for_registry(registry.index_url()).unwrap();
4686+
let hash = cargo::util::hex::short_hash(&id);
4687+
let pkg_root = paths::cargo_home()
4688+
.join("registry")
4689+
.join("src")
4690+
.join(format!("-{hash}"))
4691+
.join("foo-0.1.0");
4692+
4693+
// Generated files should have deterministic mtime after unpacking.
4694+
assert_deterministic_mtime(pkg_root.join("Cargo.lock"));
4695+
assert_deterministic_mtime(pkg_root.join("Cargo.toml"));
4696+
assert_deterministic_mtime(pkg_root.join(".cargo_vcs_info.json"));
4697+
}

tests/testsuite/vendor.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use std::fs;
88

99
use crate::prelude::*;
10+
use cargo_test_support::assert_deterministic_mtime;
1011
use cargo_test_support::compare::assert_e2e;
1112
use cargo_test_support::git;
1213
use cargo_test_support::registry::{self, Package, RegistryBuilder};
@@ -2119,3 +2120,35 @@ fn vendor_rename_fallback() {
21192120

21202121
assert!(p.root().join("vendor/log/Cargo.toml").exists());
21212122
}
2123+
2124+
#[cargo_test]
2125+
fn deterministic_mtime() {
2126+
Package::new("foo", "0.1.0")
2127+
// content doesn't matter, we just want to check mtime
2128+
.file("Cargo.lock", "")
2129+
.file(".cargo_vcs_info.json", "")
2130+
.file("src/lib.rs", "")
2131+
.publish();
2132+
2133+
let p = project()
2134+
.file(
2135+
"Cargo.toml",
2136+
r#"
2137+
[package]
2138+
name = "a"
2139+
edition = "2015"
2140+
2141+
[dependencies]
2142+
foo = '0.1.0'
2143+
"#,
2144+
)
2145+
.file("src/lib.rs", "")
2146+
.build();
2147+
2148+
p.cargo("vendor --respect-source-config").run();
2149+
2150+
// Generated files should have deterministic mtime after unpacking.
2151+
assert_deterministic_mtime(p.root().join("vendor/foo/Cargo.lock"));
2152+
assert_deterministic_mtime(p.root().join("vendor/foo/Cargo.toml"));
2153+
assert_deterministic_mtime(p.root().join("vendor/foo/.cargo_vcs_info.json"));
2154+
}

0 commit comments

Comments
 (0)