Talos Vulnerability Report

TALOS-2019-0791

Google V8 Array.prototype Memory Corruption Vulnerability

July 1, 2019
CVE Number

CVE-2019-5831

Summary

A specific JavaScript code can trigger a memory corruption in V8 7.3.492.17 which could potentially be abused for remote code execution. In order to trigger this vulnerability in the context of a browser, such as Google Chrome, the victim would need to visit a malicious web page.

Tested Versions

Google V8 7.3.492.17

Product URLs

https://v8.dev

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

Details

V8 is the core Javascript engine that runs Chrome browser. As part of Chrome and node.js, it is the most popular JavaScript engine today.

While executing the JavaScript code of the supplied PoC, it would appear that manipulating the contents and properties of Array.prototype could lead to an invalid pointer dereference when accessing an array element after garbage collection. This results in the following crash:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==7584==ERROR: AddressSanitizer: SEGV on unknown address 0x7ef007002f78 (pc 0x5613555d06a5 bp 0x7ffe4c045870 sp 0x7ffe4c045870 T0)
==7584==The signal is caused by a READ memory access.
    #0 0x5613555d06a4 in Relaxed_Load ./build/v8/v8/v8/out/asan/../../src/base/atomicops_internals_portable.h:183
    #1 0x5613555d06a4 in Relaxed_Load<unsigned long> ./build/v8/v8/v8/out/asan/../../src/base/atomic-utils.h:78
    #2 0x5613555d06a4 in Relaxed_Load ./build/v8/v8/v8/out/asan/../../src/objects/slots-inl.h:43
    #3 0x5613555d06a4 in map_word ./build/v8/v8/v8/out/asan/../../src/objects-inl.h:817
    #4 0x5613555d06a4 in map ./build/v8/v8/v8/out/asan/../../src/objects-inl.h:757
    #5 0x5613555d06a4 in IsSymbol ./build/v8/v8/v8/out/asan/../../src/objects/instance-type-inl.h:70
    #6 0x5613555d06a4 in IsSymbol ./build/v8/v8/v8/out/asan/../../src/objects-inl.h:119
    #7 0x5613555d06a4 in IsSymbol ./build/v8/v8/v8/out/asan/../../src/api.cc:3351
    #8 0x5613555d06a4 in ?? ??:0
    #9 0x5613555600ae in v8::WriteToFile(_IO_FILE*, v8::FunctionCallbackInfo<v8::Value> const&) ./build/v8/v8/v8/out/asan/../../src/d8.cc:1214
    #10 0x5613555600ae in ?? ??:0
    #11 0x5613555603ea in WriteAndFlush ./build/v8/v8/v8/out/asan/../../src/d8.cc:1234
    #12 0x5613555603ea in Print ./build/v8/v8/v8/out/asan/../../src/d8.cc:1240
    #13 0x5613555603ea in ?? ??:0
    #14 0x56135572b04c in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) ./build/v8/v8/v8/out/asan/../../src/api-arguments-inl.h:146
    #15 0x56135572b04c in ?? ??:0
    #16 0x561355728d72 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) ./build/v8/v8/v8/out/asan/../../src/builtins/builtins-api.cc:109
    #17 0x561355728d72 in ?? ??:0
    #18 0x561355726914 in v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) ./build/v8/v8/v8/out/asan/../../src/builtins/builtins-api.cc:139
    #19 0x561355726914 in ?? ??:0
    #20 0x56135727032a in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit snapshot-external.cc:?
    #21 0x56135727032a in ?? ??:0
    #22 0x5613571d4976 in Builtins_InterpreterEntryTrampoline snapshot-external.cc:?
    #23 0x5613571d4976 in ?? ??:0
    #24 0x5613571ce17f in Builtins_ArgumentsAdaptorTrampoline snapshot-external.cc:?
    #25 0x5613571ce17f in ?? ??:0
    #26 0x5613571d4976 in Builtins_InterpreterEntryTrampoline snapshot-external.cc:?
    #27 0x5613571d4976 in ?? ??:0
    #28 0x5613571ce17f in Builtins_ArgumentsAdaptorTrampoline snapshot-external.cc:?
    #29 0x5613571ce17f in ?? ??:0
    #30 0x56135729fd44 in Builtins_SortCompareUserFn snapshot-external.cc:?
    #31 0x56135729fd44 in ?? ??:0
    #32 0x5613572a32a7 in Builtins_ArrayTimSort snapshot-external.cc:?
    #33 0x5613572a32a7 in ?? ??:0
    #34 0x5613572a3f82 in Builtins_ArrayPrototypeSort snapshot-external.cc:?
    #35 0x5613572a3f82 in ?? ??:0
    #36 0x5613571d4976 in Builtins_InterpreterEntryTrampoline snapshot-external.cc:?
    #37 0x5613571d4976 in ?? ??:0
    #38 0x5613571d4976 in Builtins_InterpreterEntryTrampoline snapshot-external.cc:?
    #39 0x5613571d4976 in ?? ??:0
    #40 0x5613571d221f in Builtins_JSEntryTrampoline snapshot-external.cc:?
    #41 0x5613571d221f in ?? ??:0
    #42 0x5613571d1fac in Builtins_JSEntry snapshot-external.cc:?
    #43 0x5613571d1fac in ?? ??:0
    #44 0x561356067b0c in Call ./build/v8/v8/v8/out/asan/../../src/simulator.h:124
    #45 0x561356067b0c in Invoke ./build/v8/v8/v8/out/asan/../../src/execution.cc:266
    #46 0x561356067b0c in ?? ??:0
    #47 0x561356067085 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>*) ./build/v8/v8/v8/out/asan/../../src/execution.cc:358
    #48 0x561356067085 in ?? ??:0
    #49 0x5613555bb9ee in v8::Script::Run(v8::Local<v8::Context>) ./build/v8/v8/v8/out/asan/../../src/api.cc:2173
    #50 0x5613555bb9ee in ?? ??:0
    #51 0x561355555ca7 in v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::Value>, v8::Shell::PrintResult, v8::Shell::ReportExceptions, v8::Shell::ProcessMessageQueue) ./build/v8/v8/v8/out/asan/../../src/d8.cc:533
    #52 0x561355555ca7 in ?? ??:0
    #53 0x56135556ca05 in v8::SourceGroup::Execute(v8::Isolate*) ./build/v8/v8/v8/out/asan/../../src/d8.cc:2466
    #54 0x56135556ca05 in ?? ??:0
    #55 0x56135557270a in v8::Shell::RunMain(v8::Isolate*, int, char**, bool) ./build/v8/v8/v8/out/asan/../../src/d8.cc:2947
    #56 0x56135557270a in ?? ??:0
    #57 0x561355576848 in v8::Shell::Main(int, char**) ./build/v8/v8/v8/out/asan/../../src/d8.cc:3499
    #58 0x561355576848 in ?? ??:0
    #59 0x7f6c5b5ea82f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291
    #60 0x7f6c5b5ea82f in ?? ??:0

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (./d8+0xb716a4)
==7584==ABORTING

