mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 14:07:34 -06:00
Merge PR #2740: Never crash: ARW and SRW strips shorter than declared are refused before allocating (#2561)
This commit is contained in:
@@ -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::*;
|
||||
|
||||
Vendored
+30
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+15
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user