diff --git a/crates/raw/src/preview.rs b/crates/raw/src/preview.rs index d5dd013310..9f63173bf6 100644 --- a/crates/raw/src/preview.rs +++ b/crates/raw/src/preview.rs @@ -74,12 +74,13 @@ pub fn embedded_previews(bytes: &[u8]) -> Vec> { if let (Some(off), Some(len)) = (t.tag_uint(&ifd, tag::JPEG_INTERCHANGE_FORMAT), t.tag_uint(&ifd, tag::JPEG_INTERCHANGE_FORMAT_LENGTH)) { found.extend(candidate(bytes, off as usize, len as usize)); } - if matches!(t.tag_uint(&ifd, tag::COMPRESSION), Some(6 | 7)) { - let offs = t.tag_uints(&ifd, tag::STRIP_OFFSETS); - let lens = t.tag_uints(&ifd, tag::STRIP_BYTE_COUNTS); - if let ([off], [len]) = (offs.as_slice(), lens.as_slice()) { - found.extend(candidate(bytes, *off as usize, *len as usize)); - } + if matches!(t.tag_uint(&ifd, tag::COMPRESSION), Some(6 | 7)) + && let (Some(offsets), Some(lengths)) = (ifd.get(tag::STRIP_OFFSETS), ifd.get(tag::STRIP_BYTE_COUNTS)) + && offsets.count == 1 + && lengths.count == 1 + && let (Some(off), Some(len)) = (t.uint(offsets, 0), t.uint(lengths, 0)) + { + found.extend(candidate(bytes, off as usize, len as usize)); } // Panasonic RW2 JpgFromRaw (0x002E) holds a complete JPEG. if let Some(e) = ifd.get(0x002E).filter(|e| e.typ == 7) diff --git a/crates/raw/src/tiff.rs b/crates/raw/src/tiff.rs index cfcadee730..0fa11685f7 100644 --- a/crates/raw/src/tiff.rs +++ b/crates/raw/src/tiff.rs @@ -213,9 +213,11 @@ impl<'a> Tiff<'a> { } for t in [tag::SUB_IFDS, tag::EXIF_IFD] { if let Some(e) = ifd.get(t) { - // Reverse so the first SubIFD is visited first. - let mut v = self.uints(e); - v.truncate(16); + // Linked-directory traversal only needs the first sixteen offsets. Read + // those values directly instead of expanding the complete file-controlled + // array and truncating it afterward. + let n = (e.count as usize).min(16); + let v = (0..n).map(|i| self.uint(e, i)).collect::>>().unwrap_or_default(); stack.extend(v.into_iter().rev().map(|o| o as usize)); } } @@ -450,3 +452,20 @@ mod tests { assert!(out.contains("output truncated") || out.len() < 1 << 20); } } + +#[test] +fn linked_directory_values_are_read_only_as_needed() { + // The traversal limit is independent of the TIFF entry count: callers should + // never need a complete attacker-controlled offset array to visit SubIFDs. + let mut b = b"II*\0\x08\0\0\0".to_vec(); + b.extend_from_slice(&1u16.to_le_bytes()); + b.extend_from_slice(&330u16.to_le_bytes()); + b.extend_from_slice(&4u16.to_le_bytes()); + b.extend_from_slice(&(1_000_000u32).to_le_bytes()); + b.extend_from_slice(&26u32.to_le_bytes()); + b.extend_from_slice(&0u32.to_le_bytes()); + b.extend_from_slice(&0u32.to_le_bytes()); + b.resize(26 + 16 * 4, 0); + let t = Tiff::new(&b).unwrap(); + assert!(t.all_ifds().len() <= 17); +}