ROB: tolerate malformed /Decode array in image extraction#3899
ROB: tolerate malformed /Decode array in image extraction#3899metsw24-max wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3899 +/- ##
==========================================
+ Coverage 97.85% 97.86% +0.01%
==========================================
Files 57 57
Lines 10702 10741 +39
Branches 2001 2007 +6
==========================================
+ Hits 10472 10512 +40
Misses 127 127
+ Partials 103 102 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| ): | ||
| decode = [1.0, 0.0] * len(img.getbands()) | ||
| if decode is not None and ( | ||
| not isinstance(decode, (list, tuple)) or len(decode) % 2 != 0 |
There was a problem hiding this comment.
When will this be a tuple?
There was a problem hiding this comment.
Never in practice. It only ever comes from the image dictionary, where it parses as an ArrayObject (a list subclass), or from the literal lists built above. Dropped the tuple branch and kept it to list.
| # array would otherwise read past its end at decode[i + 1]; drop it and | ||
| # render the image without the remap. | ||
| logger_warning( | ||
| "Ignoring malformed /Decode array; expected an even number of values.", |
There was a problem hiding this comment.
| "Ignoring malformed /Decode array; expected an even number of values.", | |
| "Ignoring malformed /Decode array %(decode)s; expected an even number of values.", |
There was a problem hiding this comment.
Done, applied and passing decode through so the value lands in the message.
| @pytest.mark.parametrize( | ||
| "decode", | ||
| [ | ||
| ArrayObject([NumberObject(1), NumberObject(0), NumberObject(1)]), |
There was a problem hiding this comment.
Consider setting meaningful IDs.
There was a problem hiding this comment.
Added ids: odd-length-array, single-value-array, not-an-array.
| x_object = StreamObject() | ||
| x_object[NameObject(ImageAttributes.WIDTH)] = NumberObject(2) | ||
| x_object[NameObject(ImageAttributes.HEIGHT)] = NumberObject(2) | ||
| x_object[NameObject("/BitsPerComponent")] = NumberObject(8) |
There was a problem hiding this comment.
| x_object[NameObject("/BitsPerComponent")] = NumberObject(8) | |
| x_object[NameObject(ImageAttributes.BITS_PER_COMPONENT)] = NumberObject(8) |
| x_object[NameObject(ImageAttributes.WIDTH)] = NumberObject(2) | ||
| x_object[NameObject(ImageAttributes.HEIGHT)] = NumberObject(2) | ||
| x_object[NameObject("/BitsPerComponent")] = NumberObject(8) | ||
| x_object[NameObject(ImageAttributes.COLOR_SPACE)] = NameObject("/DeviceGray") |
There was a problem hiding this comment.
| x_object[NameObject(ImageAttributes.COLOR_SPACE)] = NameObject("/DeviceGray") | |
| x_object[NameObject(ImageAttributes.COLOR_SPACE)] = NameObject(ColorSpace.DEVICE_GRAY) |
There was a problem hiding this comment.
Done, using ColorSpaces.DEVICE_GRAY.
|
gentle ping |
|
See #3900. |
When extracting an image,
_apply_decodereads the image XObject's/Decodeentry and walks it in[min, max]pairs to build the remap table, takingdecode[i + 1]at each step. That value comes straight from the PDF and is only meaningful as an even-length array of numbers, but the length was never checked, so an object whose/Decodecarries an odd number of entries reads past the end of the list and a non-array/Decodehas no length at all. Both escape throughreader.pages[i].images, which is the public path that reaches_xobj_to_imageand then_apply_decode, so a crafted file aborts image extraction rather than degrading. I came across it on a fuzzed image whose/Decodeheld three numbers, where thedecode[i + 1]access went out of range. The guard rejects a/Decodethat is not a list or tuple of even length, warns, and falls back to rendering the image without the remap, which keeps a malformed array a best-effort image instead of an exception. I kept the change inside_apply_decodeso valid even-length arrays are unaffected, and added a regression test alongside the existing decode cases.