Chrome in-the-wild bug analysis [CVE-2021-37975]
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 pair
s and tries to check for references. It does this in three stages:
- Processes the
current_ephemerons
list and pushes entries assumed to be unreachable into thenext_ephemerons
list - Tries to discover new ephemerons and pushes them in the
next_ephemerons
list - 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.