CVE-2022-41639
A heap based buffer overflow vulnerability exists in tile decoding code of TIFF image parser in OpenImageIO master-branch-9aeece7a and v2.3.19.0. A specially-crafted TIFF file can lead to an out of bounds memory corruption, which can result in arbitrary code execution. An attacker can provide a malicious file to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
OpenImageIO Project OpenImageIO master-branch-9aeece7a
OpenImageIO Project OpenImageIO v2.3.19.0
OpenImageIO - https://github.com/OpenImageIO/oiio
9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-122 - Heap-based Buffer Overflow
OpenImageIO is an image processing library with easy-to-use interfaces and a sizable number of supported image formats. Useful for conversion and processing and even image comparison, this library is utilized by 3D-processing software from AliceVision (including Meshroom), as well as Blender for reading Photoshop .psd files.
With OpenImageIO, handling of different file formats is a relatively simple matter. auto inp = ImageInput::open(filename)
takes care of all the intricate details of figuring out which file format we’re dealing with and redirects the codeflow to the appropriate file handlers. For the TIFF file format, we first end up hitting the TIFFOpen
function inside of the opensource LibTIFF library, which provides a parsed TIFF object to OpenImageIO inside of TIFFInput:seek_subimage:
bool
TIFFInput::seek_subimage(int subimage, int miplevel)
{
// [...]
if (!m_tif) {
if (ioproxy_opened()) {
static_assert(sizeof(thandle_t) == sizeof(void*),
"thandle_t must be same size as void*");
// Strutil::print("\n\nOpening client \"{}\"\n", m_filename);
ioseek(0);
m_tif = TIFFClientOpen(m_filename.c_str(), "rm", ioproxy(),
reader_readproc, reader_writeproc,
reader_seekproc, reader_closeproc,
reader_sizeproc, reader_mapproc,
reader_unmapproc);
} else {
#ifdef _WIN32
std::wstring wfilename = Strutil::utf8_to_utf16wstring(m_filename);
m_tif = TIFFOpenW(wfilename.c_str(), "rm");
#else
m_tif = TIFFOpen(m_filename.c_str(), "rm"); // [1]
#endif
}
We first hit the code path at [1] since we don’t yet have a m_tif
object yet, which eventually gets us to the lengthy LibTIFF file parsing inside TIFFReadDirectory
. For brevity’s sake, instead of going through the code we will just examine a sample hexdump to explain the file format:
00000000 49 49 2A 00 08 00 00 00 18 00 00 01 03 00 01 00 II*............. // [2]
00000010 00 00 80 00 00 00 01 01 03 00 01 00 00 00 00 02 ................
00000020 00 00 02 01 03 00 03 00 00 00 2E 01 00 00 03 01 ................
00000030 03 00 01 00 00 00 08 00 00 00 06 01 03 00 01 00 ................
00000040 00 00 02 00 00 00 12 01 03 00 01 00 00 00 01 00 ................
00000050 00 00 15 01 03 00 01 00 00 00 03 00 00 00 16 01 ................
00000060 03 00 01 00 00 00 20 00 00 00 1C 01 03 00 01 00 ...... .........
00000070 00 00 01 00 00 00 1E 01 05 00 01 00 00 00 34 01 ..............4.
00000080 00 00 1F 01 05 00 01 00 00 00 3C 01 00 00 31 01 ..........<...1.
00000090 02 00 23 00 00 00 44 01 00 00 32 01 02 00 14 00 ..#...D...2.....
000000A0 00 00 68 01 00 00 3D 01 03 00 01 00 00 00 02 00 ..h...=.........
000000B0 00 00 42 01 03 00 01 00 00 00 40 00 00 00 43 01 ..B.......@...C.
000000C0 03 00 01 00 00 00 40 00 00 00 44 01 04 00 10 00 ......@...D.....
000000D0 00 00 7C 01 00 00 45 01 04 00 10 00 00 00 BC 01 ..|...E.........
000000E0 00 00 53 01 03 00 03 00 00 00 FC 01 00 00 BC 02 ..S.............
000000F0 01 00 24 02 00 00 02 02 00 00 16 82 02 00 0E 00 ..$.............
00000100 00 00 26 04 00 00 17 82 02 00 0C 00 00 00 34 04 ..&...........4.
00000110 00 00 18 82 0B 00 01 00 00 00 00 00 80 3E BB 83 .............>..
00000120 04 00 0A 00 00 00 40 04 00 00 F8 09 00 00 08 00 ......@.........
#define TIFF_BIGENDIAN 0x4d4d
#define TIFF_LITTLEENDIAN 0x4949
#define MDI_LITTLEENDIAN 0x5045
#define MDI_BIGENDIAN 0x4550
typedef struct {
uint16_t tiff_magic; /* magic number (defines byte order) */
uint16_t tiff_version; /* TIFF version number */
uint32_t tiff_diroff; /* byte offset to first directory */
} TIFFHeaderClassic; // [3]
typedef struct {
uint16_t tiff_magic; /* magic number (defines byte order) */
uint16_t tiff_version; /* TIFF version number */
uint16_t tiff_offsetsize; /* size of offsets, should be 8 */
uint16_t tiff_unused; /* unused word, should be 0 */
uint64_t tiff_diroff; /* byte offset to first directory */
} TIFFHeaderBig; // [4]
Since our first 2 bytes match the TIFF_LITTLEENDIAN
header of 0x4949 [2], we deal with the TiffHeaderClassic
[3] instead of the TIFFHeaderBig
[4]. Thus, the uint32_t at offset 0x4 is our tiff_diroff
, i.e. the offset at which we find our tiff directories (0x8). TIFFReadDirectory
then goes to that offset and reads in the number of directories we have. In this case, at offset 0x8 at [2], we can see that our uint16_t dircount
is 0x18. Since we are not dealing with a TIFFHeaderBig
, each of these “directories” is 0xC bytes long and looks as such:
[-.-]> ptype TIFFDirEntry
type = struct TIFFDirEntry {
uint16_t tdir_tag;
uint16_t tdir_type;
uint32_t tdir_count;
uint32_t tdir_offset;
}
As such, TiffReadDirectory
reads in the dircount16 * dirsize
bytes immediately following our dircount
. In our case, this would be 0x18 * 0xc
, resulting in 0x120 bytes being read. In the hexdump above, this would mean that our directories come directly from the bytes at offset (0xA, 0x12A). For example, if we look at the directories at offset 0x8E get parsed, we see the following:
00000080 31 01 |..........<...1.|
00000090 02 00 23 00 00 00 44 01 00 00 // start of second directory
32 01 02 00 14 00 |..#...D...2.....|
000000a0 00 00 68 01 00 00
[^~^]> p/x *direntry
$45 = {tdir_tag = 0x131, tdir_type = 0x2, tdir_count = 0x23, tdir_offset = {toff_short = 0x144, toff_long = 0x144, toff_long8 = 0x144}, tdir_ignore = 0x0}
[^.^]> p/x *(direntry+1)
$46 = {tdir_tag = 0x132, tdir_type = 0x2, tdir_count = 0x14, tdir_offset = {toff_short = 0x168, toff_long = 0x168, toff_long8 = 0x168}, tdir_ignore = 0x0}
Regardless, after populating all these directories into memory, futher processing occurs on some of the more pivotal directories inside LibTiff’s TIFFReadDirectory(TIFF *tif)
. An example of this looks like so:
int TIFFReadDirectory(TIFF* tif) {
static const char module[] = "TIFFReadDirectory";
// [...]
dp=TIFFReadDirectoryFindEntry(tif,dir,dircount,TIFFTAG_SAMPLESPERPIXEL);
if (dp)
{
if (!TIFFFetchNormalTag(tif,dp,0))
goto bad;
dp->tdir_ignore = TRUE;
}
A few passes are done over all the directories to search for specific information, including such important information as the size of the tiff file’s tiles:
#define TIFFTAG_TILEWIDTH 322 /* !tile width in pixels */
#define TIFFTAG_TILELENGTH 323 /* !tile height in pixels */
#define TIFFTAG_TILEOFFSETS 324 /* !offsets to data tiles */
#define TIFFTAG_TILEBYTECOUNTS 325 /* !byte counts for tiles */
*/
for (di=0, dp=dir; di<dircount; di++, dp++)
{
// [...]
if (!dp->tdir_ignore)
{
fip=tif->tif_fields[fii];
if (fip->field_bit==FIELD_IGNORE)
dp->tdir_ignore = TRUE;
else
{
switch (dp->tdir_tag)
{
case TIFFTAG_STRIPOFFSETS:
case TIFFTAG_STRIPBYTECOUNTS:
case TIFFTAG_TILEOFFSETS:
case TIFFTAG_TILEBYTECOUNTS:
TIFFSetFieldBit(tif,fip->field_bit);
break;
case TIFFTAG_IMAGEWIDTH:
case TIFFTAG_IMAGELENGTH:
case TIFFTAG_IMAGEDEPTH:
case TIFFTAG_TILELENGTH: // [5]
case TIFFTAG_TILEWIDTH:
case TIFFTAG_TILEDEPTH:
case TIFFTAG_PLANARCONFIG:
case TIFFTAG_ROWSPERSTRIP:
case TIFFTAG_EXTRASAMPLES:
if (!TIFFFetchNormalTag(tif,dp,0))
goto bad;
dp->tdir_ignore = TRUE;
break;
default:
if( !_TIFFCheckFieldIsValidForCodec(tif, dp->tdir_tag) )
dp->tdir_ignore = TRUE;
break;
}
At [5], we see that the TIFFTAG_TILELENGTH
field (tag 0x141) is gathered and populated into tif->dir.td_tilelength
, which will be important later.
After this, we eventually calculate the size of the tif->tif_tilesize
and the tif->scanlinesize
, both of which determine the sizes of temporary buffers when reading in the image data:
int TIFFReadDirectory(TIFF* tif) {
static const char module[] = "TIFFReadDirectory";
// [...]
/*
* Reinitialize i/o since we are starting on a new directory.
*/
tif->tif_row = (uint32_t) -1;
tif->tif_curstrip = (uint32_t) -1;
tif->tif_col = (uint32_t) -1;
tif->tif_curtile = (uint32_t) -1;
tif->tif_tilesize = (tmsize_t) -1;
tif->tif_scanlinesize = TIFFScanlineSize(tif);
if (!tif->tif_scanlinesize) {
TIFFErrorExt(tif->tif_clientdata, module,
"Cannot handle zero scanline size");
return (0);
}
if (isTiled(tif)) {
tif->tif_tilesize = TIFFTileSize(tif); //[6]
if (!tif->tif_tilesize) {
TIFFErrorExt(tif->tif_clientdata, module,
"Cannot handle zero tile size");
return (0);
}
} else {
if (!TIFFStripSize(tif)) {
TIFFErrorExt(tif->tif_clientdata, module,
"Cannot handle zero strip size");
return (0);
}
}
At [6], we see where the TIF object’s tif_tilesize
is generated, and the codeflow looks as such:
/*
* Compute the # bytes in a row-aligned tile.
*/
uint64_t
TIFFTileSize64(TIFF* tif)
{
return (TIFFVTileSize64(tif, tif->tif_dir.td_tilelength)); // [7]
}
tmsize_t
TIFFTileSize(TIFF* tif)
{
static const char module[] = "TIFFTileSize";
uint64_t m;
m=TIFFTileSize64(tif);
return _TIFFCastUInt64ToSSize(tif, m, module);
}
As shown at [7], the tif->tilesize
is directly correlated to the tif_dir.td_tilelength
, which was read directly from our tif->tif_dir
above. Continuing into TIFFVTileSize64
:
uint64_t
TIFFVTileSize64(TIFF* tif, uint32_t nrows)
{
static const char module[] = "TIFFVTileSize64";
TIFFDirectory *td = &tif->tif_dir;
if (td->td_tilelength == 0 || td->td_tilewidth == 0 ||
td->td_tiledepth == 0)
return (0);
if ((td->td_planarconfig==PLANARCONFIG_CONTIG)&&
(td->td_photometric==PHOTOMETRIC_YCBCR)&&
(td->td_samplesperpixel==3)&&
(!isUpSampled(tif)))
{
// [...]
else
return(_TIFFMultiply64(tif,nrows,TIFFTileRowSize64(tif),module)); // [8] }
Assuming the tif->tif_dir
passes some sanity checks, we make it down to [8], where we see our tif_dir.td_tilelength
gets multiplied by the return value of TIFFTileRowSize64
:
/*
* Compute the # bytes in each row of a tile.
*/
uint64_t
TIFFTileRowSize64(TIFF* tif)
{
static const char module[] = "TIFFTileRowSize64";
TIFFDirectory *td = &tif->tif_dir;
uint64_t rowsize;
uint64_t tilerowsize;
if (td->td_tilelength == 0)
{
TIFFErrorExt(tif->tif_clientdata,module,"Tile length is zero");
return 0;
}
if (td->td_tilewidth == 0)
{
TIFFErrorExt(tif->tif_clientdata,module,"Tile width is zero");
return (0);
}
rowsize = _TIFFMultiply64(tif, td->td_bitspersample, td->td_tilewidth, // [9]
"TIFFTileRowSize");
if (td->td_planarconfig == PLANARCONFIG_CONTIG)
{
if (td->td_samplesperpixel == 0)
{
TIFFErrorExt(tif->tif_clientdata,module,"Samples per pixel is zero");
return 0;
}
rowsize = _TIFFMultiply64(tif, rowsize, td->td_samplesperpixel, // [10]
"TIFFTileRowSize");
}
tilerowsize=TIFFhowmany8_64(rowsize); // [11]
if (tilerowsize == 0)
{
TIFFErrorExt(tif->tif_clientdata,module,"Computed tile row size is zero");
return 0;
}
return (tilerowsize);
}
We see that the final determinant of the size of the buffer ends up being tif_dir.td_tilelength
multiplied against the tilerowsize
variable, which is calculated by further multiplications of assorted tiff tags. TIFFTAG_BITSPERSAMPLE * TIFFTAG_TILEWIDTH
[9] further optionally multiplied against the TIFFTAG_SAMPLESPERPIXEL
[10] and then run into the TIFFhowmany8_64
function at [11]:
tiffiop.h:#define TIFFhowmany8_64(x) (((x)&0x07)?((uint64_t)(x)>>3)+1:(uint64_t)(x)>>3)
Regardless, after all that, we clearly see that the final tif->tif_tilesize
[6] is calculated from assorted TIFFTAG fields, all from the tiff input file itself. For a tiff file with a td_tilelength
of 0x40 and a td_tilewidth
of 0x40, as well as a td_bitspersample
of 0x8, we end up with tif->tif_tilesize
being 0x4000.
Carrying on further into the code after that delightful tangent, let us examine where this tif->tif_tilesize
actually matters:
bool
ImageInput::read_native_tiles(int subimage, int miplevel, int xbegin, int xend,
int ybegin, int yend, int zbegin, int zend,
void* data)
{
// [...]
ImageSpec spec = spec_dimensions(subimage, miplevel); // thread-safe // [12]
// [...]
// Base class implementation of read_native_tiles just repeatedly
// calls read_native_tile, which is supplied by every plugin that
// supports tiles. Only the hardcore ones will overload
// read_native_tiles with their own implementation.
stride_t pixel_bytes = (stride_t)spec.pixel_bytes(true);
stride_t tileystride = pixel_bytes * spec.tile_width;
stride_t tilezstride = tileystride * spec.tile_height;
stride_t ystride = (xend - xbegin) * pixel_bytes;
stride_t zstride = (yend - ybegin) * ystride;
std::unique_ptr<char[]> pels(new char[spec.tile_bytes(true)]); // [13]
for (int z = zbegin; z < zend; z += spec.tile_depth) {
for (int y = ybegin; y < yend; y += spec.tile_height) {
for (int x = xbegin; x < xend; x += spec.tile_width) {
bool ok = read_native_tile(subimage, miplevel, x, y, z,
&pels[0]);
At [12] we generate an ImageSpec spec
object that is populated from the same file data as before, and at [13] this ImageSpec
object is used to generate a variable length buffer via the tile_bytes
class method:
imagesize_t
ImageSpec::tile_pixels() const noexcept
{
if (tile_width <= 0 || tile_height <= 0 || tile_depth <= 0)
return 0;
imagesize_t r = clamped_mult64((imagesize_t)tile_width, // [14]
(imagesize_t)tile_height);
if (tile_depth > 1)
r = clamped_mult64(r, (imagesize_t)tile_depth);
return r;
}
imagesize_t
ImageSpec::tile_bytes(bool native) const noexcept
{
return clamped_mult64(tile_pixels(), (imagesize_t)pixel_bytes(native));
}
It’s worth noting that these tile_width
and tile_height
variables around [14] can be populated with the same 0x40
and 0x40
that we saw before, and if our tile depth ends up being 0x1 (which it can be), then the return value of tile_pixels()
is 0x1000, which then gets multiplied against the return value of pixel_bytes(true)
:
size_t
ImageSpec::pixel_bytes(bool native) const noexcept
{
if (nchannels < 0)
return 0;
if (!native || channelformats.empty())
return clamped_mult32((size_t)nchannels, channel_bytes()); // [15]
else {
size_t sum = 0;
for (int i = 0; i < nchannels; ++i)
sum += channelformats[i].size();
return sum;
}
}
There is a hard limit for the number of channels, so there is not much variation in the return value here. Assuming all of the channels in the tif file are the same, we hit the branch at [15]. This causes the nchannels
(which for us is 0x3) to be multiplied against the spec.format.size()
, which in our case is 0x1. As such, the resultant size of the pels
vector at [13] is 0x3 * 0x40 * 0x40, or 0x3000. And if we look at what ends up happening to this pels
vector, we eventually run into an issue:
bool
ImageInput::read_native_tiles(int subimage, int miplevel, int xbegin, int xend,
int ybegin, int yend, int zbegin, int zend,
void* data)
{
// [...]
ImageSpec spec = spec_dimensions(subimage, miplevel); // thread-safe
// [...]
std::unique_ptr<char[]> pels(new char[spec.tile_bytes(true)]);
for (int z = zbegin; z < zend; z += spec.tile_depth) {
for (int y = ybegin; y < yend; y += spec.tile_height) {
for (int x = xbegin; x < xend; x += spec.tile_width) {
bool ok = read_native_tile(subimage, miplevel, x, y, z, // [16]
&pels[0]);
At [16], this vector is passed into the tiff-specific read_native_tile
function as the temporary buffer data
. Continuing into read_native_tile
:
bool
TIFFInput::read_native_tile(int subimage, int miplevel, int x, int y, int z,
void* data)
{
lock_guard lock(*this);
if (!seek_subimage(subimage, miplevel))
return false;
x -= m_spec.x;
y -= m_spec.y;
// [...]
imagesize_t tile_pixels = m_spec.tile_pixels();
imagesize_t nvals = tile_pixels * m_spec.nchannels;
if (m_photometric == PHOTOMETRIC_PALETTE && m_bitspersample > 8)
m_scratch.resize(nvals * 2); // special case for 16 bit palette
else
m_scratch.resize(m_spec.tile_bytes()); // [17]
bool no_bit_convert = (m_bitspersample == 8 || m_bitspersample == 16
|| m_bitspersample == 32);
if (m_photometric == PHOTOMETRIC_PALETTE) {
// [...]
} else {
// Not palette
imagesize_t plane_bytes = m_spec.tile_pixels() * m_spec.format.size();
int planes = m_separate ? m_spec.nchannels : 1;
std::vector<unsigned char> scratch2(m_separate ? m_spec.tile_bytes()
: 0);
// Where to read? Directly into user data if no channel shuffling
// or bit shifting is needed, otherwise into scratch space.
unsigned char* readbuf = (no_bit_convert && !m_separate) // [18]
? (unsigned char*)data
: &m_scratch[0];
// Perform the reads. Note that for contig, planes==1, so it will
// only do one TIFFReadTile.
for (int c = 0; c < planes; ++c) /* planes==1 for contig */
if (TIFFReadTile(m_tif, &readbuf[plane_bytes * c], x, y, z, c) // [19]
< 0) {
errorf("%s", oiio_tiff_last_error());
return false;
}
We finally get to the crux of the matter in which we finally start to read Tiff tiles into our temporary readbuf
at [19]. Interestingly however, the readbuf
pointer can either point at the input data
buffer (our pels
vector from before), or the m_scratch
array [18]. The array’s size is determined around [17], as the tile_bytes()
function will resize the m_scratch buffer to 0x4000 bytes, whereas our pels
vector is only length 0x3000. Continuing on into TIFFReadTile
:
/*
* Read and decompress a tile of data. The
* tile is selected by the (x,y,z,s) coordinates.
*/
tmsize_t
TIFFReadTile(TIFF* tif, void* buf, uint32_t x, uint32_t y, uint32_t z, uint16_t s)
{
if (!TIFFCheckRead(tif, 1) || !TIFFCheckTile(tif, x, y, z, s))
return ((tmsize_t)(-1));
return (TIFFReadEncodedTile(tif,
TIFFComputeTile(tif, x, y, z, s), buf, (tmsize_t)(-1))); // [20]
}
The above code leads into TIFFComputeTile at [20]:
/*
* Read a tile of data and decompress the specified
* amount into the user-supplied buffer.
*/
tmsize_t
TIFFReadEncodedTile(TIFF* tif, uint32_t tile, void* buf, tmsize_t size)
{
static const char module[] = "TIFFReadEncodedTile";
TIFFDirectory *td = &tif->tif_dir;
tmsize_t tilesize = tif->tif_tilesize; // [21]
// [...]
if (TIFFFillTile(tif, tile) && (*tif->tif_decodetile)(tif,
(uint8_t*) buf, size, (uint16_t)(tile / td->td_stripsperimage))) { // [22]
(*tif->tif_postdecode)(tif, (uint8_t*) buf, size);
return (size);
} else
return ((tmsize_t)(-1));
}
Since -1 is passed in as our size tmsize_t size
argument, we end up using the tilesize
variable at [21]. This is clearly assigned as tif->tif_tilesize
, which we thoroughly defined as 0x4000. Eventually this gets passed in as the size of our buffer at tif->tif_decodetile(). Our pels
vector can be the destination buffer, which has a size of 0x3000, eventually resulting in a heap overflow. At least in our instance PoC, the tif->m_scratch
vector follows pretty close after the pels
vector. Its heap metadata is corrupted before triggering an ASAN crash when the tif
object is eventually destroyed and the tif->m_scratch
vector is freed. With precise memory layout control and heap metadata manipulation, this memory corruption can lead to arbitrary code execution.
==124926==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x627000003900 in thread T0
[Detaching after fork from child process 124937]
#0 0x55555566d16d in operator delete(void*) (/oiio/fuzzing_master/fuzz_oiio.bin+0x11916d) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#1 0x7ffff13ebebf in __gnu_cxx::new_allocator<unsigned char>::deallocate(unsigned char*, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/new_allocator.h:145:2
#2 0x7ffff13ebdff in std::allocator_traits<std::allocator<unsigned char> >::deallocate(std::allocator<unsigned char>&, unsigned char*, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:496:13
#3 0x7ffff13ea81a in std::_Vector_base<unsigned char, std::allocator<unsigned char> >::_M_deallocate(unsigned char*, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:354:4
#4 0x7ffff13ec981 in std::_Vector_base<unsigned char, std::allocator<unsigned char> >::~_Vector_base() /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:335:2
#5 0x7ffff13e6816 in std::vector<unsigned char, std::allocator<unsigned char> >::~vector() /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:683:7
#6 0x7ffff3ddb6a5 in OpenImageIO_v2_5_0::TIFFInput::~TIFFInput() /oiio/oiio-master/src/tiff.imageio/tiffinput.cpp:634:1
#7 0x7ffff3ddba91 in OpenImageIO_v2_5_0::TIFFInput::~TIFFInput() /oiio/oiio-master/src/tiff.imageio/tiffinput.cpp:631:1
#8 0x555555670850 in std::default_delete<OpenImageIO_v2_5_0::ImageInput>::operator()(OpenImageIO_v2_5_0::ImageInput*) const /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:85:2
#9 0x5555556705de in std::unique_ptr<OpenImageIO_v2_5_0::ImageInput, std::default_delete<OpenImageIO_v2_5_0::ImageInput> >::~unique_ptr() /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:361:4
#10 0x55555566fd73 in LLVMFuzzerTestOneInput /oiio/fuzzing_master/./oiio_harness.cpp:99:1
#11 0x5555555954e3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/oiio/fuzzing_master/fuzz_oiio.bin+0x414e3) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#12 0x55555557f25f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/oiio/fuzzing_master/fuzz_oiio.bin+0x2b25f) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#13 0x555555584fb6 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/oiio/fuzzing_master/fuzz_oiio.bin+0x30fb6) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#14 0x5555555aedd2 in main (/oiio/fuzzing_master/fuzz_oiio.bin+0x5add2) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#15 0x7fffec30bd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#16 0x7fffec30be3f in __libc_start_main csu/../csu/libc-start.c:392:3
#17 0x555555579b24 in _start (/oiio/fuzzing_master/fuzz_oiio.bin+0x25b24) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
0x627000003900 is located 2048 bytes to the right of 12288-byte region [0x627000000100,0x627000003100)
freed by thread T0 here:
#0 0x55555566d26d in operator delete[](void*) (/oiio/fuzzing_master/fuzz_oiio.bin+0x11926d) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#1 0x7ffff2b871a6 in std::enable_if<is_convertible<char (*) [], char (*) []>::value, void>::type std::default_delete<char []>::operator()<char>(char*) const /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:120:4
#2 0x7ffff2a32af3 in std::unique_ptr<char [], std::default_delete<char []> >::~unique_ptr() /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:612:4
#3 0x7ffff2d59aa2 in OpenImageIO_v2_5_0::ImageInput::read_native_tiles(int, int, int, int, int, int, int, int, void*) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:789:1
#4 0x7ffff3e40633 in OpenImageIO_v2_5_0::TIFFInput::read_native_tiles(int, int, int, int, int, int, int, int, void*) /oiio/oiio-master/src/tiff.imageio/tiffinput.cpp:2129:28
#5 0x7ffff2d5115f in OpenImageIO_v2_5_0::ImageInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:620:20
#6 0x7ffff3e4bcaf in OpenImageIO_v2_5_0::TIFFInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long) /oiio/oiio-master/src/tiff.imageio/tiffinput.cpp:2298:27
#7 0x7ffff2d61270 in OpenImageIO_v2_5_0::ImageInput::read_image(int, int, int, int, OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:941:23
#8 0x7ffff2d5cb5a in OpenImageIO_v2_5_0::ImageInput::read_image(OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:864:12
#9 0x55555566fa58 in LLVMFuzzerTestOneInput /oiio/fuzzing_master/./oiio_harness.cpp:89:18
#10 0x5555555954e3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/oiio/fuzzing_master/fuzz_oiio.bin+0x414e3) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#11 0x55555557f25f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/oiio/fuzzing_master/fuzz_oiio.bin+0x2b25f) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#12 0x555555584fb6 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/oiio/fuzzing_master/fuzz_oiio.bin+0x30fb6) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#13 0x5555555aedd2 in main (/oiio/fuzzing_master/fuzz_oiio.bin+0x5add2) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#14 0x7fffec30bd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
previously allocated by thread T0 here:
#0 0x55555566ca1d in operator new[](unsigned long) (/oiio/fuzzing_master/fuzz_oiio.bin+0x118a1d) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#1 0x7ffff2d586ff in OpenImageIO_v2_5_0::ImageInput::read_native_tiles(int, int, int, int, int, int, int, int, void*) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:770:34
#2 0x7ffff3e40633 in OpenImageIO_v2_5_0::TIFFInput::read_native_tiles(int, int, int, int, int, int, int, int, void*) /oiio/oiio-master/src/tiff.imageio/tiffinput.cpp:2129:28
#3 0x7ffff2d5115f in OpenImageIO_v2_5_0::ImageInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:620:20
#4 0x7ffff3e4bcaf in OpenImageIO_v2_5_0::TIFFInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long) /oiio/oiio-master/src/tiff.imageio/tiffinput.cpp:2298:27
#5 0x7ffff2d61270 in OpenImageIO_v2_5_0::ImageInput::read_image(int, int, int, int, OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:941:23
#6 0x7ffff2d5cb5a in OpenImageIO_v2_5_0::ImageInput::read_image(OpenImageIO_v2_5_0::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /oiio/oiio-master/src/libOpenImageIO/imageinput.cpp:864:12
#7 0x55555566fa58 in LLVMFuzzerTestOneInput /oiio/fuzzing_master/./oiio_harness.cpp:89:18
#8 0x5555555954e3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/oiio/fuzzing_master/fuzz_oiio.bin+0x414e3) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#9 0x55555557f25f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/oiio/fuzzing_master/fuzz_oiio.bin+0x2b25f) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#10 0x555555584fb6 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/oiio/fuzzing_master/fuzz_oiio.bin+0x30fb6) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#11 0x5555555aedd2 in main (/oiio/fuzzing_master/fuzz_oiio.bin+0x5add2) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2)
#12 0x7fffec30bd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: bad-free (/oiio/fuzzing_master/fuzz_oiio.bin+0x11916d) (BuildId: 3723dfbe3c255f3559fd21ebe0474ec771515df2) in operator delete(void*)
2022-10-19 - Initial Vendor Contact
2022-10-20 - Vendor Disclosure
2022-11-01 - Vendor Patch Release
2022-12-22 - Public Release
Discovered by Lilith >_> of Cisco Talos.