CVE-2019-5019
A heap overflow vulnerability exists in the PowerPoint document conversion function of Rainbow PDF Office Server Document Converter V7.0 Pro R1 (7,0,2018,1113). While parsing Document Summary Property Set stream, the getSummaryInformation function is incorrectly checking the correlation between size and the number of properties in PropertySet packets, causing an out-of-bounds write that leads to heap corruption and consequent code execution.
Antenna House Rainbow PDF Office Server Document Converter v7.0 Pro R1 for Linux64 (7,0,2018,1113)
https://www.rainbowpdf.com/trial-server-solutions/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-122: Heap-based Buffer Overflow
Rainbow PDF is a software solution, developed by Antenna House, that converts Microsoft office 97-2016 documents into a PDF.
Office document structures are sometimes complex that contain strict restraints. Not enforcing such constraints may lead to several sides effects while parsing.
The Microsoft documentation MS-OLEPS Object Linking and Embedding (OLE) is explaining some of the structures that are needed to understand the issue described in this advisory. In particular, see below the format for PropertySetStream
, PropertySet
and PropertyIdentifierAndOffset
packets.
The PropertySetStream
is described as follow:
+ ByteOrder (2 bytes): MUST be set to 0xFFFE.
+ Version (2 bytes)
+ SystemIdentifier (4 bytes)
+ CLSID (16 bytes)
+ NumPropertySets (4 bytes): An unsigned integer indicating the number of property sets
+ [...] some other fields in this structure.
+ PropertySet: MUST be a PropertySet packet described here just after.
+ [...] some other fields in this structure.
The PropertySet
packet is described as follow:
+ size (4 bytes): MUST be the total size in bytes of the PropertySet packet.
+ NumProperties (4 bytes): An unsigned integer representing the number of properties in the property set.
+ PropertyIdentifierAndOffset 0 (variable): All `PropertyIdentifierAndOffset` fields MUST be a sequence of `PropertyIdentifierAndOffset` packets.
+ Property 0 (variable): Each Property field is a sequence of property values, each of which MUST be represented by a TypedPropertyValue packet
The size
field specifies it must be the total size in bytes. This is important because the vulnerability depends on the value of this field.
The PropertyIdentifierAndOffset
packet format is described as follow:
+ PropertyIdentifier (4 bytes): An unsigned integer representing the property identifier of a property in the property set
+ Offset (4 bytes): An unsigned integer representing the offset in bytes from the beginning of the PropertySet packet to the beginning of the Property field for the property represented.
The PropertySetStream
is a succession of PropertySet
which themselves contain a succession of PropertyIdentifierAndOffset
.
The function OleCompNS::AHOleSummaryInformation::getSummaryInformation
called to parse Microsoft Office PowerPoint Summary Information and Document Summary Information details and data.
The function has been stripped and is more complex than what is following, but the important lines are kept to understand the logic.
__int64 __fastcall OleCompNS::AHOleSummaryInformation::getSummaryInformation(AHOleSummaryInformation *this)
{
SummaryInformationdataStreamOffset = OleCompNS::AHOleCompStream::OLEseek( this->AHOleCompStreamObj, 0LL, 1LL);
if ( OleCompNS::AHOleSummaryInformation::getPropHeader(this) )
{
[...]
if ( this->NumPropertySets ) [1]
{
return_value = 1;
lastPropertySet = (__int64)&heapAllocated[7 * (unsigned int)(this->NumPropertySets - 1) + 8];
while ( 1 ) [2]
{
if ( !OleCompNS::AHOleSummaryInformation::getCLSID(this, (PropertySetStreamPacket + 5))
|| !OleCompNS::AHOleSummaryInformation::getDWORD(this, PropertySetStreamPacket + 9) )
{
goto return_failed;
}
PropertySetStream_offset0 = OleCompNS::AHOleCompStream::OLEtell();
PropertySetStreamPacket[4] = OleCompNS::AHOleCompStream::OLEseek(this->AHOleCompStreamObj, PropertySetStreamPacket[9]
+ SummaryInformationdataStreamOffset,0LL);
if ( OleCompNS::AHOleSummaryInformation::getDWORD(this, PropertySetStreamPacket) [5]
&& OleCompNS::AHOleSummaryInformation::getDWORD(this, PropertySetStreamPacket + 1) ) [6]
{
heapPropertySets = new(16LL * *PropertySetStreamPacket); [7]
*((_QWORD *)PropertySetStreamPacket + 1) = heapPropertySets;
NumProperties = PropertySetStreamPacket[1];
PropertyIdentifierAndOffsetNum = NumProperties - 1; [8]
if ( !NumProperties )
goto nextPropertySet; [12]
while ( OleCompNS::AHOleSummaryInformation::getDWORD(this, heapPropertySets) ) [13]
{
if ( !OleCompNS::AHOleSummaryInformation::getDWORD(this, heapPropertySets + 1) )
break;
currentOffset = OleCompNS::AHOleCompStream::OLEtell()
[...]
nextPropertyIdentifierAndOffsetEntry: [10]
heapPropertySets += 4; [11]
OleCompNS::AHOleCompStream::OLEseek( this->AHOleCompStreamObj, currentOffset, 0LL);
if ( !PropertyIdentifierAndOffsetNum )
goto nextPropertySet;
--PropertyIdentifierAndOffsetNum; [9]
}
}
return_value = -1;
nextPropertySet:
PropertySetStreamPacket += 14;
OleCompNS::AHOleCompStream::OLEseek(this->AHOleCompStreamObj, PropertySetStream_offset0, 0LL);
if ( PropertySetStreamPacket == lastPropertySet )
return return_value;
}
}
return_value = 1;
}
else
{
return_failed:
return_value = -1;
}
return return_value;
}
OleCompNS::AHOleSummaryInformation::getSummaryInformation
is processing the PropertySetStream
using the number of PropertySet
[1] as condition and relying on an infinite loop [2], which breaks on severals conditions to process all PropertySet
data and PropertyIdentifierAndOffset
accordingly.
bool __fastcall OleCompNS::AHOleSummaryInformation::getDWORD(AHOleCompStream *this, unsigned int *buffer) [3]
{
bool result; // al
if ( SBYTE4(this->getReserve) )
result = OleCompNS::AHOleSummaryInformation::getReverse(this, buffer, 4);
else
result = ( OleCompNS::AHOleCompStream::OLEread(this, buffer, 4LL) == 4); [4]
return result;
}
The function OleCompNS::AHOleSummaryInformation::getSummaryInformation
uses the function OleCompNS::AHOleSummaryInformation::getDWORD
to parse data from file. Note that the function OleCompNS::AHOleSummaryInformation::getDWORD
[3] simply reads a DWORD from the object passed as first parameter and stores it into the buffer pointed by the second parameter [4], while reading the file using the leCompNS::AHOleCompStream::OLEread
function.
At lines [5] and [6], we can see code reading size
and NumProperties
for PropertySet
packet respectively from file. Thus the size
stored into PropertySetStreamPacket
pointer is reused at line [7] to allocate memory for PropertyIdentifierAndOffset
packet and pointer, named heapPropertySets
, while the NumProperties
is reassigned to the variable PropertyIdentifierAndOffsetNum
at [8], which will be used to determine the boundary at [9].
Every time a new PropertyIdentifierAndOffset
is parsed, the code jumps to nextPropertyIdentifierAndOffsetEntry
[10], which makes the heapPropertySets
pointer to be incremented by 4 [11]. The number of times this happens depends on the value of NumProperties
, which is also used for loop termination [12].
Since at [11] the heapPropertySets
pointer is incremented without checking the boundaries of the pointed buffer, a subsequent heap out-of-bounds write may occur at [13]. Thus, by keeping the size
field small and by controlling the value of NumProperties
, an attacker would be able to control the write at [13] and subsequently execute arbitrary code.
2019-02-13 - Vendor Disclosure
2019-02-27- Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.