What Spectre and Meltdown Mean For WebKit

Security researchers have recently uncovered security issues known as Meltdown and Spectre. These issues apply to all modern processors and allow attackers to gain read access to parts of memory that were meant to be secret. To initiate a Spectre- or Meltdown-based attack, the attacker must be able to run code on the victim’s processor. WebKit is affected because in order to render modern web sites, any web JavaScript engine must allow untrusted JavaScript code to run on the user’s processor. Spectre impacts WebKit directly. Meltdown impacts WebKit because WebKit’s security properties must first be bypassed (via Spectre) before WebKit can be used to mount a Meltdown attack.

  • WebKit relies on branch instructions to enforce what untrusted JavaScript and WebAssembly code can do. Spectre means that an attacker can control branches, so branches alone are no longer adequate for enforcing security properties.

  • Meltdown means that userland code, such as JavaScript running in a web browser, can read kernel memory. Not all CPUs are affected by Meltdown and Meltdown is being mitigated by operating system changes. Mounting a Meltdown attack via JavaScript running in WebKit requires first bypassing branch-based security checks, like in the case of a Spectre attack. Therefore, Spectre mitigations that fix the branch problem also prevent an attacker from using WebKit as the starting point for Meltdown.

This document explains how Spectre and Meltdown affect existing WebKit security mechanisms and what short-term and long-term fixes WebKit is deploying to provide protection against this new class of attacks. The first of these mitigations shipped on Jan 8, 2018:

  • iOS 11.2.2.
  • High Sierra 10.13.2 Supplemental Update. This reuses the 10.13.2 version number. You can check if your Safari and WebKit are patched by verifying the full version number in About Safari. The version number should be either 13604.4.7.1.6 or 13604.4.7.10.6.
  • Safari 11.0.2 for El Capitan and Sierra. This reuses the 11.0.2 version number. Patched versions are 11604.4.7.1.6 (El Capitan) and 12604.4.7.1.6 (Sierra).

Spectre and Security Checks

Spectre means that branches are no longer sufficient for enforcing the security properties of read operations in WebKit. The most impacted subsystem is JavaScriptCore (WebKit’s JavaScript engine). Almost all bounds checks can be bypassed to read arbitrarily out-of-bounds. This could allow an attacker to read arbitrary memory. All type checks are also vulnerable. For example, if some type contains an integer at offset 8 while another type contains a pointer at offset 8, then an attacker could use Spectre to bypass the type check that is supposed to ensure that you can’t use the integer to craft an arbitrary pointer.

JavaScriptCore is meant to be a secure language virtual machine. It should be possible to load untrusted JavaScript or WebAssembly code into your process without the risk of your process’s memory being leaked to the JavaScript code except in cases where you explicitly export data to JavaScript via our C or Objective-C binding API. Spectre breaks this property of JavaScriptCore because untrusted JavaScript or WebAssembly now has a theoretical path to reading all of the host process’s address space.

DOM APIs and system APIs called by DOM APIs also use branches to enforce their security properties, and those are callable from JavaScript. Hence, Spectre is not just an attack on JavaScriptCore itself but also everything that is callable from JavaScript.

Reasoning About Spectre

To understand how Spectre works, it’s useful to think about how security-sensitive programming language operations (like any property access in JavaScript) get executed in JavaScriptCore on a modern processor. Most discussions about Spectre have involved bounds checks, so this section considers that case as well.

var tmp = intArray[index];

In this example, let’s assume that intArray is known to our JavaScript engine to be a reference to a Int32Array instance, but that we have not proved that index is in bounds of intArray.length. The compiler will have to emit a bounds check when it translates this high-level JavaScript operation into a lower-level form:

if (((unsigned) index) >= ((unsigned) intArray->length))
    fail;
int tmp = intArray->vector[index];

Compiling this on x86 CPUs results in the following instructions:

mov 0x10(%rsi), %rdx     ; %rsi has intArray. This loads
                         ; intArray->vector.
cmp 0x18(%rsi), %ecx     ; %ecx has the index. This compares
                         ; the index to intArray->length.
jae Lfail                ; Branch to Lfail if
                         ; index >= intArray->length according
                         ; to unsigned comparison.
