diff --git a/crates/tiff/src/reader.rs b/crates/tiff/src/reader.rs index e1c6dd3a9..a644159b7 100644 --- a/crates/tiff/src/reader.rs +++ b/crates/tiff/src/reader.rs @@ -140,9 +140,10 @@ impl<'a> Ctx<'a> { } entries.sort_by_key(|e| e.tag); entries.dedup_by_key(|e| e.tag); - let next_pos = table + (n as usize).saturating_mul(entry_size); - let next = if n as usize == n_read { - match self.read_off(next_pos) { + // the next-IFD offset follows the whole table, so only a table read in full has one: a count far past the + // file (a BigTIFF's is 64-bit) must not reach the arithmetic (issue #1516) + let next = if n == n_read as u64 { + match n_read.checked_mul(entry_size).and_then(|len| table.checked_add(len)).and_then(|p| self.read_off(p)) { Some(0) | None => 0, Some(v) => base.checked_add(v).unwrap_or(0), } diff --git a/crates/tiff/tests/roundtrip.rs b/crates/tiff/tests/roundtrip.rs index 54fc82376..3b9a1bd1c 100644 --- a/crates/tiff/tests/roundtrip.rs +++ b/crates/tiff/tests/roundtrip.rs @@ -393,3 +393,37 @@ proptest! { prop_assert_eq!(t.ifds[0].f64s(501).unwrap().len(), v.len()); } } + +/// A BigTIFF whose IFD says it has an enormous number of entries, with one readable, computed where the next-IFD +/// offset would be from the advertised count: an add overflow, a panic in builds with overflow checks (issue +/// #1516). The readable entry is kept and there is no next IFD; complete chains still link. +#[test] +fn bigtiff_with_an_enormous_entry_count_does_not_panic() { + for order in ORDERS { + for count in [u64::MAX, 1 << 62, 2] { + // little-endian bytes, reversed for big-endian files + let e = |le: &[u8]| -> Vec { if order == ByteOrder::Little { le.to_vec() } else { le.iter().rev().copied().collect() } }; + let mut f = if order == ByteOrder::Little { b"II".to_vec() } else { b"MM".to_vec() }; + for part in [e(&43u16.to_le_bytes()), e(&8u16.to_le_bytes()), e(&0u16.to_le_bytes()), e(&16u64.to_le_bytes()), e(&count.to_le_bytes())] { + f.extend(part); + } + // one entry: ImageWidth, LONG, 1 value (inline in the 8-byte field) + for part in [e(&tags::IMAGE_WIDTH.to_le_bytes()), e(&4u16.to_le_bytes()), e(&1u64.to_le_bytes()), e(&640u32.to_le_bytes())] { + f.extend(part); + } + f.extend([0; 4]); + let (ifd, next) = parse_ifd_at(&f, 16, order, 0, true, &ParseOptions::default()).unwrap(); + assert_eq!(ifd.value(tags::IMAGE_WIDTH), Some(&Value::Long(vec![640])), "{order:?} {count}"); + assert_eq!(next, 0, "{order:?} {count}"); + let t = Tiff::parse(&f).unwrap(); + assert_eq!(t.ifds.len(), 1); + } + // a complete chain of two IFDs still links + let mut a = IfdBuilder::new(); + a.set(1000, Value::Short(vec![1])); + let mut b = IfdBuilder::new(); + b.set(1000, Value::Short(vec![2])); + let t = Tiff::parse(&TiffWriter::new(order, true).write(&[a, b]).unwrap()).unwrap(); + assert_eq!(t.ifds.len(), 2, "{order:?}"); + } +}