mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
fix(zip): validate CRC32 on small-entry extract fast path (#4510)
This commit is contained in:
@@ -604,6 +604,14 @@ fn write_small_zip_entry<R: Read>(reader: &mut R, output_path: &Path, size: u64)
|
|||||||
let size = usize::try_from(size).map_err(|_| io::Error::other("small zip entry size overflow"))?;
|
let size = usize::try_from(size).map_err(|_| io::Error::other("small zip entry size overflow"))?;
|
||||||
let mut buffer = [0_u8; SMALL_ZIP_EXTRACT_FAST_PATH_LIMIT as usize];
|
let mut buffer = [0_u8; SMALL_ZIP_EXTRACT_FAST_PATH_LIMIT as usize];
|
||||||
reader.read_exact(&mut buffer[..size])?;
|
reader.read_exact(&mut buffer[..size])?;
|
||||||
|
// `read_exact` stops as soon as the declared bytes are read and never performs the
|
||||||
|
// terminal zero-length read that the zip crate's `Crc32Reader` uses to validate the
|
||||||
|
// entry checksum. Force one extra read to EOF so a corrupted small entry is rejected
|
||||||
|
// here, matching the large-entry `io::copy` path which already reads through EOF.
|
||||||
|
let mut trailing = [0_u8; 1];
|
||||||
|
if reader.read(&mut trailing)? != 0 {
|
||||||
|
return Err(io::Error::other("small zip entry produced more data than its declared size").into());
|
||||||
|
}
|
||||||
std::fs::write(output_path, &buffer[..size])?;
|
std::fs::write(output_path, &buffer[..size])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1557,6 +1565,39 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_zip_rejects_small_entry_with_corrupted_crc() {
|
||||||
|
let temp = tempdir().expect("tempdir should be created");
|
||||||
|
let zip_path = temp.path().join("corrupt-crc.zip");
|
||||||
|
let extract_path = temp.path().join("extract");
|
||||||
|
|
||||||
|
// A stored entry well under the small-entry fast-path limit so extraction takes
|
||||||
|
// the in-memory buffer path rather than the streaming `io::copy` path.
|
||||||
|
let content = b"small-entry-crc-payload";
|
||||||
|
assert!((content.len() as u64) <= SMALL_ZIP_EXTRACT_FAST_PATH_LIMIT);
|
||||||
|
build_zip_file_with_entries(&zip_path, &[("small.txt", content)])
|
||||||
|
.await
|
||||||
|
.expect("zip fixture should be created");
|
||||||
|
|
||||||
|
// Corrupt the stored entry data so its bytes no longer match the recorded CRC32.
|
||||||
|
let mut raw = fs::read(&zip_path).await.expect("zip fixture should be readable");
|
||||||
|
let offset = raw
|
||||||
|
.windows(content.len())
|
||||||
|
.position(|window| window == content)
|
||||||
|
.expect("stored entry data should be present in the zip");
|
||||||
|
raw[offset] ^= 0xFF;
|
||||||
|
fs::write(&zip_path, &raw).await.expect("corrupted zip should be writable");
|
||||||
|
|
||||||
|
let err = extract_zip_simple(&zip_path, &extract_path)
|
||||||
|
.await
|
||||||
|
.expect_err("small entry with a corrupted CRC should be rejected");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(err, ZipError::Io(ref e) if e.kind() == std::io::ErrorKind::InvalidData),
|
||||||
|
"expected an InvalidData checksum error, got {err:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_extract_zip_allows_entry_count_exactly_at_limit() {
|
async fn test_extract_zip_allows_entry_count_exactly_at_limit() {
|
||||||
let temp = tempdir().expect("tempdir should be created");
|
let temp = tempdir().expect("tempdir should be created");
|
||||||
|
|||||||
Reference in New Issue
Block a user