From 0c8421b40a8d2f5a3f185c6565ebf804640e2178 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 15 Jul 2026 09:49:58 -0500 Subject: [PATCH 1/2] Fix errors in subsequent compressed members --- .../src/generic/bufread/decoder.rs | 5 ++--- crates/async-compression/tests/zstd.rs | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/async-compression/src/generic/bufread/decoder.rs b/crates/async-compression/src/generic/bufread/decoder.rs index ce827b3c..eb951a58 100644 --- a/crates/async-compression/src/generic/bufread/decoder.rs +++ b/crates/async-compression/src/generic/bufread/decoder.rs @@ -81,9 +81,8 @@ impl Decoder { } } - // The decode stage might consume all the input, - // the next stage might need to poll again if it's empty. - first = true; + // Poll again only if the decode stage consumed all the input. + first = input.unwritten().is_empty(); State::Next } else { State::Done diff --git a/crates/async-compression/tests/zstd.rs b/crates/async-compression/tests/zstd.rs index 8401aff9..596ff27e 100644 --- a/crates/async-compression/tests/zstd.rs +++ b/crates/async-compression/tests/zstd.rs @@ -2,3 +2,24 @@ mod utils; test_cases!(zstd); + +#[cfg(feature = "tokio")] +#[test] +#[ntest::timeout(1000)] +fn bufread_multiple_members_with_corrupt_second_frame() { + use async_compression::tokio::bufread::ZstdDecoder; + use tokio::io::{sink, BufReader}; + use utils::algos::zstd::sync; + + let first = sync::compress(&[1, 2, 3, 4, 5, 6]); + let mut second = sync::compress(&[0; 2048]); + let corrupt = second.len() - 2; + second[corrupt] ^= 0xff; + let compressed = [first, second].join(&[][..]); + + let mut decoder = ZstdDecoder::new(BufReader::new(compressed.as_slice())); + decoder.multiple_members(true); + + let result = futures::executor::block_on(tokio::io::copy(&mut decoder, &mut sink())); + assert!(result.is_err()); +} From 727f8841504734b95cea5948f505d68b64914130 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 15 Jul 2026 10:03:42 -0500 Subject: [PATCH 2/2] Move corrupt member test to shared decoder suite --- .../tests/utils/test_cases.rs | 18 ++++++++++++++++ crates/async-compression/tests/zstd.rs | 21 ------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/crates/async-compression/tests/utils/test_cases.rs b/crates/async-compression/tests/utils/test_cases.rs index af79c4b1..f30efe3c 100644 --- a/crates/async-compression/tests/utils/test_cases.rs +++ b/crates/async-compression/tests/utils/test_cases.rs @@ -232,6 +232,24 @@ macro_rules! io_test_cases { assert_eq!(output, &[1, 2, 3, 4, 5, 6, 6, 5, 4, 3, 2, 1][..]); } + #[test] + #[ntest::timeout(1000)] + fn corrupt_second_member() { + let first = sync::compress(&[1, 2, 3, 4, 5, 6]); + let mut second = sync::compress(&[0; 2048]); + let corrupt = second.len() - 2; + second[corrupt] ^= 0xff; + let compressed = [first, second].join(&[][..]); + + let input = InputStream::new(vec![compressed]); + let result = std::panic::catch_unwind(|| { + let mut decoder = bufread::Decoder::new(bufread::from(&input)); + decoder.multiple_members(true); + read::to_vec(decoder) + }); + assert!(result.is_err()); + } + #[test] #[ntest::timeout(1000)] fn truncated() { diff --git a/crates/async-compression/tests/zstd.rs b/crates/async-compression/tests/zstd.rs index 596ff27e..8401aff9 100644 --- a/crates/async-compression/tests/zstd.rs +++ b/crates/async-compression/tests/zstd.rs @@ -2,24 +2,3 @@ mod utils; test_cases!(zstd); - -#[cfg(feature = "tokio")] -#[test] -#[ntest::timeout(1000)] -fn bufread_multiple_members_with_corrupt_second_frame() { - use async_compression::tokio::bufread::ZstdDecoder; - use tokio::io::{sink, BufReader}; - use utils::algos::zstd::sync; - - let first = sync::compress(&[1, 2, 3, 4, 5, 6]); - let mut second = sync::compress(&[0; 2048]); - let corrupt = second.len() - 2; - second[corrupt] ^= 0xff; - let compressed = [first, second].join(&[][..]); - - let mut decoder = ZstdDecoder::new(BufReader::new(compressed.as_slice())); - decoder.multiple_members(true); - - let result = futures::executor::block_on(tokio::io::copy(&mut decoder, &mut sink())); - assert!(result.is_err()); -}