Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Side-Channel Attacks and Transient Execution

Overview

Side-channel attacks exploit observable physical effects of computation — timing, power consumption, electromagnetic emissions — to extract secrets that should be protected by software abstractions. The most significant class discovered since 2018 is transient execution attacks, where speculative or out-of-order instructions leave microarchitectural traces even after being squashed. This chapter covers the taxonomy, mechanisms, and mitigations for Spectre, Meltdown, and related attacks.

The Side-Channel Taxonomy

graph TB
    SC[Side Channels] --> Timing
    SC --> Power
    SC --> EM[Electromagnetic]
    SC --> Acoustic
    Timing --> Cache[Cache Timing]
    Timing --> Branch[Branch Timing]
    Timing --> TLB[TLB Timing]
    Timing --> Exec[Execution Timing]
    Cache --> FlushReload[Flush+Reload]
    Cache --> PrimeProbe[Prime+Probe]
    Cache --> EvictTime[Evict+Time]
    Cache --> FlushFlush[Flush+Flush]
    Exec --> TE[Transient Execution]
    TE --> Spectre
    TE --> Meltdown
    TE --> Foreshadow
    TE --> MDS

Cache Timing Attacks

Why Caches Leak Information

Caches create observable timing differences:

Access time for:
  L1 cache hit:     ~4 cycles
  L2 cache hit:     ~12 cycles  
  L3 cache hit:     ~40 cycles
  Main memory:      ~200+ cycles

If an attacker can measure access time to a known address:
  Fast access  → line was in cache (someone else accessed it)
  Slow access  → line was not in cache

Flush+Reload

Prerequisite: Attacker and victim share memory (e.g., shared libraries, page deduplication).

# Attacker code (Flush+Reload)
# Step 1: Flush the probe addresses from all cache levels
for addr in probe_addrs:
    clflush(addr)  # x86 instruction: flush from all cache levels

# Step 2: Wait for victim to execute
wait_for_victim()

# Step 3: Reload and measure time
for addr in probe_addrs:
    t1 = rdtsc()       # read timestamp counter
    access(addr)       # load the address
    t2 = rdtsc()
    if (t2 - t1) < THRESHOLD:
        print(f"Victim accessed {addr}")
Real example — leaking an AES key byte:
  Victim's AES T-table lookup: table[secret_key_byte * 256 + plaintext_byte]
  Attacker probes: table[0], table[256], table[512], ..., table[255*256]
  Fast reload of table[K*256] → victim's key byte is K

Prime+Probe

Prerequisite: No shared memory needed. Works across cores, VMs, even processes.

# Attacker code (Prime+Probe)
# Step 1: PRIME — fill attacker's cache set with known data
for addr in cache_set_addrs:
    access(addr)  # Load into cache, evicting victim's data

# Step 2: Wait for victim to execute
wait_for_victim()

# Step 3: PROBE — re-access and measure which were evicted
for addr in cache_set_addrs:
    t1 = rdtsc()
    access(addr)
    t2 = rdtsc()
    if (t2 - t1) > THRESHOLD:
        print(f"Victim evicted {addr} → victim accessed same cache set")
AttackShared Memory?Cross-VM?GranularityNoise Level
Flush+ReloadRequiredNo (usually)Single cache lineVery low
Prime+ProbeNot requiredYesCache setModerate
Evict+TimeNot requiredYesCache setModerate
Flush+FlushNot requiredYes (Intel TSX)Single cache lineVery low

Transient Execution: The Fundamental Concept

What Is Transient Execution?

Transient execution occurs when the processor executes instructions along a mispredicted path or incorrectly speculated path. These instructions are architecturally squashed, but they leave microarchitectural traces (cache state, TLB entries, branch predictor updates) before being cancelled.

sequenceDiagram
    participant FE as Front-End
    participant EX as Execution Units
    participant C as Cache
    participant ROB as Reorder Buffer
    
    FE->>EX: Speculatively execute victim instructions
    EX->>C: Transient access to secret-dependent address
    Note over C: Cache state changed! (microarchitectural side effect)
    FE->>ROB: Misprediction detected
    ROB->>FE: Squash transient instructions
    Note over ROB: Architectural state unchanged
    Note over C: But cache state persists!

Interview Angle: “What is transient execution?” Instructions execute on a wrong path (mispredicted branch, faulting load) and are later squashed. The architectural state is rolled back, but microarchitectural side effects (cache state, TLB entries, predictor updates) remain. An attacker observes these side effects to infer secret data.

Spectre Variants

Spectre v1: Bounds Check Bypass (CVE-2017-5753)

Mechanism: Mispredict a conditional branch to speculatively access out-of-bounds memory.

// Victim function (in kernel, untrusted input)
char victim_function(size_t x) {
    if (x < array1_size) {          // Branch: x < bound?
        return array2[array1[x] * 256];  // Speculative: x may exceed bound
    }
    return 0;
}
Attack sequence:
  1. Train branch predictor: call with valid x many times → predicts TAKEN
  2. Call with x = out-of-bounds index (e.g., pointing to secret)
  3. Branch mispredicted TAKEN (x >= array1_size)
  4. Speculatively loads array1[secret_byte] → value V
  5. Speculatively accesses array2[V * 256] → loads into cache
  6. Misprediction detected → squashed architecturally
  7. BUT array2[V*256] is now in cache
  8. Attacker probes array2[0], array2[256], ... to find which is cached
  9. The cached index reveals secret_byte

