The first two parts treated the cache as an oracle for what a victim did. Spectre turns it into an oracle for what a victim's CPU speculated — code that, by the architecture's rules, never officially ran. It's the attack that made "microarchitectural state is security state" impossible to ignore.
- cve
- CVE-2017-5753 (Spectre variant 1, bounds-check bypass)
- mechanism
- speculative execution past an unresolved branch
- read-out
- FLUSH+RELOAD on a cache footprint left by the speculation
- impact
- read memory across a bounds/sandbox boundary (JIT, eBPF, in-process)
speculation leaves a trace it can't take back#
When the CPU hits a conditional branch it hasn't resolved yet (because the condition's inputs aren't in cache), it predicts the outcome and speculatively executes ahead. If the prediction was wrong, it discards the architectural results — registers, memory writes, all rolled back. But the cache is not rolled back. A line pulled in during speculation stays pulled in. That gap is the whole attack.
the gadget#
The canonical victim is an ordinary, correct-looking bounds check:
Run it many times with in-bounds x and the branch predictor learns "taken." Now:
|
The * 4096 spreads each of the 256 possible byte values onto its own page, so the
hardware prefetcher can't blur which line the speculation touched —
FLUSH+RELOAD then reads it cleanly:
Walk secret_addr one byte at a time and you read memory the bounds check was
supposed to protect — out of a JS JIT sandbox, across an eBPF bounds check, or
anywhere a trusted boundary is enforced by a branch.
reading past the check, one byte at a time
stopping it (and the cost)#
- Serialize the check. An
lfencebetween the bounds compare and the array access stops speculation from running ahead — the targeted, low-overhead fix compilers insert at sensitive branches. - Mask the index. Clamp
xto the array bounds with an arithmetic mask (x &= (size-1)) so even speculatively it can't go out of range. - Starve the read-out. Reduced timer resolution and removing precise timers
(browsers did this to
performance.now/SharedArrayBuffer) raise the noise on the FLUSH+RELOAD half.
Spectre v1's descendants — v2's branch-target injection (answered by retpolines), Meltdown's fault-suppressed loads — all share this spine: a transient, "rolled-back" execution that nonetheless stamps the cache, read back by the oracles from earlier in this series. The uncomfortable lesson is architectural: correctness guarantees cover architectural state, and security had been quietly assuming that was all the state there is.