Talos Vulnerability Report

TALOS-2018-0694

MKVToolNix MKVINFO read_one_element code execution vulnerability

October 26, 2018
CVE Number

CVE-2018-4022

Summary

A use-after-free vulnerability exists in the way MKVToolNix MKVINFO v25.0.0 handles the MKV (matroska) file format. A specially crafted MKV file can cause arbitrary code execution in the context of the current user.

Tested Versions

MKVToolNix mkvinfo v25.0.0 (‘Prog Noir’) 64-bit

Product URLs

https://mkvtoolnix.download

CVSSv3 Score

7.3 - AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-416: Use After Free

Details

This vulnerability can be triggered by providing the user a specifically crafted MKV file to test with the MKVINFO tool.

While reading a new element, the parser attempts to validate the current element by checking if it has a particular valid value. If there is no such value [1], then the parser deletes the element since the read was invalid [2].

444         try {
445           ElementLevelA->Read(inDataStream, EBML_CONTEXT(ElementLevelA), UpperEltFound, FoundElt, AllowDummyElt, ReadFully); // [3]
446         } catch (...) {
447           delete ElementLevelA;
448           throw;
449         }
450
451         // Discard elements that couldn't be read properly if
452         // SCOPE_ALL_DATA has been requested. This can happen
453         // e.g. if block data is defective.
454         bool DeleteElement = true;
455
456         if (ElementLevelA->ValueIsSet() || (ReadFully != SCOPE_ALL_DATA)) { [1]
457           ElementList.push_back(ElementLevelA);
459           DeleteElement = false;
460         }
461
462         // just in case
463         if (ElementLevelA->IsFiniteSize()) {
464           ElementLevelA->SkipData(inDataStream, EBML_CONTEXT(ElementLevelA));
465           if (DeleteElement)
466             delete ElementLevelA; // [2]
467         } else {
...
479           break;

Even though the element is deleted, the value is passed back to the calling function via the l2 variable [4]. However there is no validation, even if this element is valid and was not freed before.

src/common/kax_file.cpp:152~164
152 auto l2 = static_cast<EbmlElement *>(nullptr);
153  try {
154    l1->Read(*m_es.get(), EBML_INFO_CONTEXT(*callbacks), upper_lvl_el, l2, true); [4]
155    if (!found_in(*l1, l2)) [5]
156      delete l2;
157
158  } catch (std::runtime_error &e) {
159    mxdebug_if(m_debug_resync, boost::format("exception reading element data: %1%\n") % e.what());
160    m_in.setFilePointer(l1->GetElementPosition() + 1);
161    if (!found_in(*l1, l2))
162      delete l2;
163    return {};
164  }

If the function “found_in” returns false [5], mkvinfo will try to delete the l2 EbmlElement. The found_in function, will return false because the element was never added to the ElementList. It is possible to forge a file such that the function l1->Read (line 154; EbmlMaster::Read) will free the element so another delete operation (line 156) will create a classic use-after-free vulnerability.

This situation is confirmed by valgrind:

==5888== Invalid read of size 8
==5888==    at 0x2254C7: kax_file_c::read_one_element() (kax_file.cpp:156)
==5888==    by 0x224E4B: kax_file_c::read_next_level1_element_internal(unsigned int) (kax_file.cpp:105)
==5888==    by 0x224466: kax_file_c::read_next_level1_element(unsigned int, bool) (kax_file.cpp:51)
==5888==    by 0x18BBB9: mtx::kax_info_c::handle_segment(libebml::EbmlElement*) (kax_info.cpp:1149)
==5888==    by 0x18CBC5: mtx::kax_info_c::process_file() (kax_info.cpp:1299)
==5888==    by 0x18D001: mtx::kax_info_c::open_and_process_file() (kax_info.cpp:1355)
==5888==    by 0x18CDC6: mtx::kax_info_c::open_and_process_file(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (kax_info.cpp:1318)
==5888==    by 0x14918D: main (mkvinfo.cpp:50)
==5888==  Address 0x8c8b940 is 0 bytes inside a block of size 160 free'd
==5888==    at 0x4C3123B: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5888==    by 0x298750: libmatroska::KaxSimpleBlock::~KaxSimpleBlock() (KaxBlock.h:298)
==5888==    by 0x2C2C6C: libebml::EbmlMaster::Read(libebml::EbmlStream&, libebml::EbmlSemanticContext const&, int&, libebml::EbmlElement*&, bool, libebml::ScopeMode) (EbmlMaster.cpp:463)
==5888==    by 0x225480: kax_file_c::read_one_element() (kax_file.cpp:154)

=5888== Process terminating with default action of signal 11 (SIGSEGV)
==5888==  Bad permissions for mapped region at address 0x0
==5888==    at 0x0: ???
==5888==    by 0x224E4B: kax_file_c::read_next_level1_element_internal(unsigned int) (kax_file.cpp:105)
==5888==    by 0x224466: kax_file_c::read_next_level1_element(unsigned int, bool) (kax_file.cpp:51)
==5888==    by 0x18BBB9: mtx::kax_info_c::handle_segment(libebml::EbmlElement*) (kax_info.cpp:1149)
==5888==    by 0x18CBC5: mtx::kax_info_c::process_file() (kax_info.cpp:1299)
==5888==    by 0x18D001: mtx::kax_info_c::open_and_process_file() (kax_info.cpp:1355)
==5888==    by 0x18CDC6: mtx::kax_info_c::open_and_process_file(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (kax_info.cpp:1318)
==5888==    by 0x14918D: main (mkvinfo.cpp:50)

And finally in GDB:

Program received signal SIGSEGV, Segmentation fault.
0x000000000000008b in ?? ()

(gdb) info r $rax
rax            0x8b	139
(gdb) info r $rip
rip            0x8b	0x8b
(gdb) x/3i *(unsigned long long*)$rsp-2
   0x555555671514 <kax_file_c::read_one_element()+592>:	call   rax
   0x555555671516 <kax_file_c::read_one_element()+594>:	lea    rax,[rbp-0x230]
   0x55555567151d <kax_file_c::read_one_element()+601>:	mov    rdi,rax

   0x5555556714f9 <kax_file_c::read_one_element()+565>:	mov    rdx,QWORD PTR [rbp-0x258]
   0x555555671500 <kax_file_c::read_one_element()+572>:	mov    rax,QWORD PTR [rbp-0x258]
   0x555555671507 <kax_file_c::read_one_element()+579>:	mov    rax,QWORD PTR [rax]
   0x55555567150a <kax_file_c::read_one_element()+582>:	add    rax,0x8
   0x55555567150e <kax_file_c::read_one_element()+586>:	mov    rax,QWORD PTR [rax]
   0x555555671511 <kax_file_c::read_one_element()+589>:	mov    rdi,rdx
   0x555555671514 <kax_file_c::read_one_element()+592>:	call   rax
   0x555555671516 <kax_file_c::read_one_element()+594>:	lea    rax,[rbp-0x230] 

Crash Information

Program received signal SIGSEGV, Segmentation fault.
0x000000000000008b in ?? ()
(gdb) bt
#0  0x000000000000008b in ?? ()
#1  0x0000555555671516 in kax_file_c::read_one_element (this=0x555555a2ec80)
    at src/common/kax_file.cpp:156
#2  0x0000555555670e8c in kax_file_c::read_next_level1_element_internal (this=0x555555a2ec80, 
    wanted_id=0) at src/common/kax_file.cpp:105
#3  0x00005555556704a7 in kax_file_c::read_next_level1_element (this=0x555555a2ec80, 
    wanted_id=0, report_cluster_timestamp=false) at src/common/kax_file.cpp:51
#4  0x00005555555d7bfa in mtx::kax_info_c::handle_segment (this=0x7fffffffde10, l0=
    0x555555a434f0) at src/common/kax_info.cpp:1149
