Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/compiler/fingerprint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1600,10 +1600,27 @@ fn calculate_normal(
} else {
let dep_info = dep_info_loc(build_runner, unit);
let dep_info = dep_info.strip_prefix(&build_root).unwrap().to_path_buf();
vec![LocalFingerprint::CheckDepInfo {
let mut local = vec![LocalFingerprint::CheckDepInfo {
dep_info,
checksum: build_runner.bcx.gctx.cli_unstable().checksum_freshness,
}]
}];
let source_id = unit.pkg.package_id().source_id();
let sources = build_runner.bcx.packages.sources();
let source = sources
.get(source_id)
.ok_or_else(|| internal("missing package source"))?;
// Git and registry files are omitted from dep-info. A replacement can
// keep the same source path while its checksumless origin is updated.
if source.is_replaced() && !source.supports_checksums() {
let fingerprint = source.fingerprint(&unit.pkg).with_context(|| {
format!(
"failed to determine replacement source fingerprint for {}",
unit.pkg
)
})?;
local.push(LocalFingerprint::Precalculated(fingerprint));
}
local
};

// Figure out what the outputs of our unit is, and we'll be storing them
Expand Down
10 changes: 8 additions & 2 deletions src/sources/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ impl<'gctx> SourceConfigMap<'gctx> {

let new_src = self.load_overlaid(new_id)?;
let old_src = id.load(self.gctx)?;
if !new_src.supports_checksums() && old_src.supports_checksums() {
let old_src_supports_checksums = old_src.supports_checksums();
if !new_src.supports_checksums() && old_src_supports_checksums {
bail!(
"\
cannot replace `{orig}` with `{name}`, the source `{orig}` supports \
Expand All @@ -219,7 +220,12 @@ restore the source replacement configuration to continue the build
);
}

Ok(Box::new(ReplacedSource::new(id, new_id, new_src)))
Ok(Box::new(ReplacedSource::new(
id,
new_id,
old_src_supports_checksums,
new_src,
)))
}

/// Gets the [`Source`] for a given [`SourceId`] without performing any source replacement.
Expand Down
23 changes: 18 additions & 5 deletions src/sources/registry/local.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Access to a registry on the local filesystem. See [`LocalRegistry`] for more.

use crate::sources::registry::{LoadResponse, MaybeLock, RegistryConfig, RegistryData};
use crate::sources::registry::{
LoadResponse, LockMetadata, MaybeLock, PACKAGE_SOURCE_LOCK, RegistryConfig, RegistryData,
};
use crate::util::errors::CargoResult;
use crate::util::{Filesystem, GlobalContext};
use crate::workspace::PackageId;
use cargo_util::{Sha256, paths};
use std::cell::Cell;
use std::fs::File;
use std::fs::{self, File};
use std::io::SeekFrom;
use std::io::{self, prelude::*};
use std::path::Path;
Expand Down Expand Up @@ -178,10 +180,21 @@ impl<'gctx> RegistryData for LocalRegistry<'gctx> {
let path = self.root.join(&pkg.tarball_name()).into_path_unlocked();
let mut crate_file = paths::open(&path)?;

// If we've already got an unpacked version of this crate, then skip the
// checksum below as it is in theory already verified.
// If this exact archive has already been unpacked, then its checksum was
// verified before the source lock was written.
let dst = path.file_stem().unwrap();
if self.src_path.join(dst).into_path_unlocked().exists() {
let source_lock = self
.src_path
.join(dst)
.into_path_unlocked()
.join(PACKAGE_SOURCE_LOCK);
let checksum_is_verified = fs::read_to_string(source_lock)
.ok()
.and_then(|contents| serde_json::from_str::<LockMetadata>(&contents).ok())
.map_or(false, |metadata| {
metadata.is_valid_for_checksum(checksum, true)
});
if checksum_is_verified {
return Ok(MaybeLock::Ready(crate_file));
}

Expand Down
59 changes: 43 additions & 16 deletions src/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ use anyhow::Context as _;
use cargo_util::paths;
use cargo_util_terminal::report::Level;
use flate2::read::GzDecoder;
use futures::FutureExt as _;
use serde::Deserialize;
use serde::Serialize;
use tar::{Archive, EntryType};
Expand All @@ -211,7 +210,7 @@ use crate::util::{CargoResult, Filesystem, GlobalContext, LimitErrorReader, rest
use crate::util::{VersionExt, hex};
use crate::workspace::dependency::Dependency;
use crate::workspace::global_cache_tracker;
use crate::workspace::{Package, PackageId, SourceId};
use crate::workspace::{Package, PackageId, SourceId, SourceKind};

pub use cargo_util_schemas::index::RegistryConfig;

Expand All @@ -233,6 +232,19 @@ pub const CRATES_IO_DOMAIN: &str = "crates.io";
struct LockMetadata {
/// The version of `.cargo-ok` file
v: u32,
/// The checksum of the archive that was unpacked.
#[serde(default, skip_serializing_if = "Option::is_none")]
checksum: Option<String>,
}

impl LockMetadata {
fn is_valid_for_checksum(&self, checksum: &str, checksum_required: bool) -> bool {
self.v == 1
&& match self.checksum.as_deref() {
Some(unpacked_checksum) => unpacked_checksum == checksum,
None => !checksum_required,
}
}
}

/// A [`Source`] implementation for a local or a remote registry.
Expand Down Expand Up @@ -551,17 +563,23 @@ impl<'gctx> RegistrySource<'gctx> {
/// `.cargo-ok` file is found.
///
/// [CVE-2022-36113]: https://blog.rust-lang.org/2022/09/14/cargo-cves.html#arbitrary-file-corruption-cve-2022-36113
fn unpack_package(&self, pkg: PackageId, tarball: &File) -> CargoResult<PathBuf> {
fn unpack_package(
&self,
pkg: PackageId,
tarball: &File,
checksum: &str,
) -> CargoResult<PathBuf> {
let package_dir = format!("{}-{}", pkg.name(), pkg.version());
let dst = self.src_path.join(&package_dir);
let path = dst.join(PACKAGE_SOURCE_LOCK);
let path = self
.gctx
.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
let unpack_dir = path.parent().unwrap();
let checksum_required = matches!(self.source_id.kind(), SourceKind::LocalRegistry);
match fs::read_to_string(path) {
Ok(ok) => match serde_json::from_str::<LockMetadata>(&ok) {
Ok(lock_meta) if lock_meta.v == 1 => {
Ok(lock_meta) if lock_meta.is_valid_for_checksum(checksum, checksum_required) => {
self.gctx
.deferred_global_last_use()?
.mark_registry_src_used(global_cache_tracker::RegistrySrc {
Expand All @@ -571,6 +589,10 @@ impl<'gctx> RegistrySource<'gctx> {
});
return Ok(unpack_dir.to_path_buf());
}
Ok(lock_meta) if lock_meta.v == 1 => {
tracing::debug!("archive checksum metadata missing or changed, clearing cache");
paths::remove_dir_all(dst.as_path_unlocked())?;
}
_ => {
if ok == "ok" {
tracing::debug!("old `ok` content found, clearing cache");
Expand Down Expand Up @@ -598,7 +620,10 @@ impl<'gctx> RegistrySource<'gctx> {
.open(&path)
.with_context(|| format!("failed to open `{}`", path.display()))?;

let lock_meta = LockMetadata { v: 1 };
let lock_meta = LockMetadata {
v: 1,
checksum: checksum_required.then(|| checksum.to_owned()),
};
write!(ok, "{}", serde_json::to_string(&lock_meta).unwrap())?;

self.gctx
Expand Down Expand Up @@ -645,9 +670,14 @@ impl<'gctx> RegistrySource<'gctx> {
/// should only be called after doing integrity check. That is to say,
/// you need to call either [`RegistryData::download`] or
/// [`RegistryData::finish_download`] before calling this method.
async fn get_pkg(&self, package: PackageId, path: &File) -> CargoResult<Package> {
async fn get_pkg(
&self,
package: PackageId,
path: &File,
checksum: &str,
) -> CargoResult<Package> {
let path = self
.unpack_package(package, path)
.unpack_package(package, path, checksum)
.with_context(|| format!("failed to unpack package `{}`", package))?;
let src = PathSource::new(&path, self.source_id, self.gctx);
src.load()?;
Expand All @@ -658,15 +688,9 @@ impl<'gctx> RegistrySource<'gctx> {

// After we've loaded the package configure its summary's `checksum`
// field with the checksum we know for this `PackageId`.
let cksum = self
.index
.hash(package, &*self.ops)
.now_or_never()
.expect("a downloaded dep now pending!?")
.expect("summary not found");
pkg.manifest_mut()
.summary_mut()
.set_checksum(cksum.to_string());
.set_checksum(checksum.to_owned());

Ok(pkg)
}
Expand Down Expand Up @@ -862,7 +886,10 @@ impl<'gctx> Source for RegistrySource<'gctx> {
async fn download(&self, package: PackageId) -> CargoResult<MaybePackage> {
let hash = self.index.hash(package, &*self.ops).await?;
match self.ops.download(package, &hash).await? {
MaybeLock::Ready(file) => self.get_pkg(package, &file).await.map(MaybePackage::Ready),
MaybeLock::Ready(file) => self
.get_pkg(package, &file, &hash)
.await
.map(MaybePackage::Ready),
MaybeLock::Download {
url,
descriptor,
Expand All @@ -878,7 +905,7 @@ impl<'gctx> Source for RegistrySource<'gctx> {
async fn finish_download(&self, package: PackageId, data: Vec<u8>) -> CargoResult<Package> {
let hash = self.index.hash(package, &*self.ops).await?;
let file = self.ops.finish_download(package, &hash, &data).await?;
self.get_pkg(package, &file).await
self.get_pkg(package, &file, &hash).await
}

fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
Expand Down
23 changes: 20 additions & 3 deletions src/sources/replaced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct ReplacedSource<'gctx> {
to_replace: SourceId,
/// The identifier of the new replacement source.
replace_with: SourceId,
/// Whether the original source supports checksums.
to_replace_supports_checksums: bool,
inner: Box<dyn Source + 'gctx>,
}

Expand All @@ -26,11 +28,13 @@ impl<'gctx> ReplacedSource<'gctx> {
pub fn new(
to_replace: SourceId,
replace_with: SourceId,
to_replace_supports_checksums: bool,
src: Box<dyn Source + 'gctx>,
) -> ReplacedSource<'gctx> {
ReplacedSource {
to_replace,
replace_with,
to_replace_supports_checksums,
inner: src,
}
}
Expand All @@ -55,7 +59,7 @@ impl<'gctx> Source for ReplacedSource<'gctx> {
}

fn supports_checksums(&self) -> bool {
self.inner.supports_checksums()
self.to_replace_supports_checksums
}

fn requires_precise(&self) -> bool {
Expand All @@ -69,11 +73,20 @@ impl<'gctx> Source for ReplacedSource<'gctx> {
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()> {
let (replace_with, to_replace) = (self.replace_with, self.to_replace);
let supports_checksums = self.to_replace_supports_checksums;
let dep = dep.clone().map_source(to_replace, replace_with);

self.inner
.query(&dep, kind, &mut |summary| {
f(summary.map_summary(|s| s.map_source(replace_with, to_replace)))
f(summary.map_summary(|s| {
let mut s = s.map_source(replace_with, to_replace);
// The lockfile describes the original source, not the
// stronger guarantees offered by its replacement.
if !supports_checksums {
s.clear_checksum();
}
s
}))
})
.await
.map_err(|e| {
Expand Down Expand Up @@ -132,7 +145,11 @@ impl<'gctx> Source for ReplacedSource<'gctx> {
}

fn fingerprint(&self, id: &Package) -> CargoResult<String> {
self.inner.fingerprint(id)
let replacement = self.inner.fingerprint(id)?;
match id.package_id().source_id().precise_git_fragment() {
Some(precise) => Ok(format!("{precise} {replacement}")),
None => Ok(replacement),
}
}

fn verify(&self, id: PackageId) -> CargoResult<()> {
Expand Down
4 changes: 4 additions & 0 deletions src/workspace/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ impl Summary {
Arc::make_mut(&mut self.inner).checksum = Some(cksum);
}

pub fn clear_checksum(&mut self) {
Arc::make_mut(&mut self.inner).checksum = None;
}

pub fn set_pubtime(&mut self, pubtime: jiff::Timestamp) {
Arc::make_mut(&mut self.inner).pubtime = Some(pubtime);
}
Expand Down
Loading