CVE-2020-6513
A memory corruption vulnerability exists in the way Google Chrome 83.0.4103.61 executes JavaScript inside PDF documents. A specially crafted web page can cause out of bounds memory access. To trigger this vulnerability, the victim must visit a malicious webpage or open a malicious PDF document.
Google Chrome 83.0.4103.61
https://www.google.com/chrome/
6.3 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L
CWE-125 - Out-of-bounds Read
Google Chrome is one of the most popular web browsers.
Pdfium is an open-source PDF renderer developed by Google and used extensively in the Chrome browser, online services as well as other standalone applications. This bug was triaged on the current release version, latest git version and the latest chromium address sanitizer build available.
PDFium supports execution of Javascript scripts embedded inside PDF documents. As Chrome itself, PDFium uses V8 as its Javascript engine. This particular vulnerability revolves around the definitions of this
object in a PDF document Javascript and the difference from regular Javascript execution environment. Depending on scope, the underlying meaning of this
object changes, but in a document level script it represents Doc
or the current document. The core of the proof of concept to trigger this vulnerability is:
this.__proto__ = app.activeDocs[0];
In this scope, this
represents the current document, and app.activeDocs[0] does as well. However, in PDFium context they are treated differently. We can observe the properties of both of these objects by enabling javascript debugging via --allow-natives-syntax
and adding the following prior to the above assignment:
DebugPrint(this);
DebugPrint(app.activeDocs[0]);
We can observe the following output that shows the difference:
0x7eaa08088755 <JSGlobal Object>
0x7eaa082a04f9 <JSObject>
The actual object properties are the same. An assignment of app.activeDocs[0]
to this.__proto__
would result in a circular __proto__
chain, which V8 does check for and it normally results in an error message such as:
TypeError: Cyclic __proto__ value
at Object.set __proto__ [as __proto__] (<anonymous>)
at test.js:6:13
However, in this case it will succeed. Trying to display the either this
or app.activeDocs[0]
objects, via app.alert()
for example, would result in an infinite loop when trying to resolve the objects.
The rest of the code in the PoC is there to cause specific V8 run time state that triggers memory corruption resulting in an out of bounds heap access. It is possible that this could be abused to cause further memory corruption and potentially lead to arbitrary code execution.
Address Sanitizer crash output:
=================================================================
==1608==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x604000000008 at pc 0x55c18397853e bp 0x7ffce83187d0 sp 0x7ffce83187c8
READ of size 8 at 0x604000000008 thread T0 (chrome_special)
#0 0x55c18397853d in size buildtools/third_party/libc++/trunk/include/vector:656:46
#1 0x55c18397853d in CollectionSize<int, std::__1::vector<std::__1::unique_ptr<CFXJS_ObjDefinition, std::__1::default_delete<CFXJS_ObjDefinition>>, std::__1::allocator<std::__1::unique_ptr<CFXJS_ObjDefinition, std::__1::default_delete<CFXJS_ObjDefinition>>>>> third_party/pdfium/third_party/base/stl_util.h:127:60
#2 0x55c18397853d in MaxObjDefinitionID third_party/pdfium/fxjs/cfxjs_engine.cpp:325:10
#3 0x55c18397853d in ObjDefinitionForID third_party/pdfium/fxjs/cfxjs_engine.cpp:332:27
#4 0x55c18397853d in CFXJS_Engine::NewFXJSBoundObject(int, FXJSOBJTYPE) third_party/pdfium/fxjs/cfxjs_engine.cpp:580:41
#5 0x55c1839a1a4a in CJS_Document::getField(CJS_Runtime*, std::__1::vector<v8::Local<v8::Value>, std::__1::allocator<v8::Local<v8::Value> > > const&) third_party/pdfium/fxjs/cjs_document.cpp:255:47
#6 0x55c1839c20f8 in void JSMethod<CJS_Document, &(CJS_Document::getField(CJS_Runtime*, std::__1::vector<v8::Local<v8::Value>, std::__1::allocator<v8::Local<v8::Value> > > const&))>(char const*, char const*, v8::FunctionCallbackInfo<v8::Value> const&) third_party/pdfium/fxjs/js_define.h:128:23
#7 0x55c180a613a0 in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) v8/src/api/api-arguments-inl.h:158:3
#8 0x55c180a5ef18 in v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) v8/src/builtins/builtins-api.cc:111:36
#9 0x55c180a5ca6d in v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) v8/src/builtins/builtins-api.cc:141:5
#10 0x55c182ab0277 in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit (/home/anikolich/Downloads/asan-linux-release-772044/chrome_special+0x11d31277)
#11 0x55c182a452b4 in Builtins_InterpreterEntryTrampoline (/home/anikolich/Downloads/asan-linux-release-772044/chrome_special+0x11cc62b4)
#12 0x55c182a42df9 in Builtins_JSEntryTrampoline (/home/anikolich/Downloads/asan-linux-release-772044/chrome_special+0x11cc3df9)
#13 0x55c182a42bd7 in Builtins_JSEntry (/home/anikolich/Downloads/asan-linux-release-772044/chrome_special+0x11cc3bd7)
#14 0x55c180cf5719 in Call v8/src/execution/simulator.h:142:12
#15 0x55c180cf5719 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) v8/src/execution/execution.cc:367:33
#16 0x55c180cf4640 in v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) v8/src/execution/execution.cc:461:10
#17 0x55c18090023d in v8::Script::Run(v8::Local<v8::Context>) v8/src/api/api.cc:2080:7
#18 0x55c18397913f in CFXJS_Engine::Execute(fxcrt::WideString const&) third_party/pdfium/fxjs/cfxjs_engine.cpp:561:25
#19 0x55c183a448c1 in CJS_Runtime::ExecuteScript(fxcrt::WideString const&) third_party/pdfium/fxjs/cjs_runtime.cpp:164:10
#20 0x55c1839da524 in CJS_EventContext::RunScript(fxcrt::WideString const&) third_party/pdfium/fxjs/cjs_event_context.cpp:53:23
#21 0x55c1838ae090 in RunScript third_party/pdfium/fpdfsdk/cpdfsdk_actionhandler.cpp:461:13
#22 0x55c1838ae090 in CPDFSDK_ActionHandler::RunDocumentOpenJavaScript(CPDFSDK_FormFillEnvironment*, fxcrt::WideString const&, fxcrt::WideString const&) third_party/pdfium/fpdfsdk/cpdfsdk_actionhandler.cpp:384:3
#23 0x55c1838adac2 in CPDFSDK_ActionHandler::ExecuteDocumentOpenAction(CPDF_Action const&, CPDFSDK_FormFillEnvironment*, std::__1::set<CPDF_Dictionary const*, std::__1::less<CPDF_Dictionary const*>, std::__1::allocator<CPDF_Dictionary const*> >*) third_party/pdfium/fpdfsdk/cpdfsdk_actionhandler.cpp:148:9
#24 0x55c1838ad591 in CPDFSDK_ActionHandler::DoAction_DocOpen(CPDF_Action const&, CPDFSDK_FormFillEnvironment*) third_party/pdfium/fpdfsdk/cpdfsdk_actionhandler.cpp:26:10
#25 0x55c1838ee9c1 in CPDFSDK_FormFillEnvironment::ProcOpenAction() third_party/pdfium/fpdfsdk/cpdfsdk_formfillenvironment.cpp:629:23
#26 0x55c195e35ad3 in chrome_pdf::PDFiumEngine::FinishLoadingDocument() pdf/pdfium/pdfium_engine.cc:757:3
#27 0x55c195e52636 in chrome_pdf::PDFiumEngine::ContinueLoadingDocument(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) pdf/pdfium/pdfium_engine.cc:2548:5
#28 0x55c195e341a0 in chrome_pdf::PDFiumEngine::LoadDocument() pdf/pdfium/pdfium_engine.cc:2466:5
#29 0x55c195e693df in chrome_pdf::DocumentLoaderImpl::ReadComplete() pdf/document_loader_impl.cc
#30 0x55c195e6982c in chrome_pdf::DocumentLoaderImpl::DidRead(int) pdf/document_loader_impl.cc
#31 0x55c195e6a932 in operator() ppapi/utility/completion_callback_factory.h:607:9
#32 0x55c195e6a932 in pp::CompletionCallbackFactory<chrome_pdf::DocumentLoaderImpl, pp::ThreadSafeThreadTraits>::CallbackData<pp::CompletionCallbackFactory<chrome_pdf::DocumentLoaderImpl, pp::ThreadSafeThreadTraits>::Dispatcher0<void (chrome_pdf::DocumentLoaderImpl::*)(int)> >::Thunk(void*, int) ppapi/utility/completion_callback_factory.h:584:7
#33 0x55c195e71518 in chrome_pdf::URLLoaderWrapperImpl::DidRead(int) pdf/url_loader_wrapper_impl.cc
#34 0x55c195e796b2 in operator() ppapi/utility/completion_callback_factory.h:607:9
#35 0x55c195e796b2 in pp::CompletionCallbackFactory<chrome_pdf::URLLoaderWrapperImpl, pp::ThreadSafeThreadTraits>::CallbackData<pp::CompletionCallbackFactory<chrome_pdf::URLLoaderWrapperImpl, pp::ThreadSafeThreadTraits>::Dispatcher0<void (chrome_pdf::URLLoaderWrapperImpl::*)(int)> >::Thunk(void*, int) ppapi/utility/completion_callback_factory.h:584:7
#36 0x55c18a122f41 in PP_RunCompletionCallback ppapi/c/pp_completion_callback.h:240:3
#37 0x55c18a122f41 in CallWhileUnlocked<void, PP_CompletionCallback *, int, PP_CompletionCallback *, int> ppapi/shared_impl/proxy_lock.h:135:10
#38 0x55c18a122f41 in ppapi::TrackedCallback::Run(int) ppapi/shared_impl/tracked_callback.cc:141:7
#39 0x55c193bc8a33 in RunCallback ppapi/proxy/url_loader_resource.cc:336:22
#40 0x55c193bc8a33 in OnPluginMsgFinishedLoading ppapi/proxy/url_loader_resource.cc:282:5
#41 0x55c193bc8a33 in DispatchResourceReplyImpl<ppapi::proxy::URLLoaderResource, void (ppapi::proxy::URLLoaderResource::*)(const ppapi::proxy::ResourceMessageReplyParams &, int), std::__1::tuple<int> &, 0> ppapi/proxy/dispatch_reply_message.h:32:3
#42 0x55c193bc8a33 in DispatchResourceReply<ppapi::proxy::URLLoaderResource, void (ppapi::proxy::URLLoaderResource::*)(const ppapi::proxy::ResourceMessageReplyParams &, int), std::__1::tuple<int> &> ppapi/proxy/dispatch_reply_message.h:46:3
#43 0x55c193bc8a33 in ppapi::proxy::URLLoaderResource::OnReplyReceived(ppapi::proxy::ResourceMessageReplyParams const&, IPC::Message const&) ppapi/proxy/url_loader_resource.cc:220:5
#44 0x55c193ac5cad in ppapi::proxy::PluginMessageFilter::DispatchResourceReply(ppapi::proxy::ResourceMessageReplyParams const&, IPC::Message const&) ppapi/proxy/plugin_message_filter.cc:116:13
#45 0x55c184b67e0d in Run base/callback.h:99:12
#46 0x55c184b67e0d in base::TaskAnnotator::RunTask(char const*, base::PendingTask*) base/task/common/task_annotator.cc:142:33
#47 0x55c184ba042f in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::sequence_manager::LazyNow*) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:325:23
#48 0x55c184b9fd8c in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:250:36
#49 0x55c184aa20c0 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:39:55
#50 0x55c184ba1658 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:438:12
#51 0x55c184b18c9a in base::RunLoop::Run() base/run_loop.cc:124:14
#52 0x55c1830739cd in content::PpapiPluginMain(content::MainFunctionParams const&) content/ppapi_plugin/ppapi_plugin_main.cc:165:12
#53 0x55c183ac8cdf in content::RunZygote(content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:474:14
#54 0x55c183acbffc in content::ContentMainRunnerImpl::Run(bool) content/app/content_main_runner_impl.cc:841:10
#55 0x55c183c575a5 in service_manager::Main(service_manager::MainParams const&) services/service_manager/embedder/main.cc:454:29
#56 0x55c183ac717f in content::ContentMain(content::ContentMainParams const&) content/app/content_main.cc:19:10
#57 0x55c17a263d13 in ChromeMain chrome/app/chrome_main.cc:110:12
#58 0x7f0df9d6182f in __libc_start_main /build/glibc-LK5gWL/glibc-2.23/csu/../csu/libc-start.c:291
0x604000000008 is located 8 bytes to the left of 40-byte region [0x604000000010,0x604000000038)
allocated by thread T0 (chrome_special) here:
#0 0x55c17a26122d in operator new(unsigned long) /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/asan/asan_new_delete.cpp:99:3
#1 0x7f0df2b58b4b in _init (/usr/lib/x86_64-linux-gnu/libboost_filesystem.so.1.58.0+0x6b4b)
SUMMARY: AddressSanitizer: heap-buffer-overflow buildtools/third_party/libc++/trunk/include/vector:656:46 in size
Shadow bytes around the buggy address:
0x0c087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c087fff8000: fa[fa]00 00 00 00 00 fa fa fa 00 00 00 00 05 fa
0x0c087fff8010: fa fa 00 00 00 00 00 fa fa fa 00 00 00 00 04 fa
0x0c087fff8020: fa fa 00 00 00 00 00 fa fa fa 00 00 00 00 00 fa
0x0c087fff8030: fa fa 00 00 00 00 00 fa fa fa 00 00 00 00 00 fa
0x0c087fff8040: fa fa 00 00 00 00 00 fa fa fa fd fd fd fd fd fa
0x0c087fff8050: fa fa 00 00 00 00 00 fa fa fa fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1608==ABORTING
2020-06-04 - Vendor Disclosure
2020-06-06 - Vendor patched
2020-09-14 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.