diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d6941b56..b4ebf866 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -367,7 +367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -917,7 +917,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -958,6 +958,7 @@ dependencies = [ "s390_pv_core", "serde", "serde_test", + "testing_logger", "thiserror", "zerocopy", ] @@ -1118,7 +1119,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1128,7 +1129,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "testing_logger" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d92b727cb45d33ae956f7f46b966b25f1bc712092aeef9dba5ac798fc89f720" +dependencies = [ + "log", ] [[package]] diff --git a/rust/pv/Cargo.toml b/rust/pv/Cargo.toml index 3f1a003b..fc7e494a 100644 --- a/rust/pv/Cargo.toml +++ b/rust/pv/Cargo.toml @@ -29,3 +29,4 @@ pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.12.0" } [dev-dependencies] serde_test = "1.0.177" +testing_logger = "0.1.1" diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs index 98b2d833..df75c591 100644 --- a/rust/pv/src/error.rs +++ b/rust/pv/src/error.rs @@ -179,6 +179,8 @@ pub enum HkdVerifyErrorType { IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32), #[error("Too many redirections during CRL download")] TooManyRedirectionsCrlDownload, + #[error("CRL download exceeds maximum file size of {} MiB", .0 / (1024 * 1024))] + CrlDownloadTooLarge(u64), #[error("CRL download failed")] CrlDownloadFailed, } diff --git a/rust/pv/src/verify/helper.rs b/rust/pv/src/verify/helper.rs index 0d37bb2f..52640e5a 100644 --- a/rust/pv/src/verify/helper.rs +++ b/rust/pv/src/verify/helper.rs @@ -354,6 +354,7 @@ trait HttpClient { fn follow_location(&mut self, enable: bool) -> Result<()>; fn redirect_url(&self) -> Result>; fn get_ref(&self) -> &[u8]; + fn max_filesize(&mut self, size: u64) -> Result<()>; } /// Production HTTP client implementation @@ -392,8 +393,13 @@ mod prod_client { } fn perform(&mut self) -> Result<()> { - self.handle.perform()?; - Ok(()) + match self.handle.perform() { + Ok(()) => Ok(()), + Err(err) if err.is_filesize_exceeded() => { + Err(Error::HkdVerify(CrlDownloadTooLarge(CRL_FILE_SIZE_MAX))) + } + Err(err) => Err(err.into()), + } } fn get(&mut self, enable: bool) -> Result<(), Error> { @@ -424,6 +430,11 @@ mod prod_client { fn redirect_url(&self) -> Result> { Ok(self.handle.redirect_url()?.map(|s| s.to_string())) } + + fn max_filesize(&mut self, size: u64) -> Result<()> { + self.handle.max_filesize(size)?; + Ok(()) + } } /// Searches for CRL Distribution points and downloads the CRL. Stops after the first successful @@ -448,6 +459,8 @@ mod prod_client { } } +const CRL_FILE_SIZE_MAX: u64 = 10 * 1024 * 1024; // 10 MiB + /// Searches for CRL Distribution points and downloads the CRL. Stops after the first successful /// download. /// @@ -477,6 +490,7 @@ fn download_first_crl_from_x509_impl( // redirections. client.follow_location(false)?; client.timeout(CRL_TIMEOUT_MAX)?; + client.max_filesize(CRL_FILE_SIZE_MAX)?; client.useragent("s390-tools-pv-crl")?; for i in 0..CRL_MAX_REDIRECTIONS { @@ -649,6 +663,7 @@ mod tests { url: String, responses: HashMap, perform_count: usize, + max_filesize: Option, } impl MockHttpClient { @@ -657,6 +672,7 @@ mod tests { url: String::new(), responses, perform_count: 0, + max_filesize: None, } } } @@ -692,6 +708,12 @@ mod tests { fn perform(&mut self) -> Result<()> { self.perform_count += 1; if let Some(response) = self.responses.get(&self.url) { + if self + .max_filesize + .is_some_and(|max_filesize| response.data.len() as u64 > max_filesize) + { + return Err(Error::HkdVerify(CrlDownloadTooLarge(CRL_FILE_SIZE_MAX))); + } if response.should_fail { bail_hkd_verify!(CrlDownloadFailed); } @@ -716,6 +738,11 @@ mod tests { &[] } } + + fn max_filesize(&mut self, size: u64) -> Result<()> { + self.max_filesize = Some(size); + Ok(()) + } } /// Helper to create mock response with data @@ -1185,4 +1212,67 @@ mod tests { )); } } + + mod max_filesize { + use super::*; + use crate::test_utils::get_cert_asset_path; + + #[test] + fn download_just_over_max_filesize() { + testing_logger::setup(); + + let cert = create_cert_with_crl_dps(&["http://example.com/test.crl"]); + + // Create data just over the 10 MiB limit + let over_limit_data = vec![0u8; 10 * 1024 * 1024 + 1]; + + let mut responses = HashMap::new(); + responses.insert( + "http://example.com/test.crl".to_string(), + mock_response(over_limit_data), + ); + + let result = download_with_mock(&cert, responses); + // When file size exceeds limit, the download fails and function tries next URL + // Since there's only one URL, it returns Ok(None) after exhausting all options + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + + // Verify that error was logged about file size exceeding limit + testing_logger::validate(|captured_logs| { + assert!( + captured_logs + .iter() + .any(|log| { log.body.contains("CRL download exceeds maximum file size") }), + "Expected log message about CRL file size exceeding maximum limit" + ); + }); + } + + #[test] + fn download_fallback_after_size_exceeded() { + let cert = create_cert_with_crl_dps(&[ + "http://primary.example.com/test.crl", + "http://backup.example.com/test.crl", + ]); + let crl_data = std::fs::read(get_cert_asset_path("inter_ca.crl")).unwrap(); + + let mut responses = HashMap::new(); + // First URL has file that's too large + responses.insert( + "http://primary.example.com/test.crl".to_string(), + mock_response(vec![0u8; (CRL_FILE_SIZE_MAX + 1).try_into().unwrap()]), + ); + // Second URL has valid CRL + responses.insert( + "http://backup.example.com/test.crl".to_string(), + mock_response(crl_data), + ); + + let result = download_with_mock(&cert, responses); + // Should fall back to second URL and succeed + assert!(result.is_ok()); + assert!(result.unwrap().is_some()); + } + } }