Skip to content

jpegrutils: check EXIF offsets before adding them in getExifAppleHeadroom - #499

Open
alepacheco wants to merge 2 commits into
google:mainfrom
alepacheco:fix/exif-tiff-offsets
Open

alepacheco wants to merge 2 commits into
google:mainfrom
alepacheco:fix/exif-tiff-offsets

Conversation

@alepacheco

Copy link
Copy Markdown

getExifAppleHeadroom() adds offsets read from the EXIF data to the position of the TIFF header (or of the Apple Maker Notes) and only then compares the sum with the buffer size. On 32-bit targets size_t is 32 bits, so the sum can wrap: the Maker Notes check tiffHeaderOffset + makerNotesOffset + appleMakerNotesHeaderSize <= size passes for an offset close to UINT32_MAX, and the memcmp() that follows reads before the start of the EXIF buffer. The function runs from uhdr_decode() when the gain map has Apple XMP without HDRGainMapHeadroom (and no ISO 21496-1 metadata), on the EXIF block of the primary image, so the offsets come straight from the file.

This PR makes the bounds check impossible to get wrong in that function rather than patching the one comparison:

  • resolveOffset(base, relOffset, size, &offset) turns an offset from the EXIF data into a buffer position and fails when it would lie past the end, without forming the sum first. Every offset the parser follows goes through it: the IFD and Exif sub-IFD offsets, the Maker Notes offset and the tag 33/48 value offsets.
  • matchBytes() compares the Maker Notes header only when it fits in the buffer.
  • The separate underflow and overflow guards added to the tag 33/48 path in 0f0c6ed are replaced by the same helper.

Valid EXIF resolves to exactly the same positions as before, and in-bounds input that was accepted before is still accepted.

Tests: JpegRTest.readAppleHeadroomFromExif builds an EXIF block with an Exif sub-IFD and Apple Maker Notes holding tags 33 and 48 and checks the headroom read from it; JpegRTest.rejectAppleExifOffsetsPastEnd sets each of the five offsets in turn to values at and past the end of the data, including ones that wrap when added to the TIFF header position, and expects the parse to fail. In a 32-bit AddressSanitizer build the original code reports a heap-buffer-overflow in getExifAppleHeadroom() for the wrapping Maker Notes offsets; with the change the full unit test suite passes.

…room

getExifAppleHeadroom adds offsets read from the EXIF data to the position
of the TIFF header or of the Apple Maker Notes and compares the sum with
the buffer size afterwards. On 32-bit targets the sum can wrap, so the
Maker Notes check passes for an offset close to UINT32_MAX and memcmp
reads before the start of the EXIF buffer.

Resolve every offset taken from the EXIF data through one helper that
rejects positions past the end of the buffer without forming the sum
first, and compare the Maker Notes header through a bounds-checked
helper. This replaces the separate underflow and overflow guards of the
tag 33/48 path. Valid EXIF parses to the same positions as before.

Add tests for reading the headroom from Apple Maker Notes and for
rejecting out-of-range IFD, Maker Notes and value offsets.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces helper functions resolveOffset and matchBytes in jpegrutils.cpp to safely parse EXIF data and prevent out-of-bounds reads or integer overflows, along with corresponding unit tests in jpegr_test.cpp. The review feedback recommends adding a defensive check to prevent a potential integer overflow when calculating offsetToIfd from tagData and appleMakerNotesHeaderSize.

Comment thread lib/src/jpegrutils.cpp
if (resolveOffset(tiffHeaderOffset, tagData, size, &makerNotesOffset) &&
matchBytes(exif, size, makerNotesOffset, appleMakerNotesHeader,
appleMakerNotesHeaderSize)) {
offsetToIfd = tagData + (uint32_t)appleMakerNotesHeaderSize;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

Although size is currently bounded by INT_MAX (via exif_size in getMetadataFromXMP), if getExifAppleHeadroom is ever called with a larger size (e.g., on 64-bit systems), tagData + appleMakerNotesHeaderSize can overflow uint32_t if tagData is close to UINT32_MAX. This would cause offsetToIfd to wrap around to a small value, potentially leading to incorrect parsing or out-of-bounds reads in subsequent iterations. Adding a check to prevent this overflow is recommended for robust defensive programming.

Suggested change
offsetToIfd = tagData + (uint32_t)appleMakerNotesHeaderSize;
if (tagData > UINT32_MAX - appleMakerNotesHeaderSize) return false;
offsetToIfd = tagData + (uint32_t)appleMakerNotesHeaderSize;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 35e5ef3: a Maker Notes offset that would wrap the 32-bit IFD offset is now not treated as Apple Maker Notes.

The offset of the first IFD after the Maker Notes header is stored in a
uint32_t. Reject a Maker Notes offset that would wrap it, instead of
relying on the EXIF size staying below 4 GiB.
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.

1 participant