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) <[email protected]>
This commit is contained in:
osobh
2026-09-26 01:14:33 -05:00
co-authored by Claude Opus 5.5
parent e01160299a
commit 9416c58723
+25 -4
View File
@@ -143,6 +143,9 @@ pub fn blosc_decompress(input: &[u8], limit: usize) -> Result<Vec<u8>, 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<Vec<u8>, 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}");
}
}
}