Chrome in-the-wild bug analysis: CVE-2021-30632

We discussed this vulnerability during Episode 88 on 05 October 2021

First a bit of background terminology as I understand it. Not being familiar with v8 there are likely some subtleties I am missing.

map - Maps in the context of v8 represent the memory layout of an object and are used when optimizing property access code. Multiple objects can share the same map if they have the same memory layout.

When the memory layout of an object changes such as by assigning a value to a new property then a transition is added to the map. When there is a transition it is considered an unstable map, and a stable map is a map without any transition. If, when the new property is added, a transition already exists (so the map was already unstable) the transition will be followed and it will become a stable map.

And so, with this in mind, a lot of optimized code will depend on the fact that a map cannot change without either reassigning a variaible (such as by giving it a new map all together) or by making the map unstable.

Which is where we hit the bug. When Turbofan optimizes code for storing global properties (global vars are properties of the global object) with the kConstantType attribute it will deoptimize when the property is reassigned through the generic path, or if the inserted CheckMaps call fails which ensures the map is the expected map.

Specifically if optimized while the expected map is unstable, then a DependOnStableMap will not be used to trigger a deoptimization when the map becomes unstable.

This allows for an interesting type confusion using two functions optimized at different times relying on different maps of the same object:

  1. Optimize a store/assignment on the global property while its map is unstable.
    function store(y) {
      x = y;
    }
    
  2. Add a new property to that global so that it recieves a new map.
    x.newProp = 1
    
  3. Optimize a load/access on the newly created property. This optimized load will be compiled assuming that x has the new map with newProp as a property.
    function load() {
      return x.newProp;
    }
    
  4. Use the optimized store to store an object with the old map back to the global property (x in the example). This won’t cause a deoptimization because its the expected map type.
  5. The next time the optimized load is used, there will be a type confusion.

Exploitation

Exploitation took advantage of creating a confusion between two different types of Javascript arrays, one of SMI objects (elements are 4 bytes) and a double array (elements are 8 bytes). This give relative out-of-bounds read and write primitives and the address calculations for each index will be off. Once the read and write primitives are obtained a known technique to disable W^X was used for code execution.