Skip to content

Commit

Permalink
[nextest-runner] add integrity checks for downloaded archives
Browse files Browse the repository at this point in the history
  • Loading branch information
sunshowers committed Nov 24, 2024
1 parent 8e3758d commit 4c90f20
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 3 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ future-queue = "0.3.0"
futures = "0.3.31"
globset = "0.4.15"
guppy = "0.17.8"
hex = "0.4.3"
home = "0.5.9"
http = "1.1.0"
humantime-serde = "1.1.1"
Expand Down Expand Up @@ -100,6 +101,7 @@ serde = { version = "1.0.215", features = ["derive"] }
serde_ignored = "0.1.10"
serde_json = "1.0.133"
serde_path_to_error = "0.1.16"
sha2 = "0.10.8"
shell-words = "1.1.0"
smallvec = "1.13.2"
smol_str = { version = "0.3.1", features = ["serde"] }
Expand Down
6 changes: 4 additions & 2 deletions nextest-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ zstd.workspace = true
###
### Update-related features, optionally enabled
###
hex = { workspace = true, optional = true }
http = { workspace = true, optional = true }
mukti-metadata = { workspace = true, optional = true }
sha2 = { workspace = true, optional = true }
# TODO: remove dependency on self_update, build our own thing on top of mukti
self_update = { workspace = true, optional = true }

Expand Down Expand Up @@ -146,5 +148,5 @@ name = "passthrough"
path = "test-helpers/passthrough.rs"

[features]
self-update = ["self_update", "http", "mukti-metadata"]
experimental-tokio-console = ["console-subscriber", "tracing-subscriber", "tokio/tracing"]
self-update = ["dep:hex", "dep:self_update", "dep:http", "dep:mukti-metadata", "dep:sha2"]
experimental-tokio-console = ["dep:console-subscriber", "dep:tracing-subscriber", "tokio/tracing"]
21 changes: 21 additions & 0 deletions nextest-runner/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,27 @@ mod self_update_errors {
error: std::io::Error,
},

/// An error occurred while reading from a temporary archive.
#[error("error reading from temporary archive at `{archive_path}`")]
TempArchiveRead {
/// The archive path for which there was an error.
archive_path: Utf8PathBuf,

/// The error that occurred.
#[source]
error: std::io::Error,
},

/// A checksum mismatch occurred. (Currently, the SHA-256 checksum is checked.)
#[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
ChecksumMismatch {
/// The expected checksum.
expected: String,

/// The actual checksum.
actual: String,
},

/// An error occurred while renaming a file.
#[error("error renaming `{source}` to `{dest}`")]
FsRename {
Expand Down
37 changes: 36 additions & 1 deletion nextest-runner/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
use crate::errors::{UpdateError, UpdateVersionParseError};
use camino::{Utf8Path, Utf8PathBuf};
use mukti_metadata::{MuktiProject, MuktiReleasesJson, ReleaseLocation, ReleaseStatus};
use mukti_metadata::{
DigestAlgorithm, MuktiProject, MuktiReleasesJson, ReleaseLocation, ReleaseStatus,
};
use self_update::{ArchiveKind, Compression, Download, Extract};
use semver::{Version, VersionReq};
use serde::Deserialize;
use sha2::{Digest, Sha256};
use std::{
fs,
io::{self, BufWriter},
Expand Down Expand Up @@ -374,6 +377,38 @@ impl<'a> MuktiUpdateContext<'a> {
})?;
std::mem::drop(tmp_archive);

// Verify the checksum of the downloaded file if available.
let mut hasher = Sha256::default();
// Just read the file into memory for now -- it would be nice to have an
// incremental hasher that updates the hash as it's being downloaded,
// but it's not critical since our archives are quite small.
let mut tmp_archive =
fs::File::open(&tmp_archive_path).map_err(|error| UpdateError::TempArchiveRead {
archive_path: tmp_archive_path.clone(),
error,
})?;
io::copy(&mut tmp_archive, &mut hasher).map_err(|error| UpdateError::TempArchiveRead {
archive_path: tmp_archive_path.clone(),
error,
})?;
let hash = hasher.finalize();
let hash_str = hex::encode(hash);

match self.location.checksums.get(&DigestAlgorithm::SHA256) {
Some(checksum) => {
if checksum.0 != hash_str {
return Err(UpdateError::ChecksumMismatch {
expected: checksum.0.clone(),
actual: hash_str,
});
}
debug!(target: "nextest-runner::update", "SHA-256 checksum verified: {hash_str}");
}
None => {
warn!(target: "nextest-runner::update", "unable to verify SHA-256 checksum of downloaded archive ({hash_str})");
}
}

// Now extract data from this archive.
Extract::from_source(tmp_archive_path.as_std_path())
.archive(ArchiveKind::Tar(Some(Compression::Gz)))
Expand Down

0 comments on commit 4c90f20

Please sign in to comment.