Skip to content

fix: prevent panics in EXIF/TIFF tag parsing on truncated input - #9

Merged
vbasky merged 1 commit into
vbasky:mainfrom
chiliec:fix/exif-tag-parser-panics-on-truncated-input
Sep 8, 2026
Merged

vbasky merged 1 commit into
vbasky:mainfrom
chiliec:fix/exif-tag-parser-panics-on-truncated-input

Conversation

@chiliec

@chiliec chiliec commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What

parse_tags runs on every analyzed file (Metadata::from_bytes / from_path call it after detection), so a panic there crashes the top-level API on malformed or truncated media. Two out-of-bounds panics, both reachable from any input:

parse_exif — short partial window

let head = match fa.peek_raw_at(0, 12) { Some(b) => b, None => return false };
let raw_tiff = &head[0..2] == b"II" || &head[0..2] == b"MM";  // panics if head.len() < 2

peek_raw_at returns a partial window, so head can be shorter than the 12 bytes requested. A 1-byte input panics:

range end index 2 out of range for slice of length 1

The existing buf.len() < 12 guard is checked after this head[0..2] read.

read_tiff_u16 / read_tiff_u32 — unchecked indexing

fn read_tiff_u16(data: &[u8], off: usize, bo: &str) -> u16 {
    u16::from_be_bytes([data[off], data[off + 1]])   // no bounds check
}

These index data[off..off+N] with no bounds check, unlike the sibling read_tiff_f32 / read_tiff_f64 which already guard off + N > data.len(). A truncated TIFF/EXIF IFD panics:

index out of bounds: the len is N but the index is N

Fix

  • Guard head.len() < 12 in parse_exif before indexing head[0..2].
  • Add off + 2 > data.len() / off + 4 > data.len() guards to read_tiff_u16 / read_tiff_u32, returning 0 when out of range — mirroring the existing float readers.

Testing

Added tiff_int_readers_reject_out_of_range and parse_exif_handles_short_partial_window. Verified RED→GREEN — reverting each guard reproduces the exact panic above; with the fix the parsers return normally. Existing tag-crate tests still pass (77), cargo fmt clean, no new clippy warnings.

How it was found

By feeding format-signature seeds (TIFF/EXIF, RIFF, MP4, FLAC, EBML, …) mutated with truncation, byte-flips and length extension into Metadata::from_bytes under catch_unwind (~480k inputs). These were the only panics; both trace back to the tag parser.

parse_tags runs on every analyzed file, so these crash the top-level
Metadata::from_bytes / from_path on malformed or truncated media:

- parse_exif reads head[0..2] to check the TIFF byte-order mark, but
  peek_raw_at returns a *partial* window that can be shorter than the 12
  bytes requested. A 1-byte input panics with
  'range end index 2 out of range for slice of length 1'.

- read_tiff_u16 / read_tiff_u32 index data[off..off+N] with no bounds
  check, unlike the sibling read_tiff_f32 / read_tiff_f64 which already
  guard 'off + N > data.len()'. A truncated TIFF/EXIF IFD panics with
  'index out of bounds: the len is N but the index is N'.

Guard head.len() < 12 before indexing, and add the same bounds check to
the integer readers (returning 0 when out of range, matching the float
readers). Adds regression tests.

Found by feeding mutated/truncated format-signature inputs to
Metadata::from_bytes and asserting no parser panics.
@vbasky

vbasky commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Thanks @chiliec for this fix — great find and write-up. I reviewed and verified it end-to-end: I reproduced all three panics on main (parse_exif on a 1-byte input, and out-of-range read_tiff_u16/read_tiff_u32), and confirmed the guards fix them. The two regression tests are solid and the full tag-crate suite (77) passes. Rebasing and merging — thanks again!

@vbasky
vbasky merged commit 9101608 into vbasky:main Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants