Type Confusion Exploit in Chrome (CVE-2023-3420)

We discussed this vulnerability during Episode 218 on 10 October 2023

Post from Man Yue Mo at GitHub Security Lab on an RCE in Chrome due to a bug in Chrome’s JIT compiler (TurboFan). As a bit of background, modern browsers will often compile code/functions that are deemed as ‘hotpaths’ (aka executed a lot). The JIT compiler and optimizer has to take a lot of factors into consideration to assume security checks and type checks are only removed when it can be safely assumed that some callback or other code running can’t taint the state of the object while executing. This is also important as the fields and offsets of those fields in an object are tracked via that object’s mapping, which can be changed in the optimization process.

This problem was made even trickier in Chrome 95 when concurrent compilation was introduced. With concurrent compilation, TurboFan can JIT and optimize functions in the background thread while the main thread is interpreting the JavaScript bytecode as normal. This introduces a whole new crop of potential bugs, as things like the compilation process changing the mapping of objects can break assumptions on the main interpreter thread.

The Bug The main culprit of corruption here is the CompilationDependency::PrepareInstall() method, which calls EnsureHasInitialMap which can implicitly chain the layout or map of an object via optimization. Interestingly, this function is called on the main thread and not the background thread, so it’s not a typical race as was the initial concern. However, PrepareInstall() can be executed on the main thread through an interrupt in something like a loop. So if you have a function that accesses a field on some object, run the loop for a while and allow the interrupt to fire, and have PrepareInstall() called, the next access on that field after the loop may be executed using a changed mapping, and no additional checks are inserted after the loop. This ultimately leads to an access at an incorrect offset, which leads to a sort of type confusion scenario.

This was used to overwrite a NamedDictionary’s capacity field to derive an out-of-bounds access, which was then used to confuse objects and arrays to get fakeobj and leakobj type primitives.