Chrome in-the-wild bug analysis [CVE-2021-37975]

We discussed this vulnerability during Episode 94 on 26 October 2021

A logic bug in the Chrome garbage collector was discovered which could cause use-after-free. The garbage collector (GC) is a monolithic and complex component of the browser, and some background knowledge is needed to appreciate the issue.

Background First, browser garbage collectors utilize a “mark and sweep” technique, where it will iterate over all global/in-scope objects, look for references to them, and “mark” them if any references are found. At the end, any unmarked objects get garbage collected and freed. Where it gets interesting is, the WeakMap data-structure can be used to store weak references, where objects can be stored in the map without preventing it from being garbage collected.

These cases have to be handled carefully. These WeakMap entries are known as ephemeron pairs. The browser must ensure that any map entries where the value is “reachable” from a strong reference is accounted for and won’t be collected. To do this, WeakMap’s contain a hash table. When the GC reaches this hash table, any key-value pairs that aren’t already marked are queued in a worklist to be processed again at the end of the GC cycle. At the end of the cycle, it goes back through the ephemeron pairs and tries to check for references. It does this in three stages:

  1. Processes the current_ephemerons list and pushes entries assumed to be unreachable into the next_ephemerons list
  2. Tries to discover new ephemerons and pushes them in the next_ephemerons list
  3. Processes newly discovered ephemerons

The Bug The problem is, the newly discovered ephemerons in the second stage might have strong references to previously assumed unreachable ephemerons that were already processed. This case is never accounted for and they never get marked, meaning the collector can free them even though there’s a strong reference remaining. One way this can happen is if you have two key-value pairs such as (k1, v1) and (k2, v2) where v2 = k1. If the (k1, v1) pair is visited first where both are unmarked, the second pair will get visited, and k1 will get marked via the value of the second pair, but v1 from the first pair will remain unmarked and thus will be collected.

This provides a targeted use-after-free primitive where the attacker can free any JS object they choose, as they can store any reference they want to retrieve post-GC. In this case, they targeted a large JSArray, which gets allocated in the “large object space”. By overlapping a large array of objects and a large array of doubles, fakeobj and leakobj primitives can be obtained, which then leads to easy arbitrary read/write.