From 0a2ea3a6a018201ec36471eb3dbdbbd750152fa8 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 12 Feb 2023 21:30:36 -0800 Subject: [PATCH] fix: race condition in TIFF reader (#3772) * test: Add a corrupted test file * fix(IC): upon tile read error, mark the file as broken The idea is to keep fruitlessly reading from a corrupted file. * fix: race condition in TIFF reader TIFFInput::read_native_tiles had a very subtle race condition. There is a section where it has a task_set of decompression tasks that are sent to the thread queue. When the method ends, the task_set goes out of scope, and its destructor will wait for all the remaining tasks to finish. All fine, right? Except that other variables in the function include unique_ptr's with allocated memory that the tasks need. So what can happen is that if the compiler generates the code such that the destructor of the unique_ptr happens before the destructor of the task_set, the memory that the unique_ptr held may be deallocated before some of the tasks still running in threads have finished. In practice, we seem only to hit this race condition for particular cases involving corrupted files, because the early `return` seems quite likely to run those destructors before all the threads are done, but it was possible to encounter it for valid files as well, if the timing is just right. Shocking that we never had it reported. The solution is just to have an explitit tasks.wait(), which will block until all the tasks have finished before hitting the actual end of the method and letting the other destructors to run. And for the "early return" case, change it to a break so that we go through the wait call at the end. I believe that this fix addresses TALOS-2023-1709 / CVE-2023-24472. --- src/libtexture/imagecache.cpp | 1 + src/tiff.imageio/tiffinput.cpp | 9 ++++--- testsuite/tiff-misc/ref/out-libtiff403.txt | 4 ++- testsuite/tiff-misc/ref/out-libtiff410.txt | 29 +++++++++++++++++++++ testsuite/tiff-misc/ref/out.txt | 4 ++- testsuite/tiff-misc/run.py | 3 ++- testsuite/tiff-misc/src/crash-1709.tif | Bin 0 -> 36987 bytes 7 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 testsuite/tiff-misc/ref/out-libtiff410.txt create mode 100644 testsuite/tiff-misc/src/crash-1709.tif diff --git a/src/libtexture/imagecache.cpp b/src/libtexture/imagecache.cpp index da8380fa9a..f4937a26b4 100644 --- a/src/libtexture/imagecache.cpp +++ b/src/libtexture/imagecache.cpp @@ -840,6 +840,7 @@ ImageCacheFile::read_tile(ImageCachePerThreadInfo* thread_info, int subimage, } } if (!ok) { + m_broken = true; std::string err = inp->geterror(); if (errors_should_issue()) { imagecache().error("{}", diff --git a/src/tiff.imageio/tiffinput.cpp b/src/tiff.imageio/tiffinput.cpp index 62bf5398d1..28d2a77590 100644 --- a/src/tiff.imageio/tiffinput.cpp +++ b/src/tiff.imageio/tiffinput.cpp @@ -2157,8 +2157,9 @@ TIFFInput::read_native_tiles(int subimage, int miplevel, int xbegin, int xend, // xbegin, xend, ybegin, yend, zbegin, zend); size_t tileidx = 0; for (int z = zbegin; z < zend; z += m_spec.tile_depth) { - for (int y = ybegin; y < yend; y += m_spec.tile_height) { - for (int x = xbegin; x < xend; x += m_spec.tile_width, ++tileidx) { + for (int y = ybegin; ok && y < yend; y += m_spec.tile_height) { + for (int x = xbegin; ok && x < xend; + x += m_spec.tile_width, ++tileidx) { char* cbuf = compressed_scratch.get() + tileidx * cbound; char* ubuf = scratch.get() + tileidx * tile_bytes; auto csize = TIFFReadRawTile(m_tif, tile_index(x, y, z), cbuf, @@ -2168,7 +2169,8 @@ TIFFInput::read_native_tiles(int subimage, int miplevel, int xbegin, int xend, errorf( "TIFFReadRawTile failed reading tile x=%d,y=%d,z=%d: %s", x, y, z, err.size() ? err.c_str() : "unknown error"); - return false; + ok = false; + break; } // Push the rest of the work onto the thread pool queue auto out = this; @@ -2193,6 +2195,7 @@ TIFFInput::read_native_tiles(int subimage, int miplevel, int xbegin, int xend, } } } + tasks.wait(); return ok; } diff --git a/testsuite/tiff-misc/ref/out-libtiff403.txt b/testsuite/tiff-misc/ref/out-libtiff403.txt index 206e2e2766..cf9932e6ab 100644 --- a/testsuite/tiff-misc/ref/out-libtiff403.txt +++ b/testsuite/tiff-misc/ref/out-libtiff403.txt @@ -22,6 +22,8 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info -v src/crash-1633.tif oiiotool ERROR: read : File does not exist: "src/crash-1643.tif" Full command line was: -> oiiotool --oiioattrib try_all_readers 0 --info -v src/crash-1643.tif +> oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr +iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : + Decoding error at scanline 0, incorrect header check Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/ref/out-libtiff410.txt b/testsuite/tiff-misc/ref/out-libtiff410.txt new file mode 100644 index 0000000000..477c237db6 --- /dev/null +++ b/testsuite/tiff-misc/ref/out-libtiff410.txt @@ -0,0 +1,29 @@ +Reading src/separate.tif +src/separate.tif : 128 x 128, 3 channel, uint8 tiff + SHA-1: 486088DECAE711C444FDCAB009C378F7783AD9C5 + channel list: R, G, B + compression: "zip" + DateTime: "2020:10:25 15:32:04" + Orientation: 1 (normal) + planarconfig: "separate" + Software: "OpenImageIO 2.3.0dev : oiiotool --pattern fill:topleft=0,0,0:topright=1,0,0:bottomleft=0,1,0:bottomright=1,1,1 128x128 3 --planarconfig separate -scanline -attrib tiff:rowsperstrip 17 -d uint8 -o separate.tif" + oiio:BitsPerSample: 8 + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 2 + tiff:RowsPerStrip: 7 +Comparing "src/separate.tif" and "separate.tif" +PASS +oiiotool ERROR: read : No support for data format of "src/corrupt1.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info -v src/corrupt1.tif +oiiotool ERROR: read : File does not exist: "src/crash-1633.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info -v src/crash-1633.tif +oiiotool ERROR: read : File does not exist: "src/crash-1643.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr +iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : + Decoding error at scanline 0, incorrect header check +Comparing "check1.tif" and "ref/check1.tif" +PASS diff --git a/testsuite/tiff-misc/ref/out.txt b/testsuite/tiff-misc/ref/out.txt index 1cdf173910..9c6ee4260d 100644 --- a/testsuite/tiff-misc/ref/out.txt +++ b/testsuite/tiff-misc/ref/out.txt @@ -22,6 +22,8 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info -v src/crash-1633.tif oiiotool ERROR: read : File does not exist: "src/crash-1643.tif" Full command line was: -> oiiotool --oiioattrib try_all_readers 0 --info -v src/crash-1643.tif +> oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr +iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : + Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/run.py b/testsuite/tiff-misc/run.py index 6e3262d510..cb14f8845b 100755 --- a/testsuite/tiff-misc/run.py +++ b/testsuite/tiff-misc/run.py @@ -17,6 +17,7 @@ # Test bugs we had until OIIO 2.4 for these corrupt file command += oiiotool ("--oiioattrib try_all_readers 0 --info -v src/corrupt1.tif", failureok = True) command += oiiotool ("--oiioattrib try_all_readers 0 --info -v src/crash-1633.tif", failureok = True) -command += oiiotool ("--oiioattrib try_all_readers 0 --info -v src/crash-1643.tif", failureok = True) +command += oiiotool ("--oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr", failureok = True) +command += iconvert ("src/crash-1709.tif crash-1709.exr", failureok=True) outputs = [ "check1.tif", "out.txt" ] diff --git a/testsuite/tiff-misc/src/crash-1709.tif b/testsuite/tiff-misc/src/crash-1709.tif new file mode 100644 index 0000000000000000000000000000000000000000..dedf3f14e428baf7948bb80ad7bbd3a0e12a0d91 GIT binary patch literal 36987 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 IiMiYVAEkAK)&Kwi literal 0 HcmV?d00001