mov (%rdx,%rcx,4), %ecx  ; Load intArray->vector[index].

Modern CPUs are able to execute the bounds check branch (jae) and the subsequent load (mov (%rdx,%rcx,4), %ecx) in parallel. This is possible because:

  • Modern CPUs profile branches. In this case, they will observe that the branch always falls through. This is called branch prediction.
  • Modern CPUs can roll back execution. The load after the branch can execute before the CPU verifies that the branch actually did fall through. If the branch turns out to be taken instead, the CPU can undo everything that happened between when the branch was encountered and when it finally got verified. This is called speculative execution.

Spectre is an attack that exploits information leaks from speculative execution. Consider this code:

var tmp = intArray[index];
otherArray[(tmp & 1) * 128];

The CPU has the ability to initiate loads from main memory into L1 (the CPU’s level 1 memory cache, which is the fastest and smallest) while executing speculatively. As a performance optimization, the CPU does not undo fetches into L1 when rolling back speculative execution. This leads to a timing-based information leak: in this code, whether the CPU loads otherArray[0] or otherArray[128] depends on tmp & 1, and it’s possible to later determine which the CPU loaded speculatively by timing the speed of access to otherArray[0] and otherArray[128].

The example so far involved controlling a bounds checking branch. This is a particularly effective Spectre attack because index behaves like a pointer that can be used to read ~16GB of memory above intArray->vector. But Spectre could theoretically involve any branch that enforces security properties, like the branches used for type checks in JavaScriptCore.

To summarize:

  1. Spectre requires high fidelity timing so that the difference between L1 latency and main memory latency can be observed.
  2. Spectre lets attackers control branches. Speculative execution executes branches according to past history, and the attacker can control this history. Therefore, the attacker controls what branches do during speculative execution.
  3. Spectre is a race between the branch verifier and the initiation of the information-leaking load (what we wrote as otherArray[(tmp & 1) * 128] above). The attacker knows if they won the race (one of the two otherArray cache lines will be in L1), so the attack works so long as the attacker’s chance of winning is not zero.

Mitigating Spectre

WebKit’s response to Spectre is a two-tiered defense:

  1. WebKit has disabled SharedArrayBuffer and reduced timer precision.
  2. WebKit is transitioning to using branchless security checking in addition to branch-based security checking.

Some of these changes shipped in the Jan 8 updates and more such changes are continuing to land in WebKit. The remainder of this document covers our mitigations in detail.

Reducing Timer Precision

We are reducing timer precision in WebKit. These changes have landed in trunk as of r226495 and they shipped in the Jan 8 updates.

  • Timer precision from performance.now and other sources is reduced to 1ms (r226495).
  • We have disabled SharedArrayBuffer, since it can be used to create a high-resolution timer (r226386).

Our long-term plan is to make Spectre impossible even in the presence of high fidelity timing; further work is needed to re-enable this path.

Branchless Security Checks

Since learning about Spectre, we have been researching how to do security checks without relying on branches. We have begun making the transition to this new style of security checks and we shipped the first branchless checks in the Jan 8 updates.

Index Masking

The simplest example of a branchless security check is masking the index of an array access:

int tmp = intArray->vector[index & intArray->mask];

Modern CPUs do not speculate on bit masking. If the mask is picked to fit the array length, this mitigation ensures that even with Spectre, an attacker cannot read out-of-bounds of the array.

We have implemented index masking for:

  1. Typed arrays (r226461),
  2. WebAssembly memories (r226461, mostly via shared code with typed arrays),
  3. Strings (r226068),
  4. WTF::Vector (r226068), and
  5. Plain JavaScript arrays (r225913).

Changes (1-4) shipped in the Jan 8 updates. (5) landed in WebKit but has not yet shipped.

Index masking is not yet a complete fix for out-of-bounds access. Our current index masking mitigations use a mask that is computed by rounding up the length to the next power of two (and subtracting one). This still allows out-of-bounds reads, just not to arbitrary memory.

Our current testing indicates that index masking has no measurable impact on the Speedometer and ARES-6 tests and an impact of less than 2.5% on the JetStream benchmark.

