CVE-2022-41988
An information disclosure vulnerability exists in the OpenImageIO::decode_iptc_iim() functionality of OpenImageIO Project OpenImageIO v2.3.19.0. A specially-crafted TIFF file can lead to a disclosure of sensitive information. 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 v2.3.19.0
OpenImageIO - https://github.com/OpenImageIO/oiio
5.3 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CWE-125 - Out-of-bounds Read
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 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}
The tdir_tag
field is used to sort the array of directories, along with identifying the data. The tdir_type
field designates the data’s type and the tdir_count
designates the length of the data, then finally the tdir_offset
field points to the offset inside the TIFF file where we can actually find the data. So when LibOpenImageIO wants to find the data for a given tag, it first consults the hardcoded LibTiff array of possible field types and then binary searches through the directories that it has loaded in from the file. A sample of predefined LibTiff fields is given below:
static const TIFFField
tiffFields[] = {
{ TIFFTAG_SUBFILETYPE, 1, 1, TIFF_LONG, 0, TIFF_SETGET_UINT32, TIFF_SETGET_UNDEFINED, FIELD_SUBFILETYPE, 1, 0, "SubfileType", NULL },
{ TIFFTAG_OSUBFILETYPE, 1, 1, TIFF_SHORT, 0, TIFF_SETGET_UNDEFINED, TIFF_SETGET_UNDEFINED, FIELD_SUBFILETYPE, 1, 0, "OldSubfileType", NULL },
{ TIFFTAG_IMAGEWIDTH, 1, 1, TIFF_LONG, 0, TIFF_SETGET_UINT32, TIFF_SETGET_UNDEFINED, FIELD_IMAGEDIMENSIONS, 0, 0, "ImageWidth", NULL },
{ TIFFTAG_IMAGELENGTH, 1, 1, TIFF_LONG, 0, TIFF_SETGET_UINT32, TIFF_SETGET_UNDEFINED, FIELD_IMAGEDIMENSIONS, 1, 0, "ImageLength", NULL },
// [...]
}
These directories are for the most part utilized inside of the void TIFFInput::readspec(bool read_meta)
function, which calls the various TIFFGetField
function to grab the needed data from each directory. In the most basic case that looks like so:
void
TIFFInput::readspec(bool read_meta)
{
uint32_t width = 0, height = 0, depth = 0;
TIFFGetField(m_tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(m_tif, TIFFTAG_IMAGELENGTH, &height);
TIFFGetFieldDefaulted(m_tif, TIFFTAG_IMAGEDEPTH, &depth);
TIFFGetFieldDefaulted(m_tif, TIFFTAG_SAMPLESPERPIXEL, &m_inputchannels);
// [...]
Much further down inside of void TIFFInput::readspec(bool read_meta)
we see a parsing of the TIFFTAG_RICHTIFFIPTC
directory, where the tag is 0x83bb:
int iptcsize = 0;
const void* iptcdata = NULL;
if (TIFFGetField(m_tif, TIFFTAG_RICHTIFFIPTC, &iptcsize, &iptcdata)) { // [3]
std::vector<uint32_t> iptc((uint32_t*)iptcdata, // [4]
(uint32_t*)iptcdata + iptcsize);
if (TIFFIsByteSwapped(m_tif))
TIFFSwabArrayOfLong((uint32_t*)&iptc[0], iptcsize);
decode_iptc_iim(&iptc[0], iptcsize * 4, m_spec); // [5]
}
The data is pulled from the directory and thrown into the iptcsize
and iptcdata
variables at [3], and then a uint32_t vector is created with that data at [4], via the (start_ptr
, end_ptr
) initializer. Now, quickly going back to our input hexdump, let’s find what the TIFFTAG_RICHTIFFIPTC
looks like:
#define TIFFTAG_RICHTIFFIPTC 33723 (0x83bb)
0000020a BB 83 04 00 0A 00 00 00 40 04 // [6]
// [...]
As shown above at [6], we see the TIFFTAG_RICHTIFFIPTC
tag at offset 0x20a, followed by the tdir_type
, tdir_count
, and tdir_offset
. Thus, the type of the data is TIFF_LONG
, there are 0xa entries, and the data starts at offset 0x440. So if we look at offset 0x440, we can see the following:
00000440 59 00 00 00 59 00 00 00 59 00 00 00 59 00 00 00 59 00 00 00 59 00 00 00 Y...Y...Y...Y...Y...Y...
00000458 59 00 00 00 59 00 00 00 59 00 00 00 59 00 00 00 59 00 00 00 59 00 00 00 Y...Y...Y...Y...Y...Y...
00000470 59 00 00 00 59 00 00 00 01 00 01 00 01 00 3C 3F 78 70 61 63 6B 65 74 20 Y...Y.........
And this sample data corresponds to what we see in a debugger inside of iptcdata
and iptcsize
after TIFFGetField
[3] finishes:
[x.x]> x/1s iptcdata
0x602000000270: "YYYYYYYYYY"
[^~^]> p/x iptcsize
$8 = 0xa
To summarize, we control the size and contents of the iptc
vector generated at [4], and this is then passed into the decode_iptc_iim
function at [5]:
bool
decode_iptc_iim(const void* iptc, int length, ImageSpec& spec)
{
const unsigned char* buf = (const unsigned char*)iptc;
// Now there are a series of data blocks. Each one starts with 1C
// 02, then a single byte indicating the tag type, then 2 byte (big
// endian) giving the tag length, then the data itself. This
// repeats until we've used up the whole segment buffer, or I guess
// until we don't find another 1C 02 tag start.
// N.B. I don't know why, but Picasa sometimes uses 1C 01 !
while (length > 0 && buf[0] == 0x1c && (buf[1] == 0x02 || buf[1] == 0x01)) { // [7]
int secondbyte = buf[1];
int tagtype = buf[2];
int tagsize = (buf[3] << 8) + buf[4]; // [8]
buf += 5;
length -= 5;
if (secondbyte == 0x02) {
std::string s((const char*)buf, tagsize); // [9]
for (int i = 0; iimtag[i].name; ++i) {
if (tagtype == iimtag[i].tag) {
if (iimtag[i].repeatable) {
// For repeatable IIM tags, concatenate them
// together separated by semicolons
s = Strutil::strip(s);
std::string old = spec.get_string_attribute(
iimtag[i].name);
if (old.size())
old += "; ";
spec.attribute(iimtag[i].name, old + s);
} else {
spec.attribute(iimtag[i].name, s);
}
if (iimtag[i].anothername)
spec.attribute(iimtag[i].anothername, s);
break;
}
}
}
buf += tagsize;
length -= tagsize;
}
return true;
}
Assuming our input bytes pass the validation at [7], we create a new std::string s
at [9], where the length is controlled by the input file and can be up to 0xFFFF. Since there’s no validation between [8] and [9], and also because our input data was read into a vector at [4], this ends up being a decent out-of-bounds heap read, where the data gets stored into our resultant m_spec
object at one of the calls to spec.attribute
within the function.
It’s worth noting that this vulnerability has been fixed as of release v2.4.4.2 and in the master branch (https://github.com/OpenImageIO/oiio/commit/e9103925bb2aeed36b01b3805f36959f5d1a2e18#diff-8496b368a265f99b41e3c06bf99a5ea82d4f40fff1919ee79caa26ae033b3a06R118).
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 2021219380
INFO: Loaded 3 modules (1146257 inline 8-bit counters): 102986 [0x7f643eaad0c0, 0x7f643eac630a), 1043122 [0x7f644cd27240, 0x7f644ce25cf2), 149 [0x55e76230d108, 0x55e76230d19d),
INFO: Loaded 3 PC tables (1146257 PCs): 102986 [0x7f643eac6310,0x7f643ec587b0), 1043122 [0x7f644ce25cf8,0x7f644de10818), 149 [0x55e76230d1a0,0x55e76230daf0),
./fuzz_oiio.bin: Running 1 inputs 1 time(s) each.
Running: ./crash-436f62e9d1e9692954d02c72d04c47952574f294
=================================================================
==61746==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000298 at pc 0x55e76221ff64 bp 0x7ffe6f9a5060 sp 0x7ffe6f9a4828
READ of size 34 at 0x602000000298 thread T0
#0 0x55e76221ff63 in __interceptor_memcpy (/oiio/fuzzing/fuzz_oiio.bin+0x74f63) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#1 0x7f644df65c63 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14ec63) (BuildId: f57e02bfadacc0c923c82457d5e18e1830b5faea)
#2 0x7f6448736aba in OpenImageIO_v2_3::decode_iptc_iim(void const*, int, OpenImageIO_v2_3::ImageSpec&) /oiio/oiio-2.3.19.0/src/libOpenImageIO/iptc.cpp:129:25
#3 0x7f64497c6185 in OpenImageIO_v2_3::TIFFInput::readspec(bool) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:1241:9
#4 0x7f64497ab9eb in OpenImageIO_v2_3::TIFFInput::seek_subimage(int, int) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:778:9
#5 0x7f64497a9158 in OpenImageIO_v2_3::TIFFInput::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, OpenImageIO_v2_3::ImageSpec&) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:690:15
#6 0x7f64497b073c in OpenImageIO_v2_3::TIFFInput::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, OpenImageIO_v2_3::ImageSpec&, OpenImageIO_v2_3::ImageSpec const&) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:716:12
#7 0x7f64486b6859 in OpenImageIO_v2_3::ImageInput::create(OpenImageIO_v2_3::string_view, bool, OpenImageIO_v2_3::ImageSpec const*, OpenImageIO_v2_3::Filesystem::IOProxy*, OpenImageIO_v2_3::string_view) /oiio/oiio-2.3.19.0/src/libOpenImageIO/imageioplugin.cpp:758:27
#8 0x7f6448595699 in OpenImageIO_v2_3::ImageInput::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, OpenImageIO_v2_3::ImageSpec const*, OpenImageIO_v2_3::Filesystem::IOProxy*) /oiio/oiio-2.3.19.0/src/libOpenImageIO/imageinput.cpp:105:16
#9 0x55e7622c640f in LLVMFuzzerTestOneInput /oiio/fuzzing/./oiio_harness.cpp:77:16
#10 0x55e7621ec4e3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/oiio/fuzzing/fuzz_oiio.bin+0x414e3) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#11 0x55e7621d625f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/oiio/fuzzing/fuzz_oiio.bin+0x2b25f) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#12 0x55e7621dbfb6 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/oiio/fuzzing/fuzz_oiio.bin+0x30fb6) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#13 0x55e762205dd2 in main (/oiio/fuzzing/fuzz_oiio.bin+0x5add2) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#14 0x7f643ecc9d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#15 0x7f643ecc9e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#16 0x55e7621d0b24 in _start (/oiio/fuzzing/fuzz_oiio.bin+0x25b24) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
0x602000000298 is located 0 bytes to the right of 8-byte region [0x602000000290,0x602000000298)
allocated by thread T0 here:
#0 0x55e7622c390d in operator new(unsigned long) (/oiio/fuzzing/fuzz_oiio.bin+0x11890d) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#1 0x7f644354ccad in __gnu_cxx::new_allocator<unsigned int>::allocate(unsigned long, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/new_allocator.h:127:27
#2 0x7f644354c6b3 in std::allocator_traits<std::allocator<unsigned int> >::allocate(std::allocator<unsigned int>&, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:464:20
#3 0x7f644592084b in std::_Vector_base<unsigned int, std::allocator<unsigned int> >::_M_allocate(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:346:20
#4 0x7f6449829d69 in void std::vector<unsigned int, std::allocator<unsigned int> >::_M_range_initialize<unsigned int*>(unsigned int*, unsigned int*, std::forward_iterator_tag) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:1582:14
#5 0x7f64497d8b16 in std::vector<unsigned int, std::allocator<unsigned int> >::vector<unsigned int*, void>(unsigned int*, unsigned int*, std::allocator<unsigned int> const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:657:4
#6 0x7f64497c5c5d in OpenImageIO_v2_3::TIFFInput::readspec(bool) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:1237:31
#7 0x7f64497ab9eb in OpenImageIO_v2_3::TIFFInput::seek_subimage(int, int) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:778:9
#8 0x7f64497a9158 in OpenImageIO_v2_3::TIFFInput::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, OpenImageIO_v2_3::ImageSpec&) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:690:15
#9 0x7f64497b073c in OpenImageIO_v2_3::TIFFInput::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, OpenImageIO_v2_3::ImageSpec&, OpenImageIO_v2_3::ImageSpec const&) /oiio/oiio-2.3.19.0/src/tiff.imageio/tiffinput.cpp:716:12
#10 0x7f64486b6859 in OpenImageIO_v2_3::ImageInput::create(OpenImageIO_v2_3::string_view, bool, OpenImageIO_v2_3::ImageSpec const*, OpenImageIO_v2_3::Filesystem::IOProxy*, OpenImageIO_v2_3::string_view) /oiio/oiio-2.3.19.0/src/libOpenImageIO/imageioplugin.cpp:758:27
#11 0x7f6448595699 in OpenImageIO_v2_3::ImageInput::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, OpenImageIO_v2_3::ImageSpec const*, OpenImageIO_v2_3::Filesystem::IOProxy*) /oiio/oiio-2.3.19.0/src/libOpenImageIO/imageinput.cpp:105:16
#12 0x55e7622c640f in LLVMFuzzerTestOneInput /oiio/fuzzing/./oiio_harness.cpp:77:16
#13 0x55e7621ec4e3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/oiio/fuzzing/fuzz_oiio.bin+0x414e3) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#14 0x55e7621d625f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/oiio/fuzzing/fuzz_oiio.bin+0x2b25f) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#15 0x55e7621dbfb6 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/oiio/fuzzing/fuzz_oiio.bin+0x30fb6) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#16 0x55e762205dd2 in main (/oiio/fuzzing/fuzz_oiio.bin+0x5add2) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25)
#17 0x7f643ecc9d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-buffer-overflow (/oiio/fuzzing/fuzz_oiio.bin+0x74f63) (BuildId: e9d97e110da8ca7129ca0569fb37dfa703dccc25) in __interceptor_memcpy
Shadow bytes around the buggy address:
0x0c047fff8000: fa fa fd fa fa fa fd fd fa fa fd fa fa fa fd fa
0x0c047fff8010: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
0x0c047fff8020: fa fa fd fd fa fa fd fd fa fa 00 00 fa fa fd fa
0x0c047fff8030: fa fa fd fa fa fa fd fd fa fa 00 06 fa fa fd fd
0x0c047fff8040: fa fa 00 04 fa fa 04 fa fa fa fd fd fa fa 00 02
=>0x0c047fff8050: fa fa 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff80a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==61746==ABORTING
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.