From 63b6de336fe29614eeaab867c355d13f96846bef Mon Sep 17 00:00:00 2001 From: radialmonster Date: Sun, 25 Oct 2026 20:22:54 -0400 Subject: [PATCH] raw: CR3 preview box scan checks 32-bit lengths for overflow (#2569) On wasm32 a PRVW/THMB box or JPEG length near u32::MAX wrapped j + len and start + size, so both bounds checks passed and the slice panicked (abort in the web build). Use checked_add and bytes.get, and add a regression test. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM --- crates/raw/src/preview.rs | 6 ++++-- crates/raw/src/preview/tests.rs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/raw/src/preview.rs b/crates/raw/src/preview.rs index 2047174da..2403e5035 100644 --- a/crates/raw/src/preview.rs +++ b/crates/raw/src/preview.rs @@ -438,10 +438,12 @@ fn cr3_preview_boxes(bytes: &[u8]) -> Option<&[u8]> { let be32 = |at: usize| bytes.get(at..at + 4).map(|s| u32::from_be_bytes([s[0], s[1], s[2], s[3]]) as usize); let (Some(size), Some(len)) = (be32(start), be32(start + 20)) else { continue }; let j = start + 24; - if len < 4 || j + len > start + size.max(24) || j + len > bytes.len() { + // 32-bit box lengths can wrap `usize` on wasm32: check the sums, then slice with `get` + let (Some(end), Some(lim)) = (j.checked_add(len), start.checked_add(size.max(24))) else { continue }; + if len < 4 || end > lim { continue; } - let jpeg = &bytes[j..j + len]; + let Some(jpeg) = bytes.get(j..end) else { continue }; // One that doesn't fit the budget left is skipped; a smaller one later may still fit. if walked + jpeg.len() > CR3_MAX_WALK { continue; diff --git a/crates/raw/src/preview/tests.rs b/crates/raw/src/preview/tests.rs index 2ee098414..f526e868b 100644 --- a/crates/raw/src/preview/tests.rs +++ b/crates/raw/src/preview/tests.rs @@ -199,6 +199,20 @@ fn picks_largest_dct_jpeg() { } } +/// #2569: `PRVW` box and JPEG lengths near `u32::MAX` (which wrap `usize` on wasm32) find no preview, never panic. +#[test] +fn cr3_preview_box_huge_lengths_are_rejected() { + let j = fake_jpeg(300); + for (size, len) in [(u32::MAX, u32::MAX), (u32::MAX, u32::MAX - 23), (24 + j.len() as u32, u32::MAX - 3)] { + let mut cr3 = b"ftypcrx crx isom".to_vec(); + cr3.extend_from_slice(&size.to_be_bytes()); + cr3.extend_from_slice(b"PRVWT8"); + cr3.extend_from_slice(&len.to_be_bytes()); + cr3.extend_from_slice(&j); + assert_eq!(cr3_preview_boxes(&cr3), None, "size {size:#x}, len {len:#x}"); + } +} + /// A JPEG like [`fake_jpeg`] whose frame header says `w` x `h` with `nc` components. fn sized_jpeg(w: u16, h: u16, nc: u8, n: usize) -> Vec { let mut j = vec![0xff, 0xd8, 0xff, 0xc0, 0x00, 8 + 3 * nc, 8];