CVE-2018-4022
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.
MKVToolNix mkvinfo v25.0.0 (‘Prog Noir’) 64-bit
7.3 - AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H
CWE-416: Use After Free
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]
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
2018-10-25 - Vendor Disclosure
2018-10-25 - Vendor Patched
2018-10-26 - Public Release
Discovered by Piotr Bania, Cory Duplantis and Martin Zeiser of Cisco Talos.