create: lift TIFF decoding buffer limit to allow print-sized images (#665) (#681)

* create: lift TIFF decoding buffer limit to allow print-sized images (#665)

By default, the tiff crate limits decoded buffer size to 256 MiB.
Set Decoder::with_limits(Limits::unlimited()) to match PNG decoding
behavior and prevent 'decoder limits exceeded' on large images.

* create: cap the TIFF decode buffer at 2 GiB instead of lifting it entirely (#665)

A few bytes claiming enormous dimensions must still be refused, not allocated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Krzysztof Cieślik 2026-10-10 12:12:10 +02:00 committed by GitHub
parent a211e334a0
commit 627d822709
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 44 additions and 2 deletions

View File

@ -67,3 +67,7 @@ sync_github_name = false
[people.Byxis]
sync_github_name = true
[people.Hathor875]
real_name = "Krzysztof Cieślik"
sync_github_name = true

View File

@ -303,13 +303,21 @@ fn decoded(name: &str, bytes: &[u8], format: image::ImageFormat) -> Result<Embed
Ok(rgba_image(rgba.as_raw(), rgba.dimensions(), (72.0, 72.0)))
}
/// Most decoded bytes one TIFF page may take: room for the 768 MiB CMYK poster in #665, twice over.
const MAX_TIFF_DECODED_BYTES: usize = 2 << 30;
/// Every page of a TIFF (multi-page scans become multi-page PDFs).
fn tiff_pages(name: &str, bytes: &[u8]) -> Result<Vec<Embedded>, CreateError> {
use tiff::ColorType as C;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::decoder::{Decoder, DecodingResult, Limits};
use tiff::tags::Tag;
let bad = |m: String| CreateError::Image(name.into(), m);
let mut dec = Decoder::new(std::io::Cursor::new(bytes)).map_err(|e| bad(e.to_string()))?;
// The tiff crate stops at 256 MiB of decoded pixels, below an ordinary poster scan (#665):
// 12000x16000 CMYK is 768 MiB. Raise it, but keep a ceiling so a tiny file claiming
// enormous dimensions can't ask for unbounded memory.
let mut limits = Limits::default();
limits.decoding_buffer_size = MAX_TIFF_DECODED_BYTES;
let mut dec = Decoder::new(std::io::Cursor::new(bytes)).map_err(|e| bad(e.to_string()))?.with_limits(limits);
let mut out = Vec::new();
loop {
let (w, h) = dec.dimensions().map_err(|e| bad(e.to_string()))?;

View File

@ -168,6 +168,36 @@ fn bmp_gif_and_multi_page_tiff_images() {
assert!(matches!(from_images(&[("x.webp".into(), b"RIFF0000WEBP".to_vec())]), Err(CreateError::Image(..))));
}
/// #665 lifted the tiff crate's 256 MiB decode limit; a few bytes claiming a 60000x60000 page
/// (3.6 GB of gray pixels) must still be refused, not allocated.
#[test]
fn a_tiff_claiming_enormous_dimensions_is_refused() {
let entries: [(u16, u16, u32); 9] = [
(256, 4, 60_000), // ImageWidth
(257, 4, 60_000), // ImageLength
(258, 3, 8), // BitsPerSample
(259, 3, 1), // Compression: none
(262, 3, 1), // BlackIsZero
(273, 4, 122), // StripOffsets: just past the IFD
(277, 3, 1), // SamplesPerPixel
(278, 4, 60_000), // RowsPerStrip
(279, 4, 1), // StripByteCounts
];
let mut tif = b"II*\0".to_vec();
tif.extend_from_slice(&8u32.to_le_bytes());
tif.extend_from_slice(&(entries.len() as u16).to_le_bytes());
for (tag, kind, value) in entries {
tif.extend_from_slice(&tag.to_le_bytes());
tif.extend_from_slice(&kind.to_le_bytes());
tif.extend_from_slice(&1u32.to_le_bytes());
tif.extend_from_slice(&value.to_le_bytes());
}
tif.extend_from_slice(&0u32.to_le_bytes());
assert_eq!(tif.len(), 122);
tif.push(0);
assert!(matches!(from_images(&[("huge.tif".into(), tif)]), Err(CreateError::Image(..))));
}
#[test]
fn images_export_as_jpeg_unchanged_and_others_as_png() {
let doc = reopen(