From f76af0382f65b24d1d0b02311b03b7d4208e602a Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 22 Oct 2022 01:54:18 -0700 Subject: [PATCH] Fix EXIF bugs where corrupted exif blocks could overrun memory In one case, we actually had a check for this, but an assignment to an int made the nonsensical offset appear negative, and we only tested whether the necessary offset it was bigger than the buffer size. Keeping it as (unsigned) size_t makes the test work as intended. In another case, there were several places where we never checked that we were staying within the exif block, and here we address this by changing the utility decode_ifd so instead of passing it a pointer to the ifd, it passes the offset (the pointer turned out to always be inside the buffer) so it can check the extent for subsequent accesses. Also some fixes related to squashing undefined behavior sanitizer cases. --- src/libOpenImageIO/exif.cpp | 58 +++++++++++------- src/libOpenImageIO/exif.h | 6 +- src/psd.imageio/psdinput.cpp | 5 +- testsuite/jpeg-corrupt/ref/out-alt.txt | 10 +++ testsuite/jpeg-corrupt/ref/out.txt | 10 +++ testsuite/jpeg-corrupt/run.py | 10 ++- .../jpeg-corrupt/src/corrupt-exif-1626.jpg | Bin 0 -> 19623 bytes testsuite/psd/ref/out.txt | 4 ++ testsuite/psd/run.py | 5 ++ testsuite/psd/src/crash-psd-exif-1632.psd | Bin 0 -> 25139 bytes testsuite/runtest.py | 14 ++++- 11 files changed, 89 insertions(+), 33 deletions(-) create mode 100644 testsuite/jpeg-corrupt/src/corrupt-exif-1626.jpg create mode 100644 testsuite/psd/src/crash-psd-exif-1632.psd diff --git a/src/libOpenImageIO/exif.cpp b/src/libOpenImageIO/exif.cpp index 433fa0530b..0ca2304d32 100644 --- a/src/libOpenImageIO/exif.cpp +++ b/src/libOpenImageIO/exif.cpp @@ -157,7 +157,7 @@ tiff_data_size(TIFFDataType tifftype) // Sizes of TIFFDataType members static size_t sizes[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4 }; const int num_data_sizes = sizeof(sizes) / sizeof(*sizes); - int dir_index = (int)tifftype; + int dir_index = bitcast(tifftype); if (dir_index < 0 || dir_index >= num_data_sizes) { // Inform caller about corrupted entry. return -1; @@ -360,9 +360,8 @@ makernote_handler(const TagInfo& /*taginfo*/, const TIFFDirEntry& dir, if (spec.get_string_attribute("Make") == "Canon") { std::vector ifdoffsets { 0 }; std::set offsets_seen; - decode_ifd((unsigned char*)buf.data() + dir.tdir_offset, buf, spec, - pvt::canon_maker_tagmap_ref(), offsets_seen, swapendian, - offset_adjustment); + decode_ifd(buf, dir.tdir_offset, spec, pvt::canon_maker_tagmap_ref(), + offsets_seen, swapendian, offset_adjustment); } else { // Maybe we just haven't parsed the Maker metadata yet? // Allow a second try later by just stashing the maker note offset. @@ -668,8 +667,10 @@ add_exif_item_to_spec(ImageSpec& spec, const char* name, float* f = OIIO_ALLOCA(float, n); for (int i = 0; i < n; ++i) { unsigned int num, den; - num = ((const unsigned int*)dataptr)[2 * i + 0]; - den = ((const unsigned int*)dataptr)[2 * i + 1]; //NOSONAR + memcpy(&num, dataptr + (2 * i) * sizeof(unsigned int), + sizeof(unsigned int)); + memcpy(&den, dataptr + (2 * i + 1) * sizeof(unsigned int), + sizeof(unsigned int)); //NOSONAR if (swab) { swap_endian(&num); swap_endian(&den); @@ -687,8 +688,9 @@ add_exif_item_to_spec(ImageSpec& spec, const char* name, float* f = OIIO_ALLOCA(float, n); for (int i = 0; i < n; ++i) { int num, den; - num = ((const int*)dataptr)[2 * i + 0]; - den = ((const int*)dataptr)[2 * i + 1]; //NOSONAR + memcpy(&num, dataptr + (2 * i) * sizeof(int), sizeof(int)); + memcpy(&den, dataptr + (2 * i + 1) * sizeof(int), + sizeof(int)); //NOSONAR if (swab) { swap_endian(&num); swap_endian(&den); @@ -767,7 +769,9 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan buf, // Make a copy of the pointed-to TIFF directory, swab the components // if necessary. - TIFFDirEntry dir = *dirp; + TIFFDirEntry dir; + memcpy(&dir, dirp, sizeof(TIFFDirEntry)); + unsigned int unswapped_tdir_offset = dir.tdir_offset; if (swab) { swap_endian(&dir.tdir_tag); swap_endian(&dir.tdir_type); @@ -785,7 +789,7 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan buf, if (dir.tdir_tag == TIFFTAG_EXIFIFD || dir.tdir_tag == TIFFTAG_GPSIFD) { // Special case: It's a pointer to a private EXIF directory. // Handle the whole thing recursively. - unsigned int offset = dirp->tdir_offset; // int stored in offset itself + auto offset = unswapped_tdir_offset; // int stored in offset itself if (swab) swap_endian(&offset); if (offset >= size_t(buf.size())) { @@ -840,7 +844,7 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan buf, } else if (dir.tdir_tag == TIFFTAG_INTEROPERABILITYIFD) { // Special case: It's a pointer to a private EXIF directory. // Handle the whole thing recursively. - unsigned int offset = dirp->tdir_offset; // int stored in offset itself + auto offset = unswapped_tdir_offset; // int stored in offset itself if (swab) swap_endian(&offset); if (offset >= size_t(buf.size())) { @@ -1004,23 +1008,32 @@ encode_exif_entry(const ParamValue& p, int tag, std::vector& dirs, -// Decode a raw Exif data block and save all the metadata in an -// ImageSpec. Return true if all is ok, false if the exif block was +// Decode a raw Exif data block and save all the metadata in an ImageSpec. The +// data is all inside buf. The ifd itself is at the position `ifd_offset` +// within the buf. Return true if all is ok, false if the exif block was // somehow malformed. -void -pvt::decode_ifd(const unsigned char* ifd, cspan buf, ImageSpec& spec, +bool +pvt::decode_ifd(cspan buf, size_t ifd_offset, ImageSpec& spec, const TagMap& tag_map, std::set& ifd_offsets_seen, bool swab, int offset_adjustment) { // Read the directory that the header pointed to. It should contain // some number of directory entries containing tags to process. - unsigned short ndirs = *(const unsigned short*)ifd; + if (ifd_offset + 2 > std::size(buf)) { + return false; // asking us to read beyond the buffer + } + auto ifd = buf.data() + ifd_offset; + unsigned short ndirs = *(const unsigned short*)(buf.data() + ifd_offset); if (swab) swap_endian(&ndirs); + if (ifd_offset + 2 + ndirs * sizeof(TIFFDirEntry) > std::size(buf)) { + return false; // asking us to read beyond the buffer + } for (int d = 0; d < ndirs; ++d) read_exif_tag(spec, (const TIFFDirEntry*)(ifd + 2 + d * sizeof(TIFFDirEntry)), buf, swab, offset_adjustment, ifd_offsets_seen, tag_map); + return true; } @@ -1140,12 +1153,12 @@ decode_exif(cspan exif, ImageSpec& spec) if (swab) swap_endian(&head.tiff_diroff); - const unsigned char* ifd = ((const unsigned char*)exif.data() - + head.tiff_diroff); // keep track of IFD offsets we've already seen to avoid infinite // recursion if there are circular references. std::set ifd_offsets_seen; - decode_ifd(ifd, exif, spec, exif_tagmap_ref(), ifd_offsets_seen, swab); + if (!decode_ifd(exif, head.tiff_diroff, spec, exif_tagmap_ref(), + ifd_offsets_seen, swab)) + return false; // A few tidbits to look for ParamValue* p; @@ -1172,9 +1185,10 @@ decode_exif(cspan exif, ImageSpec& spec) int makernote_offset = spec.get_int_attribute("oiio:MakerNoteOffset"); if (makernote_offset > 0) { if (Strutil::iequals(spec.get_string_attribute("Make"), "Canon")) { - decode_ifd((unsigned char*)exif.data() + makernote_offset, exif, - spec, pvt::canon_maker_tagmap_ref(), ifd_offsets_seen, - swab); + if (!decode_ifd(exif, makernote_offset, spec, + pvt::canon_maker_tagmap_ref(), ifd_offsets_seen, + swab)) + return false; } // Now we can erase the attrib we used to pass the message about // the maker note offset. diff --git a/src/libOpenImageIO/exif.h b/src/libOpenImageIO/exif.h index 4dbf19d20c..7cbb16a3dc 100644 --- a/src/libOpenImageIO/exif.h +++ b/src/libOpenImageIO/exif.h @@ -31,12 +31,12 @@ namespace pvt { inline const void* dataptr(const TIFFDirEntry& td, cspan data, int offset_adjustment) { - int len = tiff_data_size(td); + size_t len = tiff_data_size(td); if (len <= 4) return (const char*)&td.tdir_offset; else { int offset = td.tdir_offset + offset_adjustment; - if (offset < 0 || offset + len > (int)data.size()) + if (offset < 0 || size_t(offset) + len > std::size(data)) return nullptr; // out of bounds! return (const char*)data.data() + offset; } @@ -113,7 +113,7 @@ void append_tiff_dir_entry (std::vector &dirs, size_t offset_override = 0, OIIO::endian endianreq = OIIO::endian::native); -void decode_ifd (const unsigned char *ifd, cspan buf, +bool decode_ifd (cspan buf, size_t ifd_offset, ImageSpec &spec, const TagMap& tag_map, std::set& ifd_offsets_seen, bool swab=false, int offset_adjustment=0); diff --git a/src/psd.imageio/psdinput.cpp b/src/psd.imageio/psdinput.cpp index e3c2cf3ed1..da5d962c9c 100644 --- a/src/psd.imageio/psdinput.cpp +++ b/src/psd.imageio/psdinput.cpp @@ -309,8 +309,9 @@ class PSDInput final : public ImageInput { // Convert from photoshop native alpha to // associated/premultiplied template - void removeBackground(T* data, int size, int nchannels, int alpha_channel, - double* background) + OIIO_NO_SANITIZE_UNDEFINED void + removeBackground(T* data, int size, int nchannels, int alpha_channel, + double* background) { // RGB = CompRGB - (1 - alpha) * Background; double scale = std::numeric_limits::is_integer diff --git a/testsuite/jpeg-corrupt/ref/out-alt.txt b/testsuite/jpeg-corrupt/ref/out-alt.txt index cf3405510d..0c66efab4a 100644 --- a/testsuite/jpeg-corrupt/ref/out-alt.txt +++ b/testsuite/jpeg-corrupt/ref/out-alt.txt @@ -8,3 +8,13 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg YResolution: 300 jpeg:subsampling: "4:2:0" oiio:ColorSpace: "sRGB" +Reading src/corrupt-exif-1626.jpg +src/corrupt-exif-1626.jpg : 256 x 256, 3 channel, uint8 jpeg + SHA-1: 6CBB7DB602A67DB300AB28842F20F6DCA02B82B1 + channel list: R, G, B + Orientation: 1 (normal) + ResolutionUnit: "in" + XResolution: 300 + YResolution: 300 + jpeg:subsampling: "4:2:0" + oiio:ColorSpace: "sRGB" diff --git a/testsuite/jpeg-corrupt/ref/out.txt b/testsuite/jpeg-corrupt/ref/out.txt index dc1907a09c..b476cb427b 100644 --- a/testsuite/jpeg-corrupt/ref/out.txt +++ b/testsuite/jpeg-corrupt/ref/out.txt @@ -8,3 +8,13 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg YResolution: 300 jpeg:subsampling: "4:2:0" oiio:ColorSpace: "sRGB" +Reading src/corrupt-exif-1626.jpg +src/corrupt-exif-1626.jpg : 256 x 256, 3 channel, uint8 jpeg + SHA-1: 6CBB7DB602A67DB300AB28842F20F6DCA02B82B1 + channel list: R, G, B + Orientation: 1 (normal) + ResolutionUnit: "in" + XResolution: 300 + YResolution: 300 + jpeg:subsampling: "4:2:0" + oiio:ColorSpace: "sRGB" diff --git a/testsuite/jpeg-corrupt/run.py b/testsuite/jpeg-corrupt/run.py index c24aa8d914..8379a022c8 100755 --- a/testsuite/jpeg-corrupt/run.py +++ b/testsuite/jpeg-corrupt/run.py @@ -1,10 +1,14 @@ #!/usr/bin/env python +failureok = 1 +redirect = ' >> out.txt 2>&1 ' + # This file has a corrupted Exif block in the metadata. It used to # crash on some platforms, on others would be caught by address sanitizer. # Fixed by #1635. This test serves to guard against regressions. command += info_command ("src/corrupt-exif.jpg", safematch=True) -# Checking the error output is important for this test -outputs = [ "out.txt" ] -failureok = 1 +# This file has a corrupted Exif block that makes it look like one item has a +# nonsensical length, that before being fixed, caused a buffer overrun. +command += info_command ("src/corrupt-exif-1626.jpg", safematch=True) + diff --git a/testsuite/jpeg-corrupt/src/corrupt-exif-1626.jpg b/testsuite/jpeg-corrupt/src/corrupt-exif-1626.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d7b84dea1aef4369c7c2c4f73278a070dc77b467 GIT binary patch literal 19623 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 VC?rx0P-&U?Hm<|-f2{Xs{sX)|lOq5C literal 0 HcmV?d00001 diff --git a/testsuite/psd/ref/out.txt b/testsuite/psd/ref/out.txt index cb64c83ba3..b5bebe741b 100644 --- a/testsuite/psd/ref/out.txt +++ b/testsuite/psd/ref/out.txt @@ -1064,3 +1064,7 @@ src/layer-mask.psd : 10 x 10, 4 channel, uint8 psd stEvt:instanceID: "xmp.iid:a64763c8-be7b-ff48-b857-0f1ee8e5da2b; xmp.iid:9847e4c5-ca7e-fa42-9c7f-5fe6373d31de; xmp.iid:ddf40b95-b12c-744e-a1a9-5e7724fe4ca9" stEvt:softwareAgent: "Adobe Photoshop CC 2017 (Windows)" stEvt:when: "2017-07-13T10:26:10+09:00; 2017-07-13T10:32:41+09:00; 2017-07-13T11:42:54+09:00" +oiiotool ERROR: read : Failed to decode Exif data +failed to open "src/crash-psd-exif-1632.psd": failed load_resources +Full command line was: +> oiiotool --info -v -a --hash src/crash-psd-exif-1632.psd diff --git a/testsuite/psd/run.py b/testsuite/psd/run.py index c7778746b4..dea940ae53 100755 --- a/testsuite/psd/run.py +++ b/testsuite/psd/run.py @@ -1,5 +1,7 @@ #!/usr/bin/env python +redirect = ' >> out.txt 2>&1 ' + files = [ "psd_123.psd", "psd_123_nomaxcompat.psd", "psd_bitmap.psd", "psd_indexed_trans.psd", "psd_rgb_8.psd", "psd_rgb_16.psd", "psd_rgb_32.psd", "psd_rgba_8.psd" ] @@ -8,3 +10,6 @@ command += info_command ("src/different-mask-size.psd") command += info_command ("src/layer-mask.psd") + +# This file has a corrupted Exif block +command += info_command ("src/crash-psd-exif-1632.psd", failureok = 1) diff --git a/testsuite/psd/src/crash-psd-exif-1632.psd b/testsuite/psd/src/crash-psd-exif-1632.psd new file mode 100644 index 0000000000000000000000000000000000000000..ef0679b3bf307481ae3acbd01cc424cfce7118e2 GIT binary patch literal 25139 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 literal 0 HcmV?d00001 diff --git a/testsuite/runtest.py b/testsuite/runtest.py index a7b26d6c1c..b6b2794a34 100755 --- a/testsuite/runtest.py +++ b/testsuite/runtest.py @@ -215,7 +215,8 @@ def oiio_app (app): # the file "out.txt". If 'safematch' is nonzero, it will exclude printing # of fields that tend to change from run to run or release to release. def info_command (file, extraargs="", safematch=False, hash=True, - verbose=True, info_program="oiiotool") : + verbose=True, silent=False, concat=True, failureok=False, + info_program="oiiotool") : args = "" if info_program == "oiiotool" : args += " --info" @@ -225,8 +226,15 @@ def info_command (file, extraargs="", safematch=False, hash=True, args += " --no-metamatch \"DateTime|Software|OriginatingProgram|ImageHistory\"" if hash : args += " --hash" - return (oiio_app(info_program) + args + " " + extraargs - + " " + make_relpath(file,tmpdir) + redirect + ";\n") + cmd = (oiio_app(info_program) + args + " " + extraargs + + " " + make_relpath(file,tmpdir)) + if not silent : + cmd += redirect + if failureok : + cmd += " || true " + if concat: + cmd += " ;\n" + return cmd # Construct a command that will compare two images, appending output to