mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-28 12:08:46 -06:00
fix(create): refuse a GIF screen past 4096x4096 and convert its canvas in place
A one-pixel GIF on a huge logical screen decodes to a canvas that size, which the conversion then copied before splitting it into colour and alpha, so a few bytes could hold over a gigabyte. Screens past 16 Mpx are refused before decoding, and the decoded canvas is converted without a copy.
This commit is contained in:
@@ -393,11 +393,22 @@ fn rgba_image(rgba: &[u8], (w, h): (u32, u32), dpi: (f64, f64)) -> Embedded {
|
||||
/// BMP and GIF (the first frame), through the `image` decoders. Their resolution is not read:
|
||||
/// 72 dpi, as for images without one.
|
||||
fn decoded(name: &str, bytes: &[u8], format: image::ImageFormat) -> Result<Embedded, CreateError> {
|
||||
// A GIF's first frame is painted on a canvas the size of its logical screen, however small
|
||||
// the frame: a few bytes can ask for hundreds of MB.
|
||||
if format == image::ImageFormat::Gif
|
||||
&& let Some(&[w0, w1, h0, h1]) = bytes.get(6..10)
|
||||
&& u64::from(u16::from_le_bytes([w0, w1])) * u64::from(u16::from_le_bytes([h0, h1])) > MAX_GIF_PIXELS
|
||||
{
|
||||
return Err(CreateError::Image(name.into(), "the GIF's screen is larger than 4096×4096 pixels".into()));
|
||||
}
|
||||
let img = image::load_from_memory_with_format(bytes, format).map_err(|e| CreateError::Image(name.into(), e.to_string()))?;
|
||||
let rgba = img.to_rgba8();
|
||||
let rgba = img.into_rgba8();
|
||||
Ok(rgba_image(rgba.as_raw(), rgba.dimensions(), (72.0, 72.0)))
|
||||
}
|
||||
|
||||
/// Largest GIF logical screen, in pixels (4096×4096).
|
||||
const MAX_GIF_PIXELS: u64 = 1 << 24;
|
||||
|
||||
/// Most decoded bytes one TIFF page or PNG may take: room for the 768 MiB CMYK poster in #665,
|
||||
/// twice over.
|
||||
const MAX_DECODED_BYTES: usize = 2 << 30;
|
||||
|
||||
@@ -1222,3 +1222,22 @@ fn tiff_pages_are_decoded_within_a_total_budget() {
|
||||
let mut doc = Document::new_empty();
|
||||
assert!(image_xobject(&mut doc, "t.tif", &tif).is_ok(), "the unreadable last page is never decoded");
|
||||
}
|
||||
|
||||
/// A one-pixel GIF on a `w`×`h` logical screen: the decoder paints it on a canvas that size.
|
||||
fn gif_on_screen(w: u16, h: u16) -> Vec<u8> {
|
||||
let mut gif = b"GIF89a".to_vec();
|
||||
gif.extend_from_slice(&w.to_le_bytes());
|
||||
gif.extend_from_slice(&h.to_le_bytes());
|
||||
gif.extend_from_slice(&[0x80, 0, 0, 0xFF, 0xFF, 0xFF, 0, 0, 0]);
|
||||
gif.extend_from_slice(&[0x2C, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 0x44, 1, 0, 0x3B]);
|
||||
gif
|
||||
}
|
||||
|
||||
/// The decoder's own limit lets a 74-byte GIF ask for a canvas of hundreds of MB, which the
|
||||
/// conversion then copies; a screen past [`MAX_GIF_PIXELS`] is refused before decoding.
|
||||
#[test]
|
||||
fn a_tiny_gif_on_a_huge_screen_is_refused() {
|
||||
assert!(matches!(from_images(&[("big.gif".into(), gif_on_screen(4200, 4100))]), Err(CreateError::Image(..))));
|
||||
let doc = reopen(&from_images(&[("small.gif".into(), gif_on_screen(40, 30))]).unwrap());
|
||||
assert_eq!(media(&pages(&doc)[0]), [0.0, 0.0, 40.0, 30.0]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user