Pointer Poisoning

Index masking is easy to apply for array accesses, but many security check branches have to do with object type, not array bounds. Pointer poisoning is a technique that can be used to make any type check secure under Spectre by changing the shape of the object subject to the check.

Poisoning a pointer just means performing some reversible math on it that will make access attempts fail unless the pointer is unpoisoned. In WebKit, poisoning involves xoring a value that is sure to have at least one bit set in high positions so that accesses that do not unpoison are very likely to hit unmapped memory. WebKit picks poison values using a compile-time random number generator with lots of subtle rules, but to understand the approach, just consider 1 << 40 as a poison value. Adding one terabyte to a valid pointer is sure to result in an unmapped pointer in WebKit’s memory layout on macOS and iOS, and probably on other operating systems, too. Many possible poison values exist that would have this effect.

Poisoning becomes most powerful when each static declaration of a pointer field has a unique poison value, and the poison values differ in the high bits so that unpoisoning with the wrong value results in an unmapped pointer.

As an example of how to apply pointer poisoning as a branchless type check, consider some class Foo that belongs to a hierarchy of classes that participate in dynamic downcasts based on some checks:

class Foo : public Base {
public:
    ...
private:
    int m_x;
    Bar* m_y;
};

In order to make this class’s type checks sound under Spectre, we can simply move Foo’s fields out into a data struct pointed to by a uniquely poisoned pointer:

class Foo : public Base {
public:
    ....
private:
    struct Data {
        int x;
        Bar* y;
    };
    ConstExprPoisoned<FooDataKey, Data> m_data;
};

Where FooDataKey is a key unique to Foo, based on which ConstExprPoisoned<> will compute a random poison value at compile time. If all classes in this hierarchy use a pointer poisoned indirection to protect their fields then this suffices as a branchless type check for all accesses to these types. In addition to being a great Spectre mitigation, this is also a useful remote code execution mitigation, since it makes it harder to do any kind of type confusion.

Pointer poisoning sometimes does not require any extra indirections. JavaScriptCore has tons of data structures that roughly fit this pattern:

struct Thingy {
    Type type;
    void* data; // The shape of this depends on `type`.
};

Any such data structure’s type checks can be made branchless by poisoning the data pointer according to a poison value that depends on type.

We have begun converting WebKit’s object models to use pointer poisoning (r225363, rr225437, r225632, r225659, r225697, r225857, r226015, r226247, r226344, r226485, r226530), and some of the initial pointer poisoning work is shipping in the Jan 8 updates (specifically, r225363r225857). So far, we have not observed any performance regressions from using pointer poisoning.

Mitigating Meltdown

Meltdown allows userland code to read kernel memory. WebKit is indirectly affected by Meltdown because a Meltdown attack could be initiated using any kind of code execution, including JavaScript. However, the memory accesses used to perform Meltdown would be a violation of WebKit’s security model. Therefore, JavaScript-based Meltdown attacks must first get around WebKit’s security checks such as by using a Spectre attack.

If Meltdown has been mitigated by operating system changes, then even if WebKit lacked any Spectre mitigations, it would not be possible to mount a Meltdown attack via WebKit. Any future Spectre mitigations will make it even less likely that WebKit could be used for a Meltdown attack, since the Spectre stage of that attack will be harder.

Recommendations For App Developers

Spectre means that secrets in the same address space as untrusted JavaScript are more vulnerable than ever before. Based on this, we recommend:

  • Switch to the Modern WebKit API if you have not done so already. This protects your app by running untrusted JavaScript in another process.
  • Avoid exposing sources of high-precision timing to untrusted JavaScript or WebAssembly.

Conclusion

Spectre and Meltdown are a new class of security issues that apply to modern processors and the software that runs on them. WebKit is affected by both issues because WebKit allows untrusted code to run on users’ processors. In response to these new issues, we have implemented mitigations to defend against Spectre (and Meltdown attacks launched using Spectre via the browser). The first of these mitigations have shipped in the Jan 8 updates (iOS 11.2.2, High Sierra 10.13.2 supplemental update, and Safari 11.0.2 reissue). Stay tuned for more WebKit Spectre fixes!