Talos Vulnerability Report

TALOS-2019-0780

Antenna House Rainbow PDF Office server document converter getSummaryInformation NumProperties code execution vulnerability

February 28, 2019
CVE Number

CVE-2019-5019

Summary

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.

Tested Versions

Antenna House Rainbow PDF Office Server Document Converter v7.0 Pro R1 for Linux64 (7,0,2018,1113)

Product URLs

https://www.rainbowpdf.com/trial-server-solutions/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-122: Heap-based Buffer Overflow

Details

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.

Timeline

2019-02-13 - Vendor Disclosure
2019-02-27- Public Release

Credit

Discovered by Emmanuel Tacheau of Cisco Talos.