Security
Headlines
HeadlinesLatestCVEs

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.

CVE
#vulnerability#ubuntu#linux#dos#git#buffer_overflow

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

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907