#5  0x00005555555d8c06 in mtx::kax_info_c::process_file (this=0x7fffffffde10)
    at src/common/kax_info.cpp:1299
#6  0x00005555555d9042 in mtx::kax_info_c::open_and_process_file (this=0x7fffffffde10)
    at src/common/kax_info.cpp:1355
#7  0x00005555555d8e07 in mtx::kax_info_c::open_and_process_file (this=0x7fffffffde10, 
    file_name="./eip.mkv") at src/common/kax_info.cpp:1318
#8  0x00005555555951ce in main (argc=2, argv=0x7fffffffe068) at src/info/mkvinfo.cpp:50
(gdb) x/10i 0x0000555555671516
   0x555555671516 <kax_file_c::read_one_element()+594>:	lea    -0x230(%rbp),%rax
   0x55555567151d <kax_file_c::read_one_element()+601>:	mov    %rax,%rdi
   0x555555671520 <kax_file_c::read_one_element()+604>:	
    callq  0x5555555e9808 <std::__shared_ptr_access<libebml::EbmlElement, (__gnu_cxx::_Lock_policy)2, false, false>::operator*() const>
   0x555555671525 <kax_file_c::read_one_element()+609>:	mov    %rax,%rdi
   0x555555671528 <kax_file_c::read_one_element()+612>:	
    callq  0x5555556728f2 <kax_file_c::get_element_size(libebml::EbmlElement&)>
   0x55555567152d <kax_file_c::read_one_element()+617>:	mov    %rax,-0x250(%rbp)
   0x555555671534 <kax_file_c::read_one_element()+624>:	mov    -0x270(%rbp),%rax
   0x55555567153b <kax_file_c::read_one_element()+631>:	add    $0x78,%rax
   0x55555567153f <kax_file_c::read_one_element()+635>:	mov    %rax,%rdi
   0x555555671542 <kax_file_c::read_one_element()+638>:	callq  0x555555614e8e
     <debugging_option_c::operator bool() const>
     
(gdb) info r
rax            0x8b	139
rbx            0x555555a43a80	93824997407360
rcx            0x0	0
rdx            0x555555a43a80	93824997407360
rsi            0x7fffffffd258	140737488343640
rdi            0x555555a43a80	93824997407360
rbp            0x7fffffffd500	0x7fffffffd500
rsp            0x7fffffffd288	0x7fffffffd288
r8             0x555555aa4800	93824997804032
r9             0x555555a0b780	93824997177216
r10            0x555555a336a0	93824997340832
r11            0x246	582
r12            0x55555570e8b8	93824994044088
r13            0x555555a0f340	93824997192512
r14            0x0	0
r15            0x0	0
rip            0x8b	0x8b
eflags         0x10202	[ IF RF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0         

Timeline

2018-10-25 - Vendor Disclosure
2018-10-25 - Vendor Patched
2018-10-26 - Public Release

Credit

Discovered by Piotr Bania, Cory Duplantis and Martin Zeiser of Cisco Talos.