CVE-2020-6418: Chrome incorrect side-effect modelling issue in Turbofan leading to type confusions
Samuel Groß and Sergei Glazunov, Project Zero (Originally posted on Project Zero blog 2020-08-05)
The Basics
Disclosure or Patch Date: 24 February 2020
Product: Google Chrome
Advisory: https://chromereleases.googleblog.com/2020/02/stable-channel-update-for-desktop_24.html
Affected Versions: Google Chrome 60 - 80
First Patched Version: 80.0.3987.122
Issue/Bug Report: https://bugs.chromium.org/p/chromium/issues/detail?id=1053604
Patch CL: https://chromium.googlesource.com/v8/v8.git/+/fb0a60e15695466621cf65932f9152935d859447
Bug-Introducing CL: https://chromium.googlesource.com/v8/v8.git/+/0f716acadaed1d9e194593543dbe1340d600d6fc
Reporter(s): Clément Lecigne of Google's Threat Analysis Group
The Code
Proof-of-concept:
'use strict';
(function() {
var popped;
function trigger(new_target) {
function inner(new_target) {
function constructor() {
popped = Array.prototype.pop.call(array);
}
var temp = array[0];
return Reflect.construct(constructor, arguments, new_target);
}
inner(new_target);
}
var array = new Array(0, 0, 0, 0, 0);
for (var i = 0; i < 20000; i++) {
trigger(function() { });
array.push(0);
}
var proxy = new Proxy(Object, {
get: () => (array[4] = 1.1, Object.prototype)
});
trigger(proxy);
print(popped);
}());
Exploit sample: N/A
Did you have access to the exploit sample when doing the analysis? Yes
The Vulnerability
Bug class: Incorrect side-effect modelling in JIT Compiler
Vulnerability details:
The function NodeProperties::InferReceiverMapsUnsafe is responsible for inferring the Map of an object. From the source code documentation: "This information can be either 'reliable', meaning that the object is guaranteed to have one of these maps at runtime, or 'unreliable', meaning that the object is guaranteed to have HAD one of these maps". In the latter case, the caller has to ensure that the object has the correct type, either by using CheckMap
nodes or CodeDependencies
.
On a high level, the InferReceiverMapsUnsafe
function traverses the effect chain until it finds the node creating the object in question and, at the same time, marks the result as unreliable if it encounters a node without the kNoWrite
flag, indicating that executing the node could have side-effects such as changing the Maps of an object. There is a mistake in the handling of kJSCreate
: if the object in question is not the output of kJSCreate
, then the loop continues without marking the result as unreliable. This is incorrect because kJSCreate
can have side-effects, for example by using a Proxy as third argument to Reflect.construct
. The bug can then for example be triggered by inlining Array.pop
and changing the elements' kind from SMIs
to Doubles
during the unexpected side effect.
Normally, this bug class occurs due to mis-modelling of an operation in the JIT compiler. However, this time the modelling was correct (the JSCreate
function was correctly marked as having side effects), however, the specific piece of code relying on this data was incorrect as it had special-cased the JSCreate
operation and then forgot to perform the side-effect modelling.
Patch analysis: N/A
Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):
JIT side-effect related bugs are likely hard to discover through generic JavaScript engine fuzzing approaches, as, without specific compiler instrumentation, they don’t normally result in observable misbehaviour at runtime (i.e., a crash). Given that the vulnerable function is clearly security-critical, it is thus more likely that the bug was discovered through manual code analysis while looking for type inference related issues.
(Historical/present/future) context of bug:
JIT side effect modelling issues have been found and exploited in all major engines in the past years.
This vulnerability was chained with both Windows 0-days (CVE-2020-0938, CVE-2020-1020, CVE-2020-1027 and Android n-days as a part of a watering hole attack.
The Exploit
Is the exploit method known? Yes
Exploit method:
The attacker abused the unexpected side-effect to cause type confusion between the floating-point and tagged pointer array types, which allowed them to implement the fakeobj
primitive. fakeobj
makes the JavaScript engine treat an arbitrary value as a pointer to a JavaScript object. The attacker constructed a “fake” typed array and used it to overwrite the machine code of a compiled WebAssembly function with their shellcode.
The Next Steps
Variant analysis
Areas/approach for variant analysis (and why):
Manual auditing for similar issues in the same function and related ones. A more generic approach would be to build compiler instrumentation to detect unexpected side effects at runtime and abort early.
Found variants: None
Structural improvements
N/A. This bug seems to be somewhat unique (at least it’s quite different from all the other side-effect mis-modelling bugs we can think of) and thus probably not much can be done in terms of generic/structural improvements.
0-day detection methods
These types of exploits are likely hard to detect generically.
Other References
- January 2021: “In The Wild: Chrome exploits” blogpost
- May 2019: "Exploiting Logic Bugs in JavaScript JIT Engines" by @saelo