Samuel Groß, Project Zero (Originally posted on Project Zero blog 2021-02-04)

The Basics

Disclosure or Patch Date: 2 November 2020

Product: Google Chrome

Advisory: https://chromereleases.googleblog.com/2020/11/stable-channel-update-for-desktop.html

Affected Versions: 86.0.4240.111 and previous

First Patched Version: 86.0.4240.183

Issue/Bug Report:

Patch CL: https://chromium.googlesource.com/v8/v8.git/+/3ba21a17ce2f26b015cc29adc473812247472776

Bug-Introducing CL: N/A

Reporter(s): Clément Lecigne of Google's Threat Analysis Group and Samuel Groß of Google Project Zero

The Code

Proof-of-concept: https://bugs.chromium.org/p/project-zero/issues/detail?id=2106

Exploit sample: N/A

Did you have access to the exploit sample when doing the analysis? Yes

The Vulnerability

Bug class: Type Confusion

Vulnerability details: Incorrect Map deprecation in V8, leading to type confusions

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. This function will only install the dependency if the target Map can be deprecated, which is determined by Map::CanBeDeprecated. As shown below, CanBeDeprecated 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):

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;
}

However, in certain scenarios (refer to the PoC in the Project Zero issue tracker for details), V8 would accidentally deprecate a Map containing only tagged and double properties. This bug can then 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:tagged}. Turbofan will assume that map2 can never be deprecated and will not install CodeDependencies to deoptimize the JIT code if it is.
  2. Trigger the bug to deprecate map2. This does not deoptimize the JIT code.
  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

Patch analysis: N/A

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):

It is possible to find this bug through (targeted) fuzzing, although it is quite difficult to trigger it. As such, it is also (at least equally, in my opinion) possible that this bug would have been found through manual analysis, since the area of the code is known to be complex and security critical.

(Historical/present/future) context of bug:

The Map transition/deprecation mechanism is fairly complex and various bugs have been found in it in the past, for example:

Although these were related to element kinds and not property types/representations.

The Exploit

(The terms exploit primitive, exploit strategy, exploit technique, and exploit flow are defined here.)

Exploit strategy (or strategies): Still under analysis

Exploit flow: Still under analysis

Known cases of the same exploit flow: Still under analysis

Part of an exploit chain? This vulnerability was used as a Chrome RCE towards Android, but we do not know what exploits would be used after this. This vulnerability was used by the same actors during the same operation as CVE-2020-15999, CVE-2020-17087, CVE-2020-16010, CVE-2020-27930, CVE-2020-27950, and CVE-2020-27932.

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why): Fuzzing or auditing the Map transition/deprecation logic.

Found variants:

Structural improvements

  • Build targeted fuzzers for the Map transition/deprecation logic, similar to this one
  • Add a custom “sanitizer” to v8 that detects invalid Map deprecations. This can help fuzzers detect these kinds of issues earlier
  • Simplifying Map operations, e.g. by removing Map deprecations

0-day detection methods

N/A, likely hard to do generically. Triggers for JavaScript engine bugs are usually hard to distinguish from legitimate (and minified) JavaScript code. Especially for this type of bug, all the trigger code does is to create objects and load or store properties from/to them.

Other References