Headline
CVE-2020-6156: TALOS-2020-1094 || Cisco Talos Intelligence Group
A heap overflow vulnerability exists in Pixar OpenUSD 20.05 when the software parses compressed sections in binary USD files. To trigger this vulnerability, the victim needs to open an attacker-provided malformed file in an instance USDC file format path element token index.
CVE-2020-6147, CVE-2020-6148, CVE-2020-6149, CVE-2020-6150, CVE-2020-6156, CVE-2020-13493
Summary
A heap overflow vulnerability exists in Pixar OpenUSD 20.05 when the software parses compressed sections in binary USD files. A specially crafted malformed file can trigger a heap overflow which can result in remote code execution. To trigger this vulnerability, the victim needs to open an attacker-provided malformed file.
Tested Versions
Pixar OpenUSD 20.05
Apple macOS Catalina 10.15.3
Product URLs
https://openusd.org
CVSSv3 Score
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE
CWE-122 - Heap-based Buffer Overflow
Details
OpenUSD stands for “Open Universal Scene Descriptor” and is a software suite by Pixar that facilitates, among other things, the interchange of arbitrary 3-D scenes that may be composed of many elemental assets.
Most notably, USD and its backing file format usd are used on Apple iOS and macOS as part of the ModelIO framework in support of SceneKit and ARKit for sharing and displaying 3-D scenes in, for example, augmented reality applications. On macOS, these files are automatically rendered to generate thumbnails, while on iOS they can be shared via iMessage and opened with user interaction.
USD binary file format consists of a header pointing to a table of contents that in turn points to individual sections that comprise the whole file. Format specifies that if the format version is 4 or higher, sections content is compressed. Four instances of a heap overflow vulnerability exist in a way USD processes compressed data of specific sections. These are FIELDS,FIELDSETS, PATHS and SPECS.
CVE-2020-6147 - USDC file format FIELDS section decompression heap overflow
Following code is responsible for decoding the FIELDS section data (from pxr/usd/usd/crateFile.cpp):
if (auto fieldsSection = _toc.GetSection(_FieldsSectionName)) {
reader.Seek(fieldsSection->start); [1]
if (Version(_boot) < Version(0,4,0)) { [2]
_fields = reader.template Read<decltype(_fields)>();
} else {
// Compressed fields in 0.4.0.
auto numFields = reader.template Read<uint64_t>(); [3]
_fields.resize(numFields);
// Create temporary space for decompressing.
std::unique_ptr<char[]> compBuffer(
new char[Usd_IntegerCompression::
GetCompressedBufferSize(numFields)]); [4]
vector<uint32_t> tmp(numFields);
auto fieldsSize = reader.template Read<uint64_t>(); [5]
reader.ReadContiguous(compBuffer.get(), fieldsSize); [6]
In the above code, parser navigates to the beginning of the FIELDS section at [1]. If version check at [2] resolves that compressed fields are used, a 64 bit value of numFields is read at [3] and is subsequently used to calculate the size of the buffer at [4]. Another 64 bit value , fieldSize is read at [5] which is subsequently used to initiate a buffer read at [6]. Data from the file is read into buffer that is sized based on numFields, but is bounded by fieldsSize. Both values are under direct control and no check is performed to insure fieldsSize bytes can fit into the allocated buffer. This can result in buffer overflow on the heap.
CVE-2020-6148 - USDC file format FIELDSETS section decompression heap overflow
Following code is responsible for decoding the FIELDSETS section data (from pxr/usd/usd/crateFile.cpp):
// Compressed fieldSets in 0.4.0.
auto numFieldSets = reader.template Read<uint64_t>(); [7]
_fieldSets.resize(numFieldSets);
// Create temporary space for decompressing.
std::unique_ptr<char[]> compBuffer(
new char[Usd_IntegerCompression::
GetCompressedBufferSize(numFieldSets)]); [8]
vector<uint32_t> tmp(numFieldSets);
std::unique_ptr<char[]> workingSpace(
new char[Usd_IntegerCompression::
GetDecompressionWorkingSpaceSize(numFieldSets)]);
auto fsetsSize = reader.template Read<uint64_t>(); [9]
reader.ReadContiguous(compBuffer.get(), fsetsSize); [10]
Like in the previous vulnerability, parsing the FIELDSETS section starts with reading numFiledSets at [7] which is used to allocate a properly sized buffer at [8]. Then, fsetsSize 64 bit value is read at [9] and used as a read size argument to a file buffer read at [10]. Destination buffer size is calculated based on numFieldSets, but file read at [10] reads fsetsSize bytes. This constitutes an buffer overflow on the heap.
CVE-2020-6149 - USDC file format PATHS section decompression heap overflow
Following code is responsible for decoding the PATHS section data (from pxr/usd/usd/crateFile.cpp):
_paths.resize(reader.template Read<uint64_t>()); [11]
std::fill(_paths.begin(), _paths.end(), SdfPath());
WorkArenaDispatcher dispatcher;
// VERSIONING: PathItemHeader changes size from 0.0.1 to 0.1.0.
Version fileVer(_boot);
if (fileVer == Version(0,0,1)) {
_ReadPathsImpl<_PathItemHeader_0_0_1>(reader, dispatcher);
} else if (fileVer < Version(0,4,0)) {
_ReadPathsImpl<_PathItemHeader>(reader, dispatcher);
} else { [12]
// 0.4.0 has compressed paths.
_ReadCompressedPaths(reader, dispatcher);
}
First, a 64 bit value of number of paths is read at [11] and a familiar file version check is performed. If the file version is 4 or higher, code at [12] proceeds to call _ReadCompressedPaths :
size_t numPaths = reader.template Read<uint64_t>(); [13]
pathIndexes.resize(numPaths);
elementTokenIndexes.resize(numPaths);
jumps.resize(numPaths);
// Create temporary space for decompressing.
std::unique_ptr<char[]> compBuffer(
new char[Usd_IntegerCompression::GetCompressedBufferSize(numPaths)]); [14]
std::unique_ptr<char[]> workingSpace(
new char[Usd_IntegerCompression::
GetDecompressionWorkingSpaceSize(numPaths)]);
// pathIndexes.
auto pathIndexesSize = reader.template Read<uint64_t>(); [15]
reader.ReadContiguous(compBuffer.get(), pathIndexesSize); [16]
Again, a 64 bit value of numPaths is read into a size_t typed variable at [13] and is then used to calculate the size of buffer allocation at [14]. Another 64 bit value of pathIndexesSize is read at [15] and is subsequently used as a buffer read size value at [16]. Since the destination buffer size is based on numPaths value, but file read size on pathIndexesSize, buffer can be made too small to fit the whole read which will result in a buffer overflow on the heap.
CVE-2020-6150 - USDC file format SPECS section decompression heap overflow
Following code is responsible for decoding the SPECS section data (from pxr/usd/usd/crateFile.cpp):
// Version 0.4.0 specs are compressed
auto numSpecs = reader.template Read<uint64_t>(); [17]
_specs.resize(numSpecs);
// Create temporary space for decompressing.
std::unique_ptr<char[]> compBuffer(
new char[Usd_IntegerCompression::
GetCompressedBufferSize(numSpecs)]); [18]
vector<uint32_t> tmp(_specs.size());
std::unique_ptr<char[]> workingSpace(
new char[Usd_IntegerCompression::
GetDecompressionWorkingSpaceSize(numSpecs)]);
// pathIndexes.
auto pathIndexesSize = reader.template Read<uint64_t>(); [19]
reader.ReadContiguous(compBuffer.get(), pathIndexesSize); [20]
As with previous vulnerabilities, a 64 bit value is read into numSpecs at [17] which is used to calculate the size of the buffer at [18]. Actual size of the section data is read at [19] into a 64 bit value pathIndexesSize. Notice the reuse of variable name, indicating code copy/pasting. This size value is used as a file read size at [20] and can result in a heap based buffer overflow if buffer allocated at [18] is too small.
CVE-2020-6156 - USDC file format path element token index decompression heap overflow
Section PATHS in the USDC binary file contains three arrays, each encoding parts of a path value. Similarly to already reported CVE-2020-6149, a heap buffer overflow vulnerability exists in a way compressed path elements are processed. Following code is invoked :
// Read number of encoded paths.
size_t numPaths = reader.template Read<uint64_t>(); [1]
pathIndexes.resize(numPaths);
elementTokenIndexes.resize(numPaths);
jumps.resize(numPaths);
// Create temporary space for decompressing.
std::unique_ptr<char[]> compBuffer(
new char[Usd_IntegerCompression::GetCompressedBufferSize(numPaths)]); [2]
std::unique_ptr<char[]> workingSpace(
new char[Usd_IntegerCompression::
GetDecompressionWorkingSpaceSize(numPaths)]);
// pathIndexes.
auto pathIndexesSize = reader.template Read<uint64_t>();
reader.ReadContiguous(compBuffer.get(), pathIndexesSize);
Usd_IntegerCompression::DecompressFromBuffer(
compBuffer.get(), pathIndexesSize, pathIndexes.data(), numPaths,
workingSpace.get());
// elementTokenIndexes.
auto elementTokenIndexesSize = reader.template Read<uint64_t>(); [3]
reader.ReadContiguous(compBuffer.get(), elementTokenIndexesSize); [4]
Usd_IntegerCompression::DecompressFromBuffer(
compBuffer.get(), elementTokenIndexesSize,
elementTokenIndexes.data(), numPaths, workingSpace.get());
First, at [1] , a total number of paths to be reconstructed is read. This value is used to calculate the size of compressed buffer at [2]. Then, at [3] , a compressed size of element token indices is read at [3] and a large buffer read at [4]. Notice that while the compBuffer is sized based on value read from [1], the value in elementTokenIndexSize is used as a read size argument. Both of these value come directly frome the file and, if mismatched, can result in a heap buffer overflow where both allocation and overflow size , as well as overflow contents are under direct control.
CVE-2020-13493 - USDC file format path jumps decompression heap overflow
Similarly to above, a heap buffer overflow vulnerability exists in a way path jumps are processed. Following code is invoked right after the previously discussed:
// jumps.
auto jumpsSize = reader.template Read<uint64_t>(); [5]
reader.ReadContiguous(compBuffer.get(), jumpsSize); [6]
Usd_IntegerCompression::DecompressFromBuffer(
compBuffer.get(), jumpsSize, jumps.data(), numPaths,
workingSpace.get());
As in the previous case, destination buffer compBuffer size is calculated based on calculation at [2], but a second value read at [5] is used as a size argument to a ReadContiguous call at [6]. If the allocated buffer is smaller than the size read at [6], this will lead to a heap based buffer overflow.
In this case , the potential attacker controls both the size of allocated memory buffer, the size of the overflow as well as all the data of the overflow which comes directly from the file being read. With proper memory manipulation and control, this can lead to arbitrary code execution.
Crash Information
Including crash context for only one instance of the vulnerability. Tested on latest version of macOS Catalina 10.15.3.
thread #2, queue = 'com.apple.quicklook.thumbnailservicecontext.thumbnailgeneration', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00007fff6f4f0930 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 496
frame #1: 0x00007fff3ded2644 ModelIO`___lldb_unnamed_symbol96151$$ModelIO + 158
frame #2: 0x00007fff3ded1d67 ModelIO`___lldb_unnamed_symbol96148$$ModelIO + 341
frame #3: 0x00007fff3debf7ef ModelIO`___lldb_unnamed_symbol95659$$ModelIO + 867
frame #4: 0x00007fff3debf273 ModelIO`___lldb_unnamed_symbol95658$$ModelIO + 517
frame #5: 0x00007fff3debee89 ModelIO`___lldb_unnamed_symbol95657$$ModelIO + 417
frame #6: 0x00007fff3debe40d ModelIO`___lldb_unnamed_symbol95646$$ModelIO + 599
frame #7: 0x00007fff3dfc558d ModelIO`___lldb_unnamed_symbol101370$$ModelIO + 261
frame #8: 0x00007fff3df7177e ModelIO`___lldb_unnamed_symbol100320$$ModelIO + 258
frame #9: 0x00007fff3d95c195 ModelIO`___lldb_unnamed_symbol34985$$ModelIO + 1221
frame #10: 0x00007fff3d95b321 ModelIO`___lldb_unnamed_symbol34982$$ModelIO + 2545
frame #11: 0x00007fff3d95a593 ModelIO`___lldb_unnamed_symbol34979$$ModelIO + 867
frame #12: 0x00007fff3df85979 ModelIO`___lldb_unnamed_symbol100531$$ModelIO + 209
frame #13: 0x00007fff3df85ae0 ModelIO`___lldb_unnamed_symbol100532$$ModelIO + 200
frame #14: 0x00007fff3d61231a ModelIO`___lldb_unnamed_symbol2606$$ModelIO + 386
frame #15: 0x00007fff3d592ed6 ModelIO`___lldb_unnamed_symbol670$$ModelIO + 794
frame #16: 0x00007fff3d592b17 ModelIO`___lldb_unnamed_symbol669$$ModelIO + 97
frame #17: 0x00007fff43f488d4 SceneKit`loadMDLAssetWithURL + 113
frame #18: 0x00007fff43dbe878 SceneKit`-[SCNSceneSource _createSceneRefWithOptions:statusHandler:] + 788
frame #19: 0x00007fff43dbe4a5 SceneKit`-[SCNSceneSource _sceneWithClass:options:statusHandler:] + 1525
frame #20: 0x00007fff43dbdcd6 SceneKit`-[SCNSceneSource sceneWithClass:options:statusHandler:] + 117
frame #21: 0x00007fff43dbdc45 SceneKit`-[SCNSceneSource sceneWithClass:options:error:] + 88
frame #22: 0x0000000102ab25c9 SceneKitQLThumbnailExtension`___lldb_unnamed_symbol1$$SceneKitQLThumbnailExtension + 414
frame #23: 0x00007fff438e8fb2 QuickLookThumbnailing`__145-[QLThumbnailServiceContext generateThumbnailOfSize:minimumSize:scale:badgeType:withFileURLHandler:additionalResourcesWrapper:completionHandler:]_block_invoke + 432
frame #24: 0x00007fff6f2a0583 libdispatch.dylib`_dispatch_call_block_and_release + 12
Nearby code:
libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell:
-> 0x7fff6f4f0930 <+496>: c5 fc 10 46 e0 vmovups ymm0, ymmword ptr [rsi - 0x20]
0x7fff6f4f0935 <+501>: 49 89 fb mov r11, rdi
0x7fff6f4f0938 <+504>: 48 83 ef 01 sub rdi, 0x1
0x7fff6f4f093c <+508>: 48 83 e7 e0 and rdi, -0x20
0x7fff6f4f0940 <+512>: 4c 89 d9 mov rcx, r11
0x7fff6f4f0943 <+515>: 48 29 f9 sub rcx, rdi
0x7fff6f4f0946 <+518>: 48 29 ce sub rsi, rcx
0x7fff6f4f0949 <+521>: 48 29 ca sub rdx, rcx
0x7fff6f4f094c <+524>: c5 fc 10 4e e0 vmovups ymm1, ymmword ptr [rsi - 0x20]
0x7fff6f4f0951 <+529>: c4 c1 7c 11 43 e0 vmovups ymmword ptr [r11 - 0x20], ymm0
rax = 0x00007ffa0b55eb10
rbx = 0x0000000000000000
rcx = 0x4141414141414141
rdx = 0x4141414141414141
rsi = 0x414141424400d36f
rdi = 0x4141c13b4c972c51
r8 = 0x4141414141414141
r9 = 0x0000000000000000
r10 = 0x00000000fffbffff
r11 = 0x00007ff9089658e2
r12 = 0x0000700008c499a8
r13 = 0x0000700008c499a8
r14 = 0x4141414141414141
r15 = 0x00007ffa0b55eb10
rsp = 0x0000700008c498e0
rbp = 0x0000700008c498e0
rip = 0x00007fff6f4f0930 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 496
fla = o d I t s z a p c
Timeline
2020-07-01 - Vendor Disclosure to Pixar & Apple
2020-11-12 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.
Related news
A heap overflow vulnerability exists in Pixar OpenUSD 20.05 when the software parses compressed sections in binary USD files. A specially crafted USDC file format path jumps decompression heap overflow in a way path jumps are processed. To trigger this vulnerability, the victim needs to open an attacker-provided malformed file.
This issue was addressed by encrypting communications over the network to devices running iOS 14, iPadOS 14, tvOS 14, and watchOS 7. This issue is fixed in iOS 14.0 and iPadOS 14.0, Xcode 12.0. An attacker in a privileged network position may be able to execute arbitrary code on a paired device during a debug session over the network.