The supplied PoC code was tested, with same results, on the current beta branch as well as the latest dev version of v8. All built with AddressSanitizer enabled, in non-debug mode. The supplied PoC that triggers the crash isn't 100% reliable and doesn't trigger in builds without AddressSanitizer enabled, or in builds with optimizations disabled which would aid in debugging. Ultimately, we were unable to determine the exact root cause of this crash in timely manner because of these issues.

When run with certain debug flags, we can get additional information from v8's debug console - d8:

d8 --disable-in-process-stack-traces --trace-opt-verbose  --allow-natives-syntax  --trace-gc  poc.js

The above AddressSanitizer crash occurs when an array element of v3 is being accessed in the poc line:

try{    print(v3[v.length-1]);}catch(e){}

Using DebugTrace right before it, shows the following context information:

[object Object]
after f2
2000
before regex
[7584:0x62f000000400]     9662 ms: Mark-sweep 45.1 (74.9) -> 14.9 (47.5) MB, 1.8 / 0.0 ms  (+ 0.8 ms in 4 steps since start of marking, biggest step 0.8 ms, walltime since start of marking 37 ms) (average mu = 0.997, current mu = 0.996) finalize incremental marking via stack guard GC in old space requested
before f2
f2 147 999862

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x561357270566]
    1: StubFrame [pc: 0x5613572b3e2f]
