Headline
CVE-2020-18651: A heap-based buffer over-read was found in ID3_Support.cpp (#13) · Issues · libopenraw / exempi · GitLab
Buffer Overflow vulnerability in function ID3_Support::ID3v2Frame::getFrameValue in exempi 2.5.0 and earlier allows remote attackers to cause a denial of service via opening of crafted audio file with ID3V2 frame.
poc
0x00 Introduction
In exempi 2.5.0, I found a heap-based buffer over-read bug in function ID3_Support::ID3v2Frame::getFrameValue, the ASAN report is as below.
liwc@ubuntu:~/exempi-master_asan/exempi$ ./exempi -x poc
processing file poc
dump_xmp for file poc
=================================================================
==79549==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000eed1 at pc 0x0000004be2ae bp 0x7fffc062ec30 sp 0x7fffc062ec20
READ of size 4 at 0x60200000eed1 thread T0
#0 0x4be2ad in GetUns32BE ../../../source/EndianUtils.hpp:152
#1 0x4c2256 in ID3_Support::ID3v2Frame::getFrameValue(unsigned char, unsigned int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*) /home/liwc/exempi-master/XMPFiles/source/FormatSupport/ID3_Support.cpp:702
#2 0x4de2a0 in MP3_MetaHandler::ProcessXMP() /home/liwc/exempi-master/XMPFiles/source/FileHandlers/MP3_Handler.cpp:334
#3 0x48aafd in XMPFiles::GetXMP(TXMPMeta<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*, char const**, unsigned int*, XMP_PacketInfo*) /home/liwc/exempi-master/XMPFiles/source/XMPFiles.cpp:1471
#4 0x47fac8 in WXMPFiles_GetXMP_1 /home/liwc/exempi-master/XMPFiles/source/WXMPFiles.cpp:331
#5 0x41e353 in TXMPFiles<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::GetXMP(TXMPMeta<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, XMP_PacketInfo*) (/home/liwc/exempi-master_asan/exempi/exempi+0x41e353)
#6 0x40c0f1 in xmp_files_get_new_xmp /home/liwc/exempi-master/exempi/exempi.cpp:346
#7 0x408d07 in get_xmp_from_file /home/liwc/exempi-master/exempi/main.cpp:244
#8 0x408ece in dump_xmp /home/liwc/exempi-master/exempi/main.cpp:257
#9 0x409b54 in process_file /home/liwc/exempi-master/exempi/main.cpp:348
#10 0x408728 in main /home/liwc/exempi-master/exempi/main.cpp:194
#11 0x7f7b7b20482f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#12 0x407a58 in _start (/home/liwc/exempi-master_asan/exempi/exempi+0x407a58)
0x60200000eed3 is located 0 bytes to the right of 3-byte region [0x60200000eed0,0x60200000eed3)
allocated by thread T0 here:
#0 0x7f7b7c53b712 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99712)
#1 0x4c10c6 in ID3_Support::ID3v2Frame::read(XMP_IO*, unsigned char) /home/liwc/exempi-master/XMPFiles/source/FormatSupport/ID3_Support.cpp:579
#2 0x4dd4ae in MP3_MetaHandler::CacheFileData() /home/liwc/exempi-master/XMPFiles/source/FileHandlers/MP3_Handler.cpp:220
#3 0x48874f in DoOpenFile /home/liwc/exempi-master/XMPFiles/source/XMPFiles.cpp:1076
#4 0x488f8a in XMPFiles::OpenFile(char const*, unsigned int, unsigned int) /home/liwc/exempi-master/XMPFiles/source/XMPFiles.cpp:1179
#5 0x47e415 in WXMPFiles_OpenFile_1 /home/liwc/exempi-master/XMPFiles/source/WXMPFiles.cpp:233
#6 0x41dab4 in TXMPFiles<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::OpenFile(char const*, unsigned int, unsigned int) (/home/liwc/exempi-master_asan/exempi/exempi+0x41dab4)
#7 0x40bd79 in xmp_files_open_new /home/liwc/exempi-master/exempi/exempi.cpp:294
#8 0x408ccb in get_xmp_from_file /home/liwc/exempi-master/exempi/main.cpp:242
#9 0x408ece in dump_xmp /home/liwc/exempi-master/exempi/main.cpp:257
#10 0x409b54 in process_file /home/liwc/exempi-master/exempi/main.cpp:348
#11 0x408728 in main /home/liwc/exempi-master/exempi/main.cpp:194
#12 0x7f7b7b20482f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../source/EndianUtils.hpp:152 GetUns32BE
Shadow bytes around the buggy address:
0x0c047fff9d80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9d90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9db0: fa fa 00 fa fa fa 00 fa fa fa 00 fa fa fa 00 00
0x0c047fff9dc0: fa fa fd fd fa fa fd fa fa fa fd fa fa fa 00 00
=>0x0c047fff9dd0: fa fa 01 fa fa fa 04 fa fa fa[03]fa fa fa 03 fa
0x0c047fff9de0: fa fa 00 04 fa fa 05 fa fa fa 00 04 fa fa fd fd
0x0c047fff9df0: fa fa 00 05 fa fa fd fa fa fa 05 fa fa fa 00 00
0x0c047fff9e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e20: 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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
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
==79549==ABORTING
0x01 Analysis
I debugged the poc with gdb so that I found the key of this issue.
bool ID3v2Frame::getFrameValue ( XMP_Uns8 /*majorVersion*/, XMP_Uns32 logicalID, std::string* utf8string )
{
XMP_Assert ( (this->content != 0) && (this->contentSize >= 0) && (this->contentSize < 20*1024*1024) );
if ( this->contentSize == 0 ) {
utf8string->erase();
return true; // ...it is "of interest", even if empty contents.
}
XMP_Int32 pos = 0;
XMP_Uns8 encByte = 0;
// WCOP does not have an encoding byte, for all others: use [0] as EncByte, advance pos
if ( logicalID != 0x57434F50 ) { // pos1
encByte = this->content[0];
pos++;
}
// mode specific forks, COMM or USLT
bool commMode = ( (logicalID == 0x434F4D4D) || (logicalID == 0x55534C54) );
switch ( encByte ) { // pos2
case 0: //ISO-8859-1, 0-terminated
{
if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest!
char* localPtr = &this->content[pos];
size_t localLen = this->contentSize - pos;
ReconcileUtils::Latin1ToUTF8 ( localPtr, localLen, utf8string );
break;
}
case 1: // Unicode, v2.4: UTF-16 (undetermined Endianess), with BOM, terminated 0x00 00
case 2: // UTF-16BE without BOM, terminated 0x00 00
{
...
}
case 3: // UTF-8 unicode, terminated \0
{
if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest!
if ( (GetUns32BE ( &this->content[pos]) & 0xFFFFFF00 ) == 0xEFBBBF00 ) { // pos3
pos += 3; // swallow any BOM, just in case
}
utf8string->assign ( &this->content[pos], (this->contentSize - pos) );
break;
}
default:
XMP_Throw ( "unknown text encoding", kXMPErr_BadFileFormat ); //COULDDO assume latin-1 or utf-8 as best-effort
break;
}
return true;
} // ID3v2Frame::getFrameValue
At pos1, the logicalID did not equal 0x57434f50,so the if branch was satisfied and pos would incremented. Now the encByte was 0x3, the case 3 of switch statement at pos2 was satisfied. We noticed the size of heap buffer this->content was only 3 bytes and it started from 0x60200000eed0.
→ 655 if ( logicalID != 0x57434F50 ) {
656 encByte = this->content[0];
657 pos++;
658 }
659
660 // mode specific forks, COMM or USLT
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "exempi", stopped, reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
...
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ p logicalID
$1 = 0x54504f53
gef➤ p this->content
$2 = 0x60200000eed0 "\003\062\061"
Then it called the inline function GetUns32BE in EndianUtils.hpp:150 at pos3, and GetUns32BE read 4 bytes from the given addr. At this call site the given addr was 0x60200000eed1, so a 2-bytes heap-based buffer over-read happened.
We can patch this issue by check the length of this->content firstly.
0x02 Reproduce
OS:Ubuntu 16.04 x86_64
export CFLAGS="-fsanitize=address -ggdb"
export CXXFLAGS="-fsanitize=address -ggdb"
export LDFLAGS="-fsanitize=address -ggdb"
sudo apt install libboost-dev
sudo apt install libboost-test-dev
./autogen.sh
./configure --disable-shared
make
./exampi/exempi -x POC -o out
0x03 Discoverer
WenChao Li of VARAS@IIE