Spectre v2: Branch Target Injection (CVE-2017-5715)

Mechanism: Poison the branch target buffer to misdirect indirect branches.

Attack: Cross-process / cross-VM
  1. Attacker trains BTB entry for a specific indirect branch
  2. The indirect branch (e.g., in kernel) is redirected to attacker-chosen gadget
  3. Gadget speculatively loads secret data into cache
  4. Attacker measures cache to recover secret

Mitigation: Retpoline (return trampoline), IBRS, STIBP

Spectre-BHB: Branch History Buffer (CVE-2022-38182, CVE-2022-29900, CVE-2022-29901)

Mechanism: Spectre v2 mitigations (retpoline) stop branch target injection but don’t prevent the branch history buffer from being poisoned to cause misprediction of conditional branches.

Spectre-BHB attack:
  1. Attacker executes a long sequence of branches to poison the BHB
  2. Victim's conditional branch mispredicts due to poisoned history
  3. Misprediction causes transient execution (like Spectre v1)
  4. But now the victim can be kernel code (cross-privilege)

Mitigation: EIBRS (Enhanced IBRS) on Intel, CSV2_3 on ARM

Spectre-RSB: Return Stack Buffer Underflow

Attack:
  1. Attacker causes many RET instructions without matching CALLs
  2. RSB underflows, returns stale/attacker-controlled addresses
  3. Attacker controls speculative execution after RET
  4. Gadget loads secret data into cache

Mitigation: RSB stuffing (insert dummy CALL/RET pairs on context switch)

Other Spectre Variants

VariantCVEMechanismScope
Spectre v1.1—Speculative store bypass (SSB)Cross-domain
Spectre v4CVE-2018-3639Speculative store bypass, no bounds checkSame thread
SpectreRewind—Rollback to older RSB entriesCross-privilege
Spectre-RSB—RSB underflowCross-privilege
Ret2spec—Return-based speculation via call/ret gadgetsCross-domain

Meltdown Variants

Meltdown (CVE-2017-5759)

Mechanism: Exploit out-of-order execution of faulting loads. Unlike Spectre (which uses branch misprediction), Meltdown uses the fact that loads execute before permission checks complete.

// Attacker code (user space)
// Attempt to read kernel memory directly
uint8_t probe = *(uint8_t *)(kernel_address + offset);  // Will FAULT

// But before the fault, OoO execution loads the value and uses it:
uint8_t dummy = array2[probe * 256];  // Transient: cache side effect

// Fault handler catches the access violation
// But array2[probe * 256] is already cached
Timeline:
  Cycle 0:  LOAD from kernel_address        → dispatched (no fault yet)
  Cycle 1:  OoO engine uses loaded value     → array2[value*256] accessed
  Cycle 3:  Permission check fails           → FAULT raised
  Cycle 4:  Pipeline flushed, state rolled back
  Cycle 5:  Fault handler runs
  
  Architectural state: correct (fault handled)
  Microarchitectural state: array2[value*256] is in cache ← LEAK

Meltdown Variants

VariantNameMechanismAffectedMitigation
OriginalMeltdownKernel access from user spaceIntel, some ARMKPTI / KAISER
Meltdown-BRRogue In-Flight DataBranch target from kernel dataIntelKPTI + IBRS
Meltdown-USUnauthorized ReadUnmapped/slow readsAll vendorsPage table isolation
Meltdown-GPGhost PotatoTransient execution on unmapped PTEsAll vendorsINVLPGB, TLB flush
Foreshadow / L1TFL1 Terminal FaultL1TF on Intel SGX, OS, SMMIntelL1D flush on VMENTRY

Foreshadow (L1 Terminal Fault)

Foreshadow exploits L1 data cache fill from faulting loads:
  1. Attacker triggers load from address that faults (PTE not present)
  2. L1 cache is filled with data from the physical page
  3. Even though fault is raised, the L1 cache line persists briefly
  4. Attacker in another hyperthread reads the stale L1 data
  
Three sub-variants:
  - Foreshadow-SGX: leaks SGX enclave data
  - Foreshadow-OS:  leaks OS/kernel data  
  - Foreshadow-SMM: leaks System Management Mode data

MDS (Microarchitectural Data Sampling)

CVE-2018-12130, CVE-2018-12126, CVE-2019-11091, CVE-2019-11135

MDS exploits internal CPU buffers (line fill buffers, store buffers, load ports) that temporarily hold data from any security domain:

graph TB
    subgraph "CPU Internal Buffers (leak data)"
        LFB[Line Fill Buffer<br/>holds data arriving from memory]
        SB[Store Buffer<br/>holds pending store data]
        LP[Load Port<br/>holds load data temporarily]
    end
    Attacker[Attacker on<br/>sibling hyperthread] -->|"transient access<br/>to sampled buffer"| LFB
    Attacker --> SB
    Attacker --> LP
    Victim[Victim on<br/>sibling hyperthread<br/>or different domain] -->|"fills buffer<br/>with secret data"| LFB
    Victim --> SB
    Victim --> LP
