Skip to content

Commit 6c387f9

Browse files
committed
Fix ZstdDecoder to propagate UnexpectedEof error on truncated streams
The Zstd decoder was silently accepting truncated streams by not validating stream completion in finish(). This issue was discovered by the generic truncated stream test. Added state tracking to ZstdDecoder: - Added stream_ended field to track if remaining == 0 was seen - Modified decode() to set stream_ended = true when stream completes - Updated finish() to check stream_ended and return UnexpectedEof if false - Updated all constructors to initialize stream_ended = false This matches the behavior of other decoders (bzip2, gzip, lz4, etc.) and ensures applications cannot accidentally accept corrupted or incomplete zstd data as valid. The generic truncated test now passes for Zstd.
1 parent 40ad91c commit 6c387f9

File tree

1 file changed

+21
-2
lines changed

1 file changed

+21
-2
lines changed

crates/compression-codecs/src/zstd/decoder.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ use zstd_safe::get_error_name;
1313
#[derive(Debug)]
1414
pub struct ZstdDecoder {
1515
decoder: Unshared<Decoder<'static>>,
16+
stream_ended: bool,
1617
}
1718

1819
impl Default for ZstdDecoder {
1920
fn default() -> Self {
2021
Self {
2122
decoder: Unshared::new(Decoder::new().unwrap()),
23+
stream_ended: false,
2224
}
2325
}
2426
}
@@ -35,13 +37,15 @@ impl ZstdDecoder {
3537
}
3638
Self {
3739
decoder: Unshared::new(decoder),
40+
stream_ended: false,
3841
}
3942
}
4043

4144
pub fn new_with_dict(dictionary: &[u8]) -> io::Result<Self> {
4245
let decoder = Decoder::with_dictionary(dictionary)?;
4346
Ok(Self {
4447
decoder: Unshared::new(decoder),
48+
stream_ended: false,
4549
})
4650
}
4751

@@ -64,6 +68,7 @@ impl ZstdDecoder {
6468
impl DecodeV2 for ZstdDecoder {
6569
fn reinit(&mut self) -> Result<()> {
6670
self.decoder.get_mut().reinit()?;
71+
self.stream_ended = false;
6772
Ok(())
6873
}
6974

@@ -80,15 +85,29 @@ impl DecodeV2 for ZstdDecoder {
8085
.run_on_buffers(input.unwritten(), output.unwritten_initialized_mut())?;
8186
input.advance(status.bytes_read);
8287
output.advance(status.bytes_written);
83-
Ok(status.remaining == 0)
88+
89+
let finished = status.remaining == 0;
90+
if finished {
91+
self.stream_ended = true;
92+
}
93+
Ok(finished)
8494
}
8595

8696
fn flush(&mut self, output: &mut WriteBuffer<'_>) -> Result<bool> {
8797
self.call_fn_on_out_buffer(output, |decoder, out_buf| decoder.flush(out_buf))
8898
}
8999

90100
fn finish(&mut self, output: &mut WriteBuffer<'_>) -> Result<bool> {
91-
self.call_fn_on_out_buffer(output, |decoder, out_buf| decoder.finish(out_buf, true))
101+
self.call_fn_on_out_buffer(output, |decoder, out_buf| decoder.finish(out_buf, true))?;
102+
103+
if self.stream_ended {
104+
Ok(true)
105+
} else {
106+
Err(io::Error::new(
107+
io::ErrorKind::UnexpectedEof,
108+
"zstd stream did not finish",
109+
))
110+
}
92111
}
93112
}
94113

0 commit comments

Comments
 (0)