From 8abb1d1c71236add1bd587c93d80e2c1088e0f84 Mon Sep 17 00:00:00 2001 From: skylar taylor-barrick Date: Thu, 22 Oct 2026 13:11:34 -0600 Subject: [PATCH] #1807: test that a zstd block larger than its frame's window is refused ruzstd 0.9.0 let a block decompress to more than its frame's window allows (RUSTSEC-2026-0344). The test swaps a hand-built Zstandard frame with a 1 KiB window and one 100,000-byte RLE block into an Affinity container entry and checks that reading the entry fails. With ruzstd pinned back to 0.9.0 the block is decompressed and the read succeeds. --- crates/affinity/src/tests.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/affinity/src/tests.rs b/crates/affinity/src/tests.rs index 006f9e7bd..817012fa7 100644 --- a/crates/affinity/src/tests.rs +++ b/crates/affinity/src/tests.rs @@ -261,6 +261,32 @@ mod archive { assert!(matches!(a.read("doc.dat"), Err(Error::Limit(_)))); } + // Regression test for #1710: ruzstd 0.9.0 let a block decompress to more than its frame's + // window allows (RUSTSEC-2026-0344), so a crafted entry made the decoder produce far more than + // the format permits for a block. 0.9.1 refuses it. + #[test] + fn a_zstd_block_larger_than_its_frames_window_is_refused() { + const LEN: u32 = 100_000; + let plain = vec![0x61u8; LEN as usize]; + let file = synth::container(&[("doc.dat", &plain, Method::Zstd)], None); + // A Zstandard frame with a 1 KiB window (descriptor byte 0, window byte 0) holding one RLE + // block, the last, that repeats a byte LEN times: block header = last | RLE << 1 | LEN << 3. + let mut frame = vec![0x28, 0xB5, 0x2F, 0xFD, 0x00, 0x00]; + frame.extend((1 | (1 << 1) | (LEN << 3)).to_le_bytes().get(..3).unwrap()); + frame.push(0x61); + // Swap it in for the entry's compressed bytes (they follow the "#Fil" tag at offset 72). + let fat = u64::from_le_bytes(file[16..24].try_into().unwrap()) as usize; + let old_len = u64::from_le_bytes(file[fat + 59 + 21..fat + 59 + 29].try_into().unwrap()) as usize; + let mut swapped = file[..76].to_vec(); + swapped.extend(&frame); + swapped.extend(&file[76 + old_len..]); + let new_fat = fat - old_len + frame.len(); + swapped[16..24].copy_from_slice(&(new_fat as u64).to_le_bytes()); + swapped[new_fat + 59 + 21..new_fat + 59 + 29].copy_from_slice(&(frame.len() as u64).to_le_bytes()); + let mut a = Archive::open(&swapped, Limits::default()).unwrap(); + assert!(a.read("doc.dat").is_err(), "a block of {LEN} bytes was decompressed from a frame with a 1 KiB window"); + } + #[test] fn a_declared_size_smaller_than_the_data_is_rejected_not_truncated() { let big = vec![7u8; 1 << 20];