mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-27 20:06:53 -06:00
#300: a colour PNG strike's size is checked before its pixels are allocated
This commit is contained in:
@@ -29,6 +29,8 @@ const MAX_RASTER_BYTES: usize = 32 * 1024 * 1024;
|
||||
const MAX_RASTER_ENTRIES: usize = 8192;
|
||||
/// COLR layer lists kept per face (24 bytes a layer, so a count is enough).
|
||||
const MAX_LAYERS: usize = 4096;
|
||||
/// Widest and tallest PNG strike decoded (strikes are at most a few hundred pixels).
|
||||
const MAX_PNG_SIDE: u32 = 4096;
|
||||
/// Largest colour raster per em (device pixels). Past this the renderer scales one up instead.
|
||||
pub const MAX_PX_PER_EM: f64 = 512.0;
|
||||
|
||||
@@ -223,13 +225,15 @@ fn decode_png(png: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
|
||||
// Expand palettes, `tRNS` and sub-byte samples to 8-bit RGBA.
|
||||
decoder.set_transformations(png::Transformations::normalize_to_color8() | png::Transformations::ALPHA);
|
||||
let mut reader = decoder.read_info().ok()?;
|
||||
let mut buf = vec![0u8; reader.output_buffer_size()?];
|
||||
let info = reader.next_frame(&mut buf).ok()?;
|
||||
if info.width == 0 || info.height == 0 || info.width > 4096 || info.height > 4096 {
|
||||
// The header's size is checked before anything is allocated for it: a font can claim any size.
|
||||
let (w, h) = reader.info().size();
|
||||
if w == 0 || h == 0 || w > MAX_PNG_SIDE || h > MAX_PNG_SIDE {
|
||||
return None;
|
||||
}
|
||||
let mut buf = vec![0u8; reader.output_buffer_size()?];
|
||||
let info = reader.next_frame(&mut buf).ok()?;
|
||||
let (width, height) = (info.width, info.height);
|
||||
let data = &buf[..info.buffer_size()];
|
||||
let data = buf.get(..info.buffer_size())?;
|
||||
let rgba = match info.color_type {
|
||||
png::ColorType::Rgba => data.to_vec(),
|
||||
png::ColorType::Rgb => data.as_chunks::<3>().0.iter().flat_map(|p| [p[0], p[1], p[2], 255]).collect(),
|
||||
|
||||
@@ -86,6 +86,22 @@ fn png_strikes_decode_to_premultiplied_rgba() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_png_strike_claiming_a_huge_size_is_refused_before_anything_is_allocated() {
|
||||
// PNG_STRAIGHT with its header saying 1 048 576 × 1 048 576 pixels (4 TiB as RGBA).
|
||||
let mut png = PNG_STRAIGHT.to_vec();
|
||||
assert_eq!(crc32(&png[12..29]).to_be_bytes(), png[29..33], "the patched header passes its checksum");
|
||||
png[16..24].copy_from_slice(&[0, 0x10, 0, 0, 0, 0x10, 0, 0]);
|
||||
let crc = crc32(&png[12..29]);
|
||||
png[29..33].copy_from_slice(&crc.to_be_bytes());
|
||||
assert!(decode_png(&png).is_none());
|
||||
}
|
||||
|
||||
/// The CRC-32 a PNG chunk ends with.
|
||||
fn crc32(bytes: &[u8]) -> u32 {
|
||||
!bytes.iter().fold(!0u32, |c, b| (0..8).fold(c ^ u32::from(*b), |c, _| if c & 1 == 1 { 0xedb8_8320 ^ (c >> 1) } else { c >> 1 }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_bgra_strikes_are_already_premultiplied() {
|
||||
// BitmapData::Bgra is BGRA with premultiplied channels (the spec says so), so decoding only
|
||||
|
||||
Reference in New Issue
Block a user