MDS VariantBuffer ExploitedCVEMitigation
ZombieLoadLine Fill BufferCVE-2018-12130L1D flush, MD_CLEAR
FalloutStore BufferCVE-2018-12126VERW instruction
RIDLInternal CPU bufferingCVE-2019-11091Microcode + kernel
Store-to-Leak ForwardingStore-forwarding logicCVE-2019-11135MD_CLEAR, VERW

Mitigations: Defense in Depth

Hardware Mitigations

MitigationMechanismPerformance Impact
KPTI / KAISERSeparate user/kernel page tables2–5% syscall heavy
RetpolineReplace indirect branches with return trampolines1–3% indirect heavy
IBRS / STIBPIndirect Branch Restricted Speculation2–15% (varies)
SSBDSpeculative Store Bypass Disable~1% store heavy
L1D FlushFlush L1 on context switch/VM entry1–3% VM workloads
MD_CLEAR / VERWClear internal buffers before returning to user<1%
Enhanced IBRSHardware-enforced IBRS (no retpoline needed)Minimal (Zen 2+, Ice Lake+)

Software Mitigations

1. Speculation barriers:
   - lfence (x86): prevents later instructions from executing before lfence completes
   - csdb (ARM):    constrain speculative data flow
   - barrier.i (RISC-V): instruction fence

2. Array indexing sanitization:
   - Use branchless access: array[(x & (size-1))]
   - Or: mask with constant-time comparison

3. Avoid secret-dependent control flow:
   - Use constant-time comparisons (memcmp, not string comparison)
   - Avoid: if (secret_byte == 0) { ... }  
   - Prefer: result |= (secret_byte == 0) * flag;

4. Disable speculation for critical code:
   - __builtin_ia32_lfence() (GCC/Clang)
   - -mretpoline compiler flag

Performance Impact Summary

WorkloadKPTI ImpactRetpoline ImpactCombined
Syscall-heavy (nginx, redis)3–5%1–2%4–7%
Database (PostgreSQL)2–4%2–5%4–9%
Compute-bound (blender)<1%<1%<2%
Virtualization (KVM)2–8%1–3%3–11%
SGX workloads——10–40%

Interview Angle: “What is the difference between Spectre and Meltdown?” Spectre exploits branch misprediction — the victim speculatively executes valid code along a wrong path. Meltdown exploits faulting loads — the CPU transiently executes an instruction that will fault. Both leave cache traces. Spectre requires training the predictor; Meltdown requires no training on vulnerable hardware.

Interview Questions

Q1: Explain how Flush+Reload works.

A: The attacker flushes specific cache lines, waits for the victim to execute, then reloads the same lines and measures access time. A fast reload indicates the victim accessed that cache line. This reveals which memory addresses the victim touched, potentially leaking secrets through cache timing.

Q2: How does Spectre v1 differ from Meltdown?

A: Spectre v1 tricks the CPU into mispredicting a conditional branch, causing speculative execution of valid instructions that access secret data. Meltdown directly executes an instruction that accesses unauthorized memory (e.g., kernel space from user space), relying on out-of-order execution before the permission check faults. Spectre requires branch prediction training; Meltdown works without training on vulnerable CPUs.

Q3: Why do transient execution attacks work even though instructions are squashed?

A: The CPU squashes the architectural effects (register values, program counter, memory writes). But microarchitectural side effects (cache state changes, TLB entries, branch predictor state updates) are not rolled back. These microarchitectural traces persist and are observable through timing measurements.

Q4: What is Prime+Probe and why is it more powerful than Flush+Reload?

A: Prime+Probe doesn’t require shared memory. The attacker fills a cache set with their own data (Prime), waits, then re-accesses it (Probe). If the victim accessed the same cache set, the victim’s access evicted one of the attacker’s lines, causing a slower Probe time. This works across VMs, cores, and even processes without shared pages.

Q5: How does retpoline mitigate Spectre v2?

A: Retpoline replaces indirect branch instructions (call *%reg) with a return-based trampoline that cannot be predicted by the BTB. The CPU’s return stack buffer (RSB) handles the return, and since the RSB is separate from the BTB, the attacker cannot poison the indirect branch target. However, retpoline doesn’t help against Spectre-BHB (branch history poisoning).

Summary

Attack TypeMechanismKey Mitigation
Spectre v1 (Bounds Check)Branch misprediction → OOB accesslfence, constant-time code
Spectre v2 (BTI)BTB poisoning → indirect branch gadgetRetpoline, IBRS
Spectre-BHBBranch history poisoningEIBRS, CSV2_3
MeltdownFaulting load transient executionKPTI (page table isolation)
Foreshadow / L1TFL1 terminal faultL1D flush on VM entry
MDS / ZombieLoadInternal buffer samplingMD_CLEAR, VERW instruction
Flush+ReloadShared cache timingCache partitioning
Prime+ProbeCache set eviction timingRandomized mapping

Cross-References