Repository navigation
jpegrutils: check EXIF offsets before adding them in getExifAppleHeadroom - #499
alepacheco wants to merge 2 commits into
Conversation
…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.
There was a problem hiding this comment.
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.
| if (resolveOffset(tiffHeaderOffset, tagData, size, &makerNotesOffset) && | ||
| matchBytes(exif, size, makerNotesOffset, appleMakerNotesHeader, | ||
| appleMakerNotesHeaderSize)) { | ||
| offsetToIfd = tagData + (uint32_t)appleMakerNotesHeaderSize; |
There was a problem hiding this comment.
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.
| offsetToIfd = tagData + (uint32_t)appleMakerNotesHeaderSize; | |
| if (tagData > UINT32_MAX - appleMakerNotesHeaderSize) return false; | |
| offsetToIfd = tagData + (uint32_t)appleMakerNotesHeaderSize; |
There was a problem hiding this comment.
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.
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 targetssize_tis 32 bits, so the sum can wrap: the Maker Notes checktiffHeaderOffset + makerNotesOffset + appleMakerNotesHeaderSize <= sizepasses for an offset close toUINT32_MAX, and thememcmp()that follows reads before the start of the EXIF buffer. The function runs fromuhdr_decode()when the gain map has Apple XMP withoutHDRGainMapHeadroom(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.Valid EXIF resolves to exactly the same positions as before, and in-bounds input that was accepted before is still accepted.
Tests:
JpegRTest.readAppleHeadroomFromExifbuilds 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.rejectAppleExifOffsetsPastEndsets 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 ingetExifAppleHeadroom()for the wrapping Maker Notes offsets; with the change the full unit test suite passes.