Skip to content

Commit 0460192

Browse files
committed
Auto merge of #11337 - weihanglo:compression-ratio, r=ehuss
Aware of compression ratio for unpack size limit
2 parents 861110c + de7cd31 commit 0460192

File tree

2 files changed

+57
-10
lines changed

2 files changed

+57
-10
lines changed

src/cargo/sources/registry/mod.rs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ const PREFIX_TEMPLATE: &str = "{prefix}";
197197
const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}";
198198
const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}";
199199
const MAX_UNPACK_SIZE: u64 = 512 * 1024 * 1024;
200+
const MAX_COMPRESSION_RATIO: usize = 20; // 20:1
200201

201202
/// A "source" for a local (see `local::LocalRegistry`) or remote (see
202203
/// `remote::RemoteRegistry`) registry.
@@ -627,9 +628,12 @@ impl<'cfg> RegistrySource<'cfg> {
627628
return Ok(unpack_dir.to_path_buf());
628629
}
629630
}
630-
let gz = GzDecoder::new(tarball);
631-
let gz = LimitErrorReader::new(gz, max_unpack_size());
632-
let mut tar = Archive::new(gz);
631+
let mut tar = {
632+
let size_limit = max_unpack_size(tarball.metadata()?.len());
633+
let gz = GzDecoder::new(tarball);
634+
let gz = LimitErrorReader::new(gz, size_limit);
635+
Archive::new(gz)
636+
};
633637
let prefix = unpack_dir.file_name().unwrap();
634638
let parent = unpack_dir.parent().unwrap();
635639
for entry in tar.entries()? {
@@ -851,18 +855,47 @@ impl<'cfg> Source for RegistrySource<'cfg> {
851855
}
852856
}
853857

854-
/// For integration test only.
855-
#[inline]
856-
fn max_unpack_size() -> u64 {
857-
const VAR: &str = "__CARGO_TEST_MAX_UNPACK_SIZE";
858-
if cfg!(debug_assertions) && std::env::var(VAR).is_ok() {
859-
std::env::var(VAR)
858+
/// Get the maximum upack size that Cargo permits
859+
/// based on a given `size of your compressed file.
860+
///
861+
/// Returns the larger one between `size * max compression ratio`
862+
/// and a fixed max unpacked size.
863+
///
864+
/// In reality, the compression ratio usually falls in the range of 2:1 to 10:1.
865+
/// We choose 20:1 to cover almost all possible cases hopefully.
866+
/// Any ratio higher than this is considered as a zip bomb.
867+
///
868+
/// In the future we might want to introduce a configurable size.
869+
///
870+
/// Some of the real world data from common compression algorithms:
871+
///
872+
/// * <https://www.zlib.net/zlib_tech.html>
873+
/// * <https://cran.r-project.org/web/packages/brotli/vignettes/brotli-2015-09-22.pdf>
874+
/// * <https://blog.cloudflare.com/results-experimenting-brotli/>
875+
/// * <https://tukaani.org/lzma/benchmarks.html>
876+
fn max_unpack_size(size: u64) -> u64 {
877+
const SIZE_VAR: &str = "__CARGO_TEST_MAX_UNPACK_SIZE";
878+
const RATIO_VAR: &str = "__CARGO_TEST_MAX_UNPACK_RATIO";
879+
let max_unpack_size = if cfg!(debug_assertions) && std::env::var(SIZE_VAR).is_ok() {
880+
// For integration test only.
881+
std::env::var(SIZE_VAR)
860882
.unwrap()
861883
.parse()
862884
.expect("a max unpack size in bytes")
863885
} else {
864886
MAX_UNPACK_SIZE
865-
}
887+
};
888+
let max_compression_ratio = if cfg!(debug_assertions) && std::env::var(RATIO_VAR).is_ok() {
889+
// For integration test only.
890+
std::env::var(RATIO_VAR)
891+
.unwrap()
892+
.parse()
893+
.expect("a max compresssion ratio in bytes")
894+
} else {
895+
MAX_COMPRESSION_RATIO
896+
};
897+
898+
u64::max(max_unpack_size, size * max_compression_ratio as u64)
866899
}
867900

868901
fn make_dep_prefix(name: &str) -> String {

tests/testsuite/registry.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,10 +2756,12 @@ fn reach_max_unpack_size() {
27562756
.file("src/main.rs", "fn main() {}")
27572757
.build();
27582758

2759+
// Size of bar.crate is around 180 bytes.
27592760
Package::new("bar", "0.0.1").publish();
27602761

27612762
p.cargo("build")
27622763
.env("__CARGO_TEST_MAX_UNPACK_SIZE", "8") // hit 8 bytes limit and boom!
2764+
.env("__CARGO_TEST_MAX_UNPACK_RATIO", "0")
27632765
.with_status(101)
27642766
.with_stderr(
27652767
"\
@@ -2776,6 +2778,18 @@ Caused by:
27762778
27772779
Caused by:
27782780
maximum limit reached when reading
2781+
",
2782+
)
2783+
.run();
2784+
2785+
// Restore to the default ratio and it should compile.
2786+
p.cargo("build")
2787+
.env("__CARGO_TEST_MAX_UNPACK_SIZE", "8")
2788+
.with_stderr(
2789+
"\
2790+
[COMPILING] bar v0.0.1
2791+
[COMPILING] foo v0.0.1 ([..])
2792+
[FINISHED] dev [..]
27792793
",
27802794
)
27812795
.run();

0 commit comments

Comments
 (0)