From 9416c5872310046980ce1e3b856fdd2debbbe2d4 Mon Sep 17 00:00:00 2001 From: osobh Date: Sat, 26 Sep 2026 01:14:33 -0500 Subject: [PATCH] fix(format): a Blosc frame shorter than its header is an error, not a panic A hostile chunk whose header gave a compressed size below 16 bytes, not stored raw, made the block-table check subtract past zero: a panic in any build with overflow checks (cargo test, maturin develop, debug CLI). The frame size is now checked against the header size, and the stream-length read no longer adds to an untrusted offset. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/clawhdf5-format/src/filters_blosc.rs | 29 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/clawhdf5-format/src/filters_blosc.rs b/crates/clawhdf5-format/src/filters_blosc.rs index 0730c15..1bf1d15 100644 --- a/crates/clawhdf5-format/src/filters_blosc.rs +++ b/crates/clawhdf5-format/src/filters_blosc.rs @@ -143,6 +143,9 @@ pub fn blosc_decompress(input: &[u8], limit: usize) -> Result, FormatErr if cbytes > input.len() { return Err(err("frame is longer than the chunk")); } + if cbytes < HEADER { + return Err(err("truncated frame")); + } let src = &input[..cbytes]; if nbytes == 0 { return Ok(Vec::new()); @@ -189,10 +192,11 @@ pub fn blosc_decompress(input: &[u8], limit: usize) -> Result, FormatErr let mut pos = le32(src, HEADER + 4 * j)?; let tmp = &mut tmp[..bsize]; for s in 0..nsplits { - if pos + 4 > src.len() { - return Err(err("block offset out of range")); - } - let clen = le32(src, pos)?; + let clen = src + .get(pos..) + .and_then(|rest| rest.get(..4)) + .map(|b| u32::from_le_bytes(b.try_into().unwrap()) as usize) + .ok_or_else(|| err("block offset out of range"))?; pos += 4; let stream = src .get(pos..pos.saturating_add(clen)) @@ -601,4 +605,21 @@ mod tests { let ctx0 = FilterContext { filter: &f0, ..ctx }; assert!(blosc_encode(&data, &ctx0).is_err()); } + + /// A frame whose header claims a compressed size smaller than the + /// header itself, not stored raw: an error, not an arithmetic overflow + /// (it panicked in debug builds). + #[test] + fn frame_size_below_the_header_is_an_error() { + let mut frame = vec![2u8, 1, 1 << 5, 4]; + for v in [64u32, 64, 8] { + frame.extend_from_slice(&v.to_le_bytes()); + } + frame.extend_from_slice(&[0; 40]); + assert!(blosc_decompress(&frame, 1000).is_err()); + for cbytes in 0..16u32 { + frame[12..16].copy_from_slice(&cbytes.to_le_bytes()); + assert!(blosc_decompress(&frame, 1000).is_err(), "cbytes={cbytes}"); + } + } }