How Safari Mitigated Spectre and Meltdown Attacks
In early 2018, the discovery of the Spectre and Meltdown hardware
vulnerabilities forced web browser developers to urgently rethink how
they handle memory and timing operations. Because modern web browsers
execute untrusted JavaScript code from across the internet, they became
a primary vector for exploiting these CPU-level flaws. Apple's Safari
web browser, powered by the WebKit engine, responded swiftly to protect
users from these speculative execution side-channel attacks. This
article explains how Safari neutralized these threats by deliberately
reducing the precision of the performance.now() timer and
isolating the SharedArrayBuffer object.
Understanding the Browser Threat
Spectre and Meltdown exploit a hardware optimization technique called speculative execution. To make processors faster, CPUs guess which instructions will be needed next and execute them ahead of time. If the guess is wrong, the CPU discards the operation, but traces of the speculatively accessed data remain in the CPU's cache.
While Meltdown was primarily mitigated at the operating system kernel level in macOS and iOS, Spectre posed a unique threat to Safari. An attacker could use malicious JavaScript on a website to trick the CPU into speculatively accessing sensitive memory (like passwords or session tokens) belonging to other websites. To actually steal this data, the attacker needed a high-precision timer to measure how fast the CPU accessed certain memory addresses. Fast access meant the data was in the cache, allowing the attacker to deduce the contents of protected memory byte by byte.
To stop Spectre in Safari, Apple didn't need to fix the CPU; they simply needed to blind the attacker by removing the high-precision clocks available in JavaScript.
Reducing
performance.now() Timer Precision
The most direct way to measure time in a browser is the
performance.now() JavaScript API. Originally, this function
provided timestamps with microsecond precision to help developers
measure frame rates and benchmark code performance. However, this level
of accuracy was precise enough for attackers to distinguish between a
CPU cache hit (fast) and a cache miss (slow).
To neutralize this side-channel, the WebKit team immediately degraded
the accuracy of performance.now().
- Resolution Clamping: Safari reduced the timer's resolution to 1 millisecond (1,000 microseconds). Because CPU cache operations happen in nanoseconds, a 1-millisecond tick is far too coarse to measure the difference between cached and uncached memory.
- Timer Jitter: In addition to rounding the time, WebKit introduced random noise, or "jitter," to the timestamps. This prevented attackers from using statistical analysis over thousands of attempts to guess the exact moment a millisecond ticked over.
Disabling and
Isolating SharedArrayBuffer
Degrading performance.now() was only half the battle.
Clever attackers realized they could build their own high-resolution
timer using a JavaScript feature called
SharedArrayBuffer.
SharedArrayBuffer allows a website to share memory
simultaneously between the main browser thread and background threads
(Web Workers). An attacker could set up a Web Worker whose only job was
to endlessly increment a number in a shared memory space (e.g., 1, 2, 3,
4...). By reading this rapidly increasing number from the main thread
before and after a memory operation, the attacker had a makeshift
stopwatch capable of nanosecond-level precision, completely bypassing
the crippled performance.now() timer.
Safari handled SharedArrayBuffer in two distinct
phases:
Phase 1: Complete Removal
Because SharedArrayBuffer could be weaponized as a
custom clock, Apple completely disabled the feature in Safari 11.1. By
removing the API from the browser entirely, attackers could no longer
use concurrent threads to measure the speed of speculative
execution.
Phase 2: Cross-Origin Isolation
While disabling SharedArrayBuffer secured the browser,
it broke legitimate high-performance web applications, such as video
editors and 3D games, that relied on shared memory. To bring the feature
back safely, Safari implemented a security model called Cross-Origin
Isolation (introduced in Safari 15.2).
Even if a Spectre attack succeeds, it can only read memory that
exists within the same operating system process. Therefore, Safari now
requires websites that want to use SharedArrayBuffer to opt
into strict security headers:
- Cross-Origin-Opener-Policy (COOP)
- Cross-Origin-Embedder-Policy (COEP)
When a website sends these headers, Safari places the webpage into
its own isolated process and blocks it from loading resources from other
websites unless those sites explicitly allow it. Because no sensitive
cross-origin data exists in the same process anymore, an attacker has
nothing to steal. Only when a website is fully isolated in this manner
does Safari unlock SharedArrayBuffer and restore
high-precision timers.