How Did Meltdown and Spectre Exploit Speculative Execution?
Meltdown and Spectre are hardware vulnerabilities disclosed in 2018 that broke the fundamental memory isolation guarantees of modern processors. Rather than exploiting software bugs, both attacks abused performance optimizations—specifically out-of-order execution and speculative execution—to force a CPU to transiently access secret data and leave observable traces in the processor cache. By measuring cache access times, an attacker could reconstruct sensitive information such as passwords, encryption keys, and kernel memory.
Performance Optimizations as an Attack Surface
Modern classical processors achieve high performance by executing instructions out of program order and predicting the outcome of conditional branches before their results are known. When a branch prediction is wrong or an instruction triggers a fault, the CPU discards the architectural results of those transient instructions so the program's visible state remains correct.
However, while the CPU rolls back architectural registers and memory writes, it does not revert changes made to microarchitectural state, particularly the CPU cache. This discrepancy creates a covert channel: if a transient instruction loads data into the cache based on a secret value, that cache line remains present even after the instruction is squashed.
Meltdown: Exploiting Out-of-Order Execution
Meltdown (CVE-2017-5754) targeted the boundary between user-space applications and privileged kernel memory. On affected processors, an unprivileged process could attempt to read a kernel memory address. Although the hardware eventually raises a page fault because the user process lacks permission, out-of-order execution allows subsequent instructions to execute before the fault is handled.
In a typical Meltdown attack, the attacker's code performs the following sequence:
- Attempt to read a secret byte from a kernel address into a register.
- Use that secret byte as an index to access an element in a
user-space probe array, such as
probe_array[secret * 4096]. - Handle or suppress the resulting page fault.
Because the second instruction executes out of order using the
transiently loaded secret byte, it brings the corresponding page of
probe_array into the CPU cache. Once the fault is resolved
and normal execution resumes, the secret value itself is gone, but its
footprint in the cache remains.
Spectre: Exploiting Speculative Execution
Spectre (CVE-2017-5753 and CVE-2017-5715) bypassed bounds checks and isolation boundaries by manipulating branch prediction. Unlike Meltdown, Spectre did not rely on a hardware permission check race; instead, it tricked the CPU into speculatively executing valid code paths with out-of-bounds or attacker-controlled inputs.
In Spectre Variant 1 (Bounds Check Bypass), an attacker trains the
CPU's branch predictor to expect a bounds check—such as
if (x < array_size)—to evaluate to true. The attacker
then supplies an out-of-bounds value for x. Speculatively,
the CPU bypasses the check, reads memory past the end of the array, and
uses that secret data to index into a probe array. Even though the CPU
later realizes the branch was mispredicted and discards the speculative
state, the cache line corresponding to the secret byte is already
loaded.
Spectre Variant 2 (Branch Target Injection) extended this concept by poisoning the Branch Target Buffer (BTB), redirecting speculative execution to a chosen gadget in the victim's address space.
Extracting Secrets via Cache Timing
To recover the secret byte from the cache, both Meltdown and Spectre
commonly used cache side-channel techniques such as Flush+Reload. Before
triggering the transient execution, the attacker flushes all pages of
the probe_array from the cache. After the transient
execution completes, the attacker measures the time required to read
each possible entry probe_array[i * 4096] for \(i \in \{0, 1, \dots, 255\}\).
A cache hit—indicating that the transient instruction accessed that specific entry—takes significantly fewer CPU cycles (typically tens of cycles) than a cache miss, which requires fetching data from main memory (typically hundreds of cycles). If entry \(k\) shows a markedly lower access time than all other entries, the attacker deduces that the secret byte was \(k\). Repeating this process across consecutive memory addresses allows the attacker to dump arbitrary secret memory.