Spook.js - Speculative Type Confusion

We discussed this vulnerability during Episode 82 on 14 September 2021

The cool part of this paper is the speculative type confusion attack where the browser’s optimizer is trained to expect a memory access will be a uint8 array, and the CPU branch predictor that it will always go down that path. Then the attack changes both conditions leading to the CPU speculatively executing the uint8 access using data from another object, aligned in memory such that two 32bit value in JavaScript become one 64bit value.

There are a number of gotchas or conditions that are needed for this attack. For example the authors need to prevent Chrome from deoptimizing the code when the type of the access changes. They manage this by attempting to keep the entire attack within the speculation window from a mispredicted branch so to Chrome it never accesses the array with the wrong type.

This leads into another problem, the speculation window for this attack needs to be quite large. Under the original Specter attack all that was needed was an L1 cache eviction primitive, cause speculation on the evicted value and you’d have a 10 cycle speculation window while the value was retrieved from the L2 cache. For this attack a Last-Level Cache eviction primitive is necessary which gives a considerable longer speculation window as the value is retrieved from system memory. It is worth pointing out here that while the attack could work against AMD CPUs also, the authors were unable to discover a reliable LLC eviction primitive.

Another problem was that while they needed to trigger the LLC eviction, they also needed some of the information within the object to remain in the cache. To do this they coerced Chrome into compacting a set of objects in memory, and sizing the objects such that they wouldn’t quite fill an entire cache line, leading to a guarentee that there would be at least one object that straddled the boundary between 64 byte cache lines.

All of this adds up to the ability to speculate and leak information across the entire 64bit process space despite the 32bit addressing using by the JS engine.