mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 14:22:39 +00:00
Place: WebP EXIF stops at the declared RIFF end
The RIFF size counts the bytes after its own field, so the container ends at offset 8 plus the size, not 12. An EXIF chunk that ran up to four bytes past the declared container was read when trailing bytes followed it. A new test covers an exact size with and without trailing bytes, a size declared four bytes short, and a file cut inside the EXIF chunk.
This commit is contained in:
1 parent
7ba1f8b96f
commit
58cf0ba28a
2 files changed
+36
-1
No files matched your search
@@ -203,8 +203,9 @@ fn png_resolution(bytes: &[u8]) -> Option<(f64, f64)> {
|
||||
|
||||
/// A WebP EXIF chunk carries a TIFF header: reuse the TIFF resolution tags.
|
||||
fn webp_resolution(bytes: &[u8]) -> Option<(f64, f64)> {
|
||||
// The RIFF size counts the bytes after the size field itself (offset 8).
|
||||
let riff = u32::from_le_bytes(bytes.get(4..8)?.try_into().ok()?) as usize;
|
||||
let end = 12usize.checked_add(riff)?.min(bytes.len());
|
||||
let end = 8usize.checked_add(riff)?.min(bytes.len());
|
||||
let mut pos = 12;
|
||||
loop {
|
||||
let kind = bytes.get(pos..pos + 4)?;
|
||||
|
||||
@@ -676,6 +676,40 @@ fn webp_exif_after_many_frames_is_read() {
|
||||
assert_eq!(raster_resolution(&webp), Some((300.0, 300.0)));
|
||||
}
|
||||
|
||||
/// The RIFF size counts the bytes after its own field (offset 8): an EXIF chunk that runs past
|
||||
/// the declared container is not read, even when trailing bytes follow the container.
|
||||
#[test]
|
||||
fn webp_exif_past_the_declared_riff_end_is_not_read() {
|
||||
let mut body = b"WEBP".to_vec();
|
||||
body.extend_from_slice(b"VP8X");
|
||||
body.extend_from_slice(&10u32.to_le_bytes());
|
||||
body.extend_from_slice(&[0; 10]);
|
||||
let exif = tiff_bytes((300, 1), (300, 1), Some(2));
|
||||
body.extend_from_slice(b"EXIF");
|
||||
body.extend_from_slice(&(exif.len() as u32).to_le_bytes());
|
||||
body.extend_from_slice(&exif);
|
||||
if exif.len() % 2 == 1 {
|
||||
body.push(0);
|
||||
}
|
||||
let riff = |size: usize, trailing: usize| {
|
||||
let mut webp = b"RIFF".to_vec();
|
||||
webp.extend_from_slice(&(size as u32).to_le_bytes());
|
||||
webp.extend_from_slice(&body);
|
||||
webp.extend_from_slice(&vec![0xAA; trailing]);
|
||||
webp
|
||||
};
|
||||
// The exact size, with or without trailing data, reads the chunk.
|
||||
assert_eq!(raster_resolution(&riff(body.len(), 0)), Some((300.0, 300.0)));
|
||||
assert_eq!(raster_resolution(&riff(body.len(), 4)), Some((300.0, 300.0)));
|
||||
// A size declared 4 bytes short ends the container inside the EXIF chunk.
|
||||
assert_eq!(raster_resolution(&riff(body.len() - 4, 4)), None);
|
||||
assert_eq!(raster_resolution(&riff(body.len() - 4, 0)), None);
|
||||
// A file cut inside the EXIF chunk is not read either.
|
||||
let mut cut = riff(body.len(), 0);
|
||||
cut.truncate(cut.len() - 6);
|
||||
assert_eq!(raster_resolution(&cut), None);
|
||||
}
|
||||
|
||||
/// A real encoder-written BMP with the header pixels-per-meter patched (compression and image
|
||||
/// size stay zero, as the encoder writes them).
|
||||
fn bmp_with_ppm(ppm: i32) -> Vec<u8> {
|
||||
|
||||
Reference in new issue
Block a user