Spectre - Meltdown speculative side channels
Core Mechanism: The Speculative Execution Window
How Speculative Execution Creates Vulnerabilities
Modern out-of-order processors maintain the architectural state (registers, memory visible to programs) separate from the microarchitectural state (caches, branch predictors, TLBs). When speculation occurs:
WHY speculation exists: Memory is ~200× slower than CPU. Waiting for every branch or memory access would waste billions of cycles. Branch prediction + speculative execution let the CPU work on probable paths while waiting.
WHAT goes wrong: The CPU promises that wrong speculation is architecturally invisible — mispredicted instructions never commit, registers roll back. But it ONLY rolls back architectural state. Cache contents, a microarchitectural side effect, persist.
HOW the attack works:
- Train the predictor (make CPU expect a pattern)
- Trigger speculative execution with malicious code
- Extract secrets through cache timing side channels
Meltdown: Breaking Kernel Isolation
Derivation: Why Kernel Memory Becomes Accessible
Step 1: Normal Protection Mechanism
In x86, the supervisor bit in page tables marks kernel memory. User-mode access should trigger:
Step 2: Out-of-Order Timing Gap
The protection check happens during the retire stage (when instruction commits). But execution stage runs first:
Time 0: Load kernel_addr issued
Time 1: Load executes out-of-order (data fetched to cache)
Time 2: Load retires → permission check → #PF raised → instruction aborted
Gap = Time 1 to Time 2 where kernel data is in cache.
Step 3: Encoding Secret into Cache
// Attacker code
char kernel_secret = *kernel_addr; // Faults, BUT executes first
char dummy = probe_array[kernel_secret * 4096]; // Also executes before faultWHY this step?: After fault, kernel_secret is lost. But if kernel_secret =0x41, the cache line holding probe_array[0x41000] is now cached. The attacker measures which cache line is hot.
The byte value is where .
WHY 4096-byte spacing? Cache lines are typically 64 bytes. Spacing by page size (4 KB) ensures each byte value maps to a DIFFERENT cache line, avoiding false sharing.
// 2. Speculative load of kernel memory if (0) { // Always false, but predictor might mispredict char secret = (char)kernel_address; // This load executes speculatively before page fault probe_array[secret * 4096] = 1; // Encodes secret in cache }
// 3. Measure timing to recover secret for (int i = 0; i < 256; i++) { uint64_t start = __rdtsc(); char dummy = probe_array[i * 4096]; uint64_t time = __rdtsc() - start; if (time < CACHE_THRESHOLD) printf("Secret byte =0x%02x\n", i); }
**Why this step?** The `if (0)` block never executes architecturally, but branch predictors have multi-bit history. If we train them with `if (1)` earlier, they might speculatively enter the block once.
## Spectre: Poisoning the Branch Predictor
> [!definition] Spectre (CVE-2017-5753, CVE-2017-5715)
> A class of attacks that ==train the CPU's branch predictor== to mispredict a security check, causing speculative execution of attacker-chosen code paths that leak secrets through microarchitectural state. Unlike Meltdown, victim code itself performs the leaky operations.
### Variant 1: Bounds Check Bypass (BCB)
**Scenario**: Code with a bounds check:
```c
if (x < array_size) {
y = array[x];
z = data[y * 4096]; // Dependent load
}
Attack Setup:
- Train the predictor: Call with valid
xthousands of times → predictor learns branch is taken - Exploit: Call with malicious
x = address - &array- Predictor mispredicts branch as taken
- Speculative path loads
array[x](actuallyaddress), encodes indata[y*4096]
- Extract: Measure cache timing on
data[]
Why it works: The comparison x < array_size takes ~cycles to compute (may involve memory fetch for array_size). Branch predictor decides in1 cycle using history. Speculative window = compute time.
// Attacker training phase for (int i = 0; i < 1000; i++) { _mm_clflush(&array1_size); // Slow down the check victim_function(i % array1_size); // Train branch as taken }
// Attack _mm_clflush(&array1_size); size_t malicious_x = (size_t)&secret - (size_t)array1; victim_function(malicious_x); // Speculatively reads secret
// Timing analysis recovers secret
**Why flush array1_size?** Makes the comparison `x < array1_size` slower (cache miss), widening the speculative window.
### Variant 2: Branch Target Injection (BTI)
**Mechanism**: Poison the ==Branch Target Buffer (BTB)==, which predicts indirect jump targets:
```c
void (*func_ptr)(void) = user_input;
func_ptr(); // Indirect call
Attacker trains BTB to mispredict func_ptr destination → CPU speculatively executes gadget code in victim's address space.
Why indirect branches? Direct branches encode target instruction. Indirect branches (function pointers, virtual calls, returns) require prediction — the BTB stores (branch_address, history) → target_address.
BT aliasing: BTBs index with low address bits. Different virtual addresses with matching low bits collide:
Attacker code at 0x1234 can poison entry used by victim code at 0xFF1234.
Why it's wrong: Spectre affects ANY code with:
- Conditional branches based on secret-dependent data
- Speculation windows (all modern CPUs)
- Shared microarchitectural state (even between processes)
Spectre has been demonstrated in: OS kernels, hypervisors, TEs (SGX), cryptographic libraries, language runtimes. The vulnerability is in the CPU's optimization strategy, not software.
The fix: Software mitigations (retpoline, speculation barriers) + microcode updates + architectural changes (IBRS, STIBP).
Mitigations and Performance Trade-offs
Software Mitigations
- KPTI (Kernel Page Table Isolation) — Meltdown
- Separate page tables for user/kernel mode
- Kernel memory unmapped in user page tables
- Cost: Every syscall/interrupt flushes TLB (5-30% overhead)
-
Retpoline — Spectre v2
- Replace indirect jumps with return-based trampoline
- Exploits return stack buffer (RSB) instead of BTB
; Instead of: jmp *%rax call set_up_target capture_spec: pause jmp capture_spec set_up_target: mov %rax, (%rsp) ; Overwrite return address ret ; RSB predicts capture_spec, actual target is rax- Cost: ~10-30% for indirect-call-heavy workloads
-
Speculation Barriers — Spectre v1
lfence(x86): Serialize execution, block speculation
if (x < size) { lfence(); // Force condition resolution before continuing y = array[x]; }- Cost: 20-40 cycles per barrier
Typical ranges:
- Syscall-heavy: 20-30% (KPTI)
- Indirect-call-heavy: 15-25% (Retpoline)
- Tight bounds-check loops: 5-10% (lfence)
Hardware Mitigations
Modern CPUs add:
- IBRS (Indirect Branch Restricted Speculation): Disable predictor sharing
- STIBP (Single Thread Indirect Branch Predictors): Isolate sibling hyperthreads
- SSBD (Speculative Store Bypass Disable): Block speculative load-store bypassing
WHY not just disable speculation? Would reduce performance by 2-5×. Modern architectures rely on it.
// With lfence mitigation for (int i = 0; i < N; i++) { if (index[i] < SIZE) { _mm_lfence(); sum += array[index[i]]; } }
// Measure: cycles = rdtsc_after - rdtsc_before // Typical: ~40 cycles/iteration → ~60 cycles/iteration
**Why this step?** Quantifies real impact. Microbenchmarks show worst case; real apps average ~5-10% due to other bottlenecks.
> [!recall]- Explain to a 12-Year-Old
> Imagine a super-fast student (CPU) doing homework. To go faster, they start working on the next problem before finishing the current one — that's speculation. They guess what the next problem is by looking at patterns.
Now imagine some problems are locked in a teacher-only cabinet (kernel memory). One day, the student realizes: "If I sneak a peek at the locked cabinet WHILE I'm guessing, and touch a specific toy depending on what I see, then check which toy is warm later... I can figure out the secret answers!" The teacher says "Hey, you weren't allowed to look!" and makes the student erase their written answer. But the toy is still warm — the evidence is still there.
That's Spectre/Meltdown. The CPU erases its wrong answers but forgets to erase the "warm toy" (cache). Hackers measure which toy is warm to steal secrets. The fix: better locks (KPTI), slower but safer guessing (retpoline), and teaching the CPU to double-check before touching toys (lfence).
> [!mnemonic] S.P.E.C.T.R
> - **S**peculative execution before checks
> - **P**redictor poisoning (training phase)
> - **E**ncoding secrets in cache state
> - **C**ache timing reveals information
> - **T**ransient instructions leave traces
> - **R**ollback only fixes registers (not cache)
> - **E**xploit microarchitectural side effects
## Connections
- [[Branch Prediction]] — trained predictors enable Spectre attacks
- [[Cache Memory Hierarchy]] — timing differences enable side channels
- [[Virtual Memory and Paging]] — page table isolation (KPTI) mitigates Meltdown
- [[Out-of-Order Execution]] — creates speculative windows
- [[Privilege Levels and Protection Rings]] — attacked isolation boundaries
- [[TLB (Translation Lookaside Buffer)]] — flushed by KPTI, performance cost
- [[Hyperthreading and SMT]] — shared microarchitectural state increases attack surface
---
#flashcards/hardware
What is the key difference between Meltdown and Spectre? :: Meltdown exploits out-of-order execution to directly read kernel memory from user space (CPU bug), while Spectre trains the branch predictor to trick victim code into leaking its own secrets (affects correctly-working CPUs).
What are the two microarchitectural states that create the vulnerability? ::: Architectural state (registers, memory — correctly rolled back) and microarchitectural state (cache, branch predictors — NOT rolled back after mispeculation).
Why is the probe array spaced 4096 bytes apart in cache timing attacks? ::: To ensure each possible byte value (0-255) maps to a DIFFERENT cache line, avoiding false sharing.4 KB = page size > cache line size (64 B).
What is the speculative execution window? ::: The time gap between when a speculative instruction executes and when the CPU realizes the speculation was wrong and aborts. Typically 10-50 cycles. Created by branch prediction latency or slow condition computation.
How does retpoline mitigate Spectre v2? ::: Replaces indirect jumps with a return-based trampoline that exploits the return stack buffer (RSB) instead of the poisonable branch target buffer (BTB), preventing attacker-controlled speculation targets.
What does KPTI do and what is its cost? ::: Kernel Page Table Isolation unmaps kernel memory from user-mode page tables, blocking Meltdown. Cost: 5-30% overhead from TLB flushes on every syscall/interrupt.
What makeslfence (speculation barrier) effective against Spectre v1? ::: It serializes execution — forces the CPU to fully resolve the condition before continuing, eliminating the speculative window where bounds checks can be bypassed.
Why can't you fully disable speculation to fix these vulnerabilities? ::: Modern CPUs rely on speculation for performance — disabling it would cause 2-5× slowdown. Speculation hides memory latency (200+ cycles) and branch misprediction costs (10-20 cycles).
What is BTB aliasing and how does it enable Spectre v2? ::: The Branch Target Buffer indexes using low address bits. Attacker code at 0x1234 can poison the BTB entry used by victim code at 0xFFFF1234, causing mispredicted indirect jumps to attacker-chosen gadgets.
How does cache timing convert microarchitectural state into observable information? ::: Cache hits (~4 ns) are ~50× faster than memory mises (~200 ns). By measuring access times to probe array locations, attackers determine which cache line was brought in by speculative execution, revealing the secret value.
## 🖼️ Concept Map
```mermaid
flowchart TD
A[Speculative Execution] -->|hides latency of| B[Slow Memory ~200x]
A -->|guided by| C[Branch Prediction]
A -->|separates| D[Architectural State]
A -->|leaves traces in| E[Microarchitectural State]
D -->|rolled back on| F[Misprediction]
E -->|NOT rolled back| F
E -->|includes| G[Cache Contents]
G -->|leaked via| H[Cache Timing Side Channel]
A -->|enables attacks| I[Spectre]
A -->|enables attacks| J[Meltdown]
J -->|reads| K[Kernel Memory]
J -->|exploits gap before| L[Page Fault #PF]
L -->|checked at| M[Retire Stage]
K -->|encoded into| G
G -->|decoded by| H
H -->|extracts| N[Secret Value]
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, modern CPUs bahut fast hone ke liye speculation karte hain — matlab future ko predict karke pehle se hi instructions execute kar dete hain, branch prediction use karke. Lekin problem yahan hai: jab CPU ko pata chalta hai ki uska guess galat tha, tab wo results ko discard kar deta hai (registers roll back ho jate hain), lekin cache mein jo footprints reh gaye, unhe clear nahi karta! Yahi vulnerability hai.
Meltdown attack mein attacker out-of-order execution ka fayda uthata hai. User space se kernel memory ko directly read karta hai — normally ye page fault trigger karega, par CPU pehle instruction execute kar deta hai, fir check karta hai.Uss speculative execution window mein kernel data cache mein aa jata hai. Fir cache timing side channel se (fast access = cache hit, slow = cache miss), attacker wo secret data recover kar leta hai. Formula simple hai: probe_array[secret * 4096] access karke, fir measure karo ki konsi cache line hot hai — wahi secret byte reveal karta hai.
Spectre attack thoda alag hai —isme attacker branch predictor ko train karta hai bahut baar valid