mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 14:22:39 +00:00
Images: a WebP with an ICC chunk longer than the file is read without its profile
* Images: a WebP whose ICC chunk declares more than the file holds is read without it
This commit is contained in:
1 parent
23465223bc
commit
eb0ea78d9a
3 files changed
+56
No files matched your search
@@ -433,9 +433,32 @@ pub fn decode_rgba(bytes: &[u8]) -> Option<image::RgbaImage> {
|
||||
to_display_rgba(px, Some(&icc), &DisplayColour::active())
|
||||
}
|
||||
|
||||
/// False when `bytes` is a WebP whose `ICCP` chunk declares more bytes than the file holds or than
|
||||
/// [`colour::MAX_ICC_BYTES`]. The decoder sizes its buffer from the declared length before it reads
|
||||
/// anything, so such a file must not reach `icc_profile()`; its pixels decode without it.
|
||||
pub fn webp_icc_is_sane(bytes: &[u8]) -> bool {
|
||||
if bytes.get(0..4) != Some(b"RIFF") || bytes.get(8..12) != Some(b"WEBP") {
|
||||
return true;
|
||||
}
|
||||
let mut pos = 12usize;
|
||||
while let Some(head) = bytes.get(pos..pos.saturating_add(8)) {
|
||||
let size = u32::from_le_bytes([head[4], head[5], head[6], head[7]]) as usize;
|
||||
let body = pos + 8;
|
||||
if &head[..4] == b"ICCP" {
|
||||
return size <= colour::MAX_ICC_BYTES && body.saturating_add(size) <= bytes.len();
|
||||
}
|
||||
// Chunks are padded to an even size.
|
||||
pos = body.saturating_add(size).saturating_add(size & 1);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// The ICC profile embedded in a PNG, JPEG, TIFF or WebP.
|
||||
fn embedded_icc(bytes: &[u8]) -> Option<Vec<u8>> {
|
||||
use image::ImageDecoder;
|
||||
if !webp_icc_is_sane(bytes) {
|
||||
return None;
|
||||
}
|
||||
let mut d = image::ImageReader::new(std::io::Cursor::new(bytes)).with_guessed_format().ok()?.into_decoder().ok()?;
|
||||
d.icc_profile().ok().flatten()
|
||||
}
|
||||
|
||||
@@ -534,3 +534,30 @@ fn gif_tone_and_cmyk_inks_check_the_rgba_budget() {
|
||||
assert!(too_large_to_decode(&tiff));
|
||||
assert_eq!(decode_cmyk_tiff(&tiff), None);
|
||||
}
|
||||
|
||||
fn webp_with(chunks: &[(&[u8; 4], u32, &[u8])]) -> Vec<u8> {
|
||||
let mut v = b"RIFF\0\0\0\0WEBP".to_vec();
|
||||
for (id, declared, body) in chunks {
|
||||
v.extend_from_slice(*id);
|
||||
v.extend_from_slice(&declared.to_le_bytes());
|
||||
v.extend_from_slice(body);
|
||||
if body.len() % 2 == 1 {
|
||||
v.push(0);
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// A WebP that declares an `ICCP` chunk larger than the file (or than the ICC cap) never reaches the
|
||||
/// decoder's profile read, which would size a buffer from the declared length.
|
||||
#[test]
|
||||
fn a_webp_icc_chunk_that_declares_more_than_it_holds_is_refused_before_decoding() {
|
||||
assert!(webp_icc_is_sane(&webp_with(&[(b"VP8X", 10, &[0; 10]), (b"ICCP", 4, &[1; 4])])));
|
||||
// An odd-sized chunk before it is padded to even.
|
||||
assert!(webp_icc_is_sane(&webp_with(&[(b"EXIF", 3, &[0; 3]), (b"ICCP", 4, &[1; 4])])));
|
||||
assert!(!webp_icc_is_sane(&webp_with(&[(b"VP8X", 10, &[0; 10]), (b"ICCP", u32::MAX, &[1; 4])])));
|
||||
assert!(!webp_icc_is_sane(&webp_with(&[(b"ICCP", 100, &[1; 4])])));
|
||||
assert!(embedded_icc(&webp_with(&[(b"ICCP", u32::MAX, &[1; 4])])).is_none());
|
||||
assert!(webp_icc_is_sane(b"\x89PNG not a webp"));
|
||||
assert!(webp_icc_is_sane(&webp_with(&[(b"VP8L", 6, &[0; 6])])));
|
||||
}
|
||||
@@ -47,6 +47,9 @@ pub(super) struct RasterMode {
|
||||
/// The ICC profile embedded in an image file, if it has one.
|
||||
pub(super) fn embedded_icc(data: &[u8]) -> Option<Vec<u8>> {
|
||||
use image::ImageDecoder;
|
||||
if !designcraft_images::webp_icc_is_sane(data) {
|
||||
return None;
|
||||
}
|
||||
let mut dec = image::ImageReader::new(std::io::Cursor::new(data)).with_guessed_format().ok()?.into_decoder().ok()?;
|
||||
dec.icc_profile().ok().flatten()
|
||||
}
|
||||
@@ -89,6 +92,9 @@ pub(super) fn jpeg(data: krilla::Data, icc: impl FnOnce() -> Option<Vec<u8>>, mo
|
||||
/// its own.
|
||||
fn untagged_rgb(data: &[u8]) -> bool {
|
||||
use image::ImageDecoder;
|
||||
if !designcraft_images::webp_icc_is_sane(data) {
|
||||
return false;
|
||||
}
|
||||
let Some(mut dec) = image::ImageReader::new(std::io::Cursor::new(data)).with_guessed_format().ok().and_then(|r| r.into_decoder().ok()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user