Merge PR #2740: Never crash: ARW and SRW strips shorter than declared are refused before allocating (#2561)

This commit is contained in:
aiongg
2026-10-31 02:10:18 +00:00
3 changed files with 63 additions and 0 deletions
+18
View File
@@ -225,6 +225,24 @@ fn solve4(mut a: [[f64; 4]; 4], mut b: [f64; 4]) -> Option<[f64; 4]> {
x.iter().all(|v| v.is_finite()).then_some(x)
}
/// Test helper: make the single-strip `StripByteCounts` entry (LONG, count 1) that holds `len` declare `declared`
/// bytes instead, as a file cut short or a hostile header does.
#[cfg(test)]
pub(crate) fn declare_strip_len(file: &mut [u8], len: u32, declared: u32) {
let big = file.starts_with(b"MM");
let entry = |v: u32| -> Vec<u8> {
if big {
[&279u16.to_be_bytes()[..], &4u16.to_be_bytes(), &1u32.to_be_bytes(), &v.to_be_bytes()].concat()
} else {
[&279u16.to_le_bytes()[..], &4u16.to_le_bytes(), &1u32.to_le_bytes(), &v.to_le_bytes()].concat()
}
};
let (old, new) = (entry(len), entry(declared));
let at: Vec<usize> = file.windows(old.len()).enumerate().filter(|(_, w)| *w == old.as_slice()).map(|(i, _)| i).collect();
assert_eq!(at.len(), 1, "one StripByteCounts entry of {len} bytes");
file[at[0]..at[0] + new.len()].copy_from_slice(&new);
}
#[cfg(test)]
mod tests {
use super::*;
+30
View File
@@ -642,6 +642,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result<RawImage> {
}
32767 if one_byte_per_sample => {
let src = chunk_bytes(bytes, &chunks[0]).ok_or_else(|| RawError::Corrupt("raw strip outside file".into()))?;
if src.len() as u64 != strip_len {
return Err(RawError::Corrupt("raw strip shorter than its rows".into()));
}
let curve = code_curve(&raw.u64s(TONE_CURVE).unwrap_or_else(|| vec![8000, 10400, 12900, 14100]));
let mut data = vec![0u16; samples as usize];
data.par_chunks_mut(w).enumerate().for_each(|(y, out)| {
@@ -659,6 +662,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result<RawImage> {
}
32767 if packed12 => {
let src = chunk_bytes(bytes, &chunks[0]).ok_or_else(|| RawError::Corrupt("raw strip outside file".into()))?;
if src.len() as u64 != strip_len {
return Err(RawError::Corrupt("raw strip shorter than its rows".into()));
}
let row_bytes = w / 2 * 3;
let mut data = vec![0u16; samples as usize];
data.par_chunks_mut(w).enumerate().for_each(|(y, out)| {
@@ -1561,4 +1567,28 @@ mod tests {
assert!(is_quad_tiled(&file, &info));
assert_eq!(read_quad_tiles(&file, &info).unwrap(), mosaic);
}
/// #2561: a small file whose one-byte or packed 12-bit strip declares a large frame is refused before the full
/// decode allocates the frame.
#[test]
fn a_strip_cut_short_is_refused_before_allocating() {
use lightcraft_tiff::{IfdBuilder, ImageData, TiffWriter, Value};
let (w, h) = (3000u32, 2000u32);
for declared in [w * h, w * h / 2 * 3] {
let mut raw = IfdBuilder::new();
raw.set(t::MAKE, Value::Ascii("SONY".into()));
raw.set(t::MODEL, Value::Ascii("DSLR-A100".into()));
raw.set(t::IMAGE_WIDTH, Value::Long(vec![w]));
raw.set(t::IMAGE_LENGTH, Value::Long(vec![h]));
raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![12]));
raw.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1]));
raw.set(t::PHOTOMETRIC, Value::Short(vec![photometric::CFA]));
raw.set(t::COMPRESSION, Value::Short(vec![32767]));
raw.set(TONE_CURVE, Value::Short(vec![8000, 10400, 12900, 14100]));
raw.set_image(ImageData::Strips { rows_per_strip: h, strips: vec![vec![0u8; 12]] });
let mut f = TiffWriter::default().write(&[raw]).unwrap();
crate::vendor::declare_strip_len(&mut f, 12, declared);
assert!(matches!(decode(&f, Mode::Full), Err(RawError::Corrupt(_))), "{declared} bytes declared");
}
}
}
+15
View File
@@ -107,6 +107,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result<RawImage> {
};
let data = if mode == Mode::Full {
let src = chunk_bytes(bytes, &chunks[0]).ok_or_else(|| RawError::Corrupt("SRW strip outside file".into()))?;
if src.len() as u64 != strip {
return Err(RawError::Corrupt("SRW strip shorter than its rows".into()));
}
let mut d = vec![0u16; n];
match coding {
Coding::Words => read_u16s(src, ByteOrder::Little, &mut d),
@@ -540,4 +543,16 @@ mod tests {
let _ = probe_info(&bytes[..cut]);
}
}
/// #2561: a small file whose strip declares a large frame is refused before the full decode allocates the frame.
#[test]
fn a_strip_cut_short_is_refused_before_allocating() {
let (w, h) = (3000u32, 2000u32);
for (compression, declared) in [(32769, w * h * 2), (32770, w * h / 2 * 3)] {
let mut f = srw(Spec { w, h, compression, strip: vec![0u8; 12], pattern: Some([0, 1, 1, 2]), note: None, picture: None });
crate::vendor::declare_strip_len(&mut f, 12, declared);
assert_eq!(probe(&f), Some(crate::RawFormat::Srw));
assert!(matches!(decode(&f), Err(RawError::Corrupt(_))), "compression {compression}");
}
}
}