From 63402451cce0d76926119cc9c0e961e7a4076b18 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:15:57 +0200 Subject: [PATCH] Check embedded PNG/JPEG bitmap size from the header before decoding (#1716) --- crates/metafile/src/dib.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/crates/metafile/src/dib.rs b/crates/metafile/src/dib.rs index ef98ef15..a091087d 100644 --- a/crates/metafile/src/dib.rs +++ b/crates/metafile/src/dib.rs @@ -161,11 +161,12 @@ pub(crate) fn decode(bmi: &[u8], bits: &[u8], alpha: Alpha) -> Result MAX_READ_SIDE || h > MAX_READ_SIDE || u64::from(w) * u64::from(h) > MAX_READ_PIXELS { return Err("a bitmap is too large to read".into()); } + let img = image::load_from_memory(bits).map_err(|_| bad())?.to_rgba8(); return Ok(Rgba { width: w, height: h, pixels: img.into_raw() }); } if compression != BI_RGB && compression != BI_BITFIELDS { @@ -287,6 +288,34 @@ mod tests { Rgba { width: 3, height: 2, pixels } } + /// CRC-32 of a PNG chunk. + fn crc32(data: &[u8]) -> u32 { + let mut crc = !0u32; + for &b in data { + crc ^= u32::from(b); + for _ in 0..8 { + crc = if crc & 1 == 1 { (crc >> 1) ^ 0xEDB8_8320 } else { crc >> 1 }; + } + } + !crc + } + + #[test] + fn oversized_png_is_refused_from_its_header() { + let mut png = vec![]; + image::GrayImage::new(1, 1).write_to(&mut Cursor::new(&mut png), image::ImageFormat::Png).unwrap(); + // Claim a width over the limit in the IHDR chunk (and fix its CRC); the pixel data no longer + // matches, so decoding it would fail with a different error. + png[16..20].copy_from_slice(&(MAX_READ_SIDE + 1).to_be_bytes()); + let crc = crc32(&png[12..29]); + png[29..33].copy_from_slice(&crc.to_be_bytes()); + let mut bmi = vec![0u8; 40]; + bmi[..4].copy_from_slice(&40u32.to_le_bytes()); + bmi[16..20].copy_from_slice(&BI_PNG.to_le_bytes()); + let err = decode(&bmi, &png, Alpha::Ignore).unwrap_err(); + assert_eq!(err, "a bitmap is too large to read"); + } + #[test] fn rgb24_is_bottom_up_bgr_over_white() { let (bmi, bits) = rgb24(&sample(), 1.0);