Security context: 0x7e8ad7f19329 <JSObject>#0#
    2: f2 [0x7e8ad7f1e101] [poc_orig.js:23] [bytecode=0x7e8ad7f1e669 offset=49](this=0x7ecf13180bb9 <JSGlobal Object>#1#,0x7eef3c1804d1 <undefined>,0x7eef3c1804d1 <undefined>,0x7eef3c1804d1 <undefined>)
    3: arguments adaptor frame: 0->3
    4: f1 [0x7e8ad7f1e169] [poc_orig.js:54] [bytecode=0x7ecf13180469 offset=659](this=0x7ecf13180bb9 <JSGlobal Object>#1#,0,0,0x7eef3c1804d1 <undefined>)
    5: arguments adaptor frame: 2->3
    6: StubFrame [pc: 0x56135729fd45]
    7: StubFrame [pc: 0x5613572a32a8]
    8: sort [0x7e8ad7f103e1](this=0x7ecf13180c61 <JSArray[2000]>#2#,0x7e8ad7f1e169 <JSFunction f1 (sfi = 0x7e8ad7f1dcb9)>#3#)
    9: main [0x7e8ad7f1e099] [poc_orig.js:17] [bytecode=0x7e8ad7f1e2e9 offset=94](this=0x7ecf13180bb9 <JSGlobal Object>#1#)
   10: /* anonymous */ [0x7e8ad7f1de01] [poc_orig.js:61] [bytecode=0x7e8ad7f1dd79 offset=32](this=0x7ecf13180bb9 <JSGlobal Object>#1#)
   11: InternalFrame [pc: 0x5613571d2220]
   12: EntryFrame [pc: 0x5613571d1fad]

==== Details ================================================

[0]: ExitFrame [pc: 0x561357270566]
[1]: StubFrame [pc: 0x5613572b3e2f]
[2]: f2 [0x7e8ad7f1e101] [poc_orig.js:23] [bytecode=0x7e8ad7f1e669 offset=49](this=0x7ecf13180bb9 <JSGlobal Object>#1#,0x7eef3c1804d1 <undefined>,0x7eef3c1804d1 <undefined>,0x7eef3c1804d1 <undefined>) {
  // expression stack (top to bottom)
  [05] : 0x7eef3c1804d1 <undefined>
  [04] : 0x7eef3c1804d1 <undefined>
  [03] : 0x7eef3c1804d1 <undefined>
  [02] : 0x7e8ad7f0fdb9 <JSArray[999862]>#4#
  [01] : 0x7eba7b975b59 <String[13]: f2 147 999862>
  [00] : 0x7e8ad7f19cd9 <JSFunction print (sfi = 0x7e8ad7f19ca1)>#5#
--------- s o u r c e   c o d e ---------
function f2(arg1, arg2, arg3) { \x0a\x09print("f2 " + r2 + " " + Array.prototype.length);\x0a\x09%DebugTrace();\x0atry{\x09print(v3[v.length-1]);}catch(e){}\x0a\x09r2++; if( r2>5) return;\x0a\x09// initially , get the Array.prototype and change it\x0a\x09v = Array.prototype.fill(100);\x0a\x09// make length non zero\x0a\x09v.splice(Infinity,"a",5,v4); // inf...

-----------------------------------------
}

[3]: arguments adaptor frame: 0->3 {
}

[4]: f1 [0x7e8ad7f1e169] [poc_orig.js:54] [bytecode=0x7ecf13180469 offset=659](this=0x7ecf13180bb9 <JSGlobal Object>#1#,0,0,0x7eef3c1804d1 <undefined>) {
  // expression stack (top to bottom)
  [11] : 0x7ecf13180bb9 <JSGlobal Object>#1#
  [10] : 106
  [09] : 99
  [08] : 14
  [07] : 0x7e8ad7f09071 <JSFunction String (sfi = 0x7eef5c9076b1)>#6#
  [06] : 0x7efca05c5e71 <String[3]\: \x0ecj>
  [05] : 0x7efca05c3e39 <JSArray[1025]>#7#
  [04] : 0x7e8ad7f104c1 <JSFunction join (sfi = 0x7eef5c906e39)>#8#
  [03] : 0x7efca05c96b9 <Very long string[480795]>#9#
  [02] : 0x7e8ad7f04b71 <Object map = 0x7edb177809f9>#10#
  [01] : 0x7ecf13180221 <String[#9]: before f2>
  [00] : 0x7e8ad7f1e101 <JSFunction f2 (sfi = 0x7e8ad7f1dc61)>#11#
--------- s o u r c e   c o d e ---------
function f1(arg1, arg2, arg3) { \x0aprint(v3.length);\x0a//it can either crash when removing an item and shifting v or when printing it in f\x0av.shift();\x0aprint("before regex");\x0a\x0a\x0a//causes both memory and cpu pressure, laaaarge string that keeps json parser busy for a while\x0atry { v5 = JSON.parse(\x0a\x09""+ Array(1025).join(...

-----------------------------------------
}

[5]: arguments adaptor frame: 2->3 {
  // actual arguments
  [00] : 0
  [01] : 0
}

[6]: StubFrame [pc: 0x56135729fd45]
[7]: StubFrame [pc: 0x5613572a32a8]
[8]: sort [0x7e8ad7f103e1](this=0x7ecf13180c61 <JSArray[2000]>#2#,0x7e8ad7f1e169 <JSFunction f1 (sfi = 0x7e8ad7f1dcb9)>#3#) {
// optimized frame
--------- s o u r c e   c o d e ---------
<No Source>
-----------------------------------------
}
[9]: main [0x7e8ad7f1e099] [poc_orig.js:17] [bytecode=0x7e8ad7f1e2e9 offset=94](this=0x7ecf13180bb9 <JSGlobal Object>#1#) {
  // expression stack (top to bottom)
  [04] : 0x7e8ad7f1e169 <JSFunction f1 (sfi = 0x7e8ad7f1dcb9)>#3#
  [03] : 0x7ecf13180c61 <JSArray[2000]>#2#
  [02] : 0x7e8ad7f1e169 <JSFunction f1 (sfi = 0x7e8ad7f1dcb9)>#3#
  [01] : 0x7ecf13180c61 <JSArray[2000]>#2#
  [00] : 0x7e8ad7f103e1 <JSFunction sort (sfi = 0x7eef5c906cf9)>#12#
--------- s o u r c e   c o d e ---------
function main() {\x0a\x0a\x09f2();\x0a\x09v[1000000] = "a"; // grow Array.prototype to large size \x0a        Array.prototype.fill(0); //fill up Array.prototype\x0a\x09v3 = new Array(2000); // note that prototype is different now\x0a\x09v4 = {};   // next time f is called, v4 will be an empty object\x0a\x09v3.sort(f1); // will call f1 2000 times, ...

-----------------------------------------
}

[10]: /* anonymous */ [0x7e8ad7f1de01] [poc_orig.js:61] [bytecode=0x7e8ad7f1dd79 offset=32](this=0x7ecf13180bb9 <JSGlobal Object>#1#) {
  // expression stack (top to bottom)
  [04] : 0x7ecf13180bb9 <JSGlobal Object>#1#
  [03] : 0x7e8ad7f1de01 <JSFunction (sfi = 0x7e8ad7f1da81)>#13#
  [02] : 0x7eef3c1804d1 <undefined>
  [01] : 0x7e8ad7f1e099 <JSFunction main (sfi = 0x7e8ad7f1dc09)>#14#
  [00] : 0x7eef3c1804d1 <undefined>
--------- s o u r c e   c o d e ---------
var r2 = 0;\x0avar v = 0; \x0avar v1;\x0avar v3;\x0avar v4; \x0avar v5;\x0avar tmp;\x0a\x0a\x0afunction main() {\x0a\x0a\x09f2();\x0a\x09v[1000000] = "a"; // grow Array.prototype to large size \x0a        Array.prototype.fill(0); //fill up Array.prototype\x0a\x09v3 = new Array(2000); // note that prototype is different now\x0a\x09v4 = {};   // next time f...

-----------------------------------------
}

[11]: InternalFrame [pc: 0x5613571d2220]
[12]: EntryFrame [pc: 0x5613571d1fad]
==== Key         ============================================

 #0# 0x7e8ad7f19329: 0x7e8ad7f19329 <JSObject>
 #1# 0x7ecf13180bb9: 0x7ecf13180bb9 <JSGlobal Object>
 #2# 0x7ecf13180c61: 0x7ecf13180c61 <JSArray[2000]>
 #3# 0x7e8ad7f1e169: 0x7e8ad7f1e169 <JSFunction f1 (sfi = 0x7e8ad7f1dcb9)>
 #4# 0x7e8ad7f0fdb9: 0x7e8ad7f0fdb9 <JSArray[999862]>
                 0: 100
                 1: 100
                 2: 100
                 3: 100
                 4: 100
                 5: 100
                 6: 100
                 7: 100
                 8: 100
                 9: 100
                  ...
 #5# 0x7e8ad7f19cd9: 0x7e8ad7f19cd9 <JSFunction print (sfi = 0x7e8ad7f19ca1)>
 #6# 0x7e8ad7f09071: 0x7e8ad7f09071 <JSFunction String (sfi = 0x7eef5c9076b1)>
 #7# 0x7efca05c3e39: 0x7efca05c3e39 <JSArray[1025]>
 #8# 0x7e8ad7f104c1: 0x7e8ad7f104c1 <JSFunction join (sfi = 0x7eef5c906e39)>
 #9# 0x7efca05c96b9: 0x7efca05c96b9 <Very long string[480795]>
 #10# 0x7e8ad7f04b71: 0x7e8ad7f04b71 <Object map = 0x7edb177809f9>
 #11# 0x7e8ad7f1e101: 0x7e8ad7f1e101 <JSFunction f2 (sfi = 0x7e8ad7f1dc61)>
 #12# 0x7e8ad7f103e1: 0x7e8ad7f103e1 <JSFunction sort (sfi = 0x7eef5c906cf9)>
 #13# 0x7e8ad7f1de01: 0x7e8ad7f1de01 <JSFunction (sfi = 0x7e8ad7f1da81)>
 #14# 0x7e8ad7f1e099: 0x7e8ad7f1e099 <JSFunction main (sfi = 0x7e8ad7f1dc09)>
=====================

Also, since we enabled garbage collection tracing, it can be observed that memory corruption that leads to a crash always follows a specific garbage collection event:

[7584:0x62f000000400]     9662 ms: Mark-sweep 45.1 (74.9) -> 14.9 (47.5) MB, 1.8 / 0.0 ms  (+ 0.8 ms in 4 steps since start of marking, biggest step 0.8 ms, walltime since start of marking 37 ms) (average mu = 0.997, current mu = 0.996) finalize incremental marking via stack guard GC in old space requested

This leads us to believe that this issue is related to garbage collection, which is a very sensitive part of JavaScript engine, and that it could be open for further abuse.

Timeline

2019-03-15 - Vendor Disclosure
2019-03-25 - Vendor fixed/issued beta release
2019-07-01 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.