Computer Architecture · Branch Prediction
Speculative Execution and the Architectural Side Effects of a Wrong Guess
Squashing removes speculative state from the architecture but not from caches, predictors, and timing; how that residual state becomes an observable channel and what mitigation costs.
The recovery mechanisms described in misprediction recovery have a precise contract: no architecturally visible effect of a wrong-path instruction may survive. Registers are restored from checkpoints, stores never reach the cache hierarchy, and the program counter is redirected. That contract is about the architectural state, and it holds. It says nothing about the microarchitectural state — cache contents, predictor tables, TLB entries, the readiness of a functional unit — and that state is shared between the speculative execution and everything that follows it, including code belonging to another privilege level or another process. The residual is the whole basis of a family of attacks, and it turns a performance mechanism into a security boundary problem.
The gap between squash and undo
A wrong-path load is never allowed to write a register or a memory location. It is still issued. It still translates its address through the TLB, still checks its tags in the data cache, and still fills a line if it misses. None of those actions is architecturally visible; all of them change the machine’s future timing.
The same applies to the predictor. A wrong-path branch is fetched, and its address may be installed in the branch target buffer or the direction tables. The history register is restored to its checkpoint value, but the allocator’s decisions about which entries were replaced are not. A wrong-path execution can therefore evict entries that the correct path will later need, changing the latency of correct-path execution in a way that depends on what the wrong path did.
This is not a bug in the recovery logic. Distinguishing “state that must be restored” from “state that may be left dirty” is the design point: restoring caches on every squash would cost far more than the misprediction itself, and would eliminate the very thing that makes speculation worth doing. The recovery mechanism optimises for the architectural contract and treats microarchitectural residue as harmless. Under an adversarial model, it is not.
Turning timing into a data channel
The classic observation is that a cache’s contents can be inferred from access times. If an attacker can measure how long a load takes, and the load hits or misses depending on whether a line is resident, then the attacker can read one bit of information: was the line present. Percival’s 2005 work on cache-based attacks against cryptographic implementations established the technique in a same-privilege-level setting; it has been used since to recover keys from table-driven ciphers whose table accesses leak the secret index.
Speculative execution extends the reach of that technique across a boundary the architecture is supposed to enforce. The pattern has four parts:
- Mistrain a predictor. Cause a branch to be predicted in a direction that will later be wrong, by training it with inputs the attacker controls.
- Trigger the speculation. Arrange for the branch to be executed with the wrong prediction and a secret in a register or a memory location the attacker cannot architecturally read.
- Encode the secret into microarchitectural state. On the wrong path, use the secret as an index into a table so that the cache line touched depends on the secret’s value. Nothing is written to memory; only the cache is affected.
- Measure. After the squash, time the table accesses. The fast one reveals the secret.
Every step uses a mechanism designed for performance. The predictor’s training is the learning behaviour described in every article in this cluster. The speculation window is the out-of-order window. The cache is the cache. The measurement is timing.
The precondition in step 2 is that a wrong-path instruction can read a value it should not be able to read — because the bounds check or privilege check that would stop it has not retired yet. In a correctly speculative machine, the wrong-path load executes before the check resolves, and the check eventually fails, so the load’s result is discarded. The cache line it touched is not.
Which predictors are the lever
The attack needs the predictor to be trainable by the attacker and confined to a structure that the attacker can influence. Two training styles exist.
Same-process, same-address. The attacker trains a branch in code it controls, so the predictor entry is directly influenced. The victim code executes the same branch. This is the simplest form and the easiest to mitigate by tagging predictor entries with an address-space or context identifier.
Cross-process aliasing. The predictor’s index is a hash of the address, so entries are shared between address spaces. An attacker can train an entry with an address that collides with a victim branch’s entry and influence the victim’s prediction without ever executing the victim’s code. This is the harder case, because it requires the predictor to be isolated per context rather than merely controlled.
A third style trains the return address stack or the indirect branch predictor so that a victim’s indirect call or return jumps to an attacker-chosen target. Returns are particularly exposed because the stack’s correctness depends on a disciplined call-and-return pattern, and any speculation that desynchronises it changes where a later return predicts.
The common thread: any structure that (a) is updated speculatively, (b) is indexed by something the attacker can influence, and (c) affects the fetch address, is a candidate lever. Isolation by tagging entries with an address-space identifier, and flushing the relevant state on a context switch, are the direct defences. Both cost area or cycles, and both reduce the predictor’s effectiveness in the benign case, because entries that would otherwise be reused across a context switch must be retrained.
Why the mitigations are expensive
The architectural mitigations fall into three families, and each is a statement about how much performance the design will give up.
Serialising instructions. An instruction that stops speculation at a boundary — a fence that prevents younger instructions from executing until the fence retires — removes the window the attack needs. The cost is the window itself: everything the out-of-order engine would have overlapped is now serialised. Placed at a trusted boundary, the instruction is cheap; placed often, it removes most of the benefit of speculative execution.
Index masking. Forcing array accesses whose index is not provably in range to be clamped to a safe value removes the attacker’s ability to make the wrong-path load touch an arbitrary line. This is a compiler transformation and costs a few instructions per access, plus a register for the mask.
Predictor and cache partitioning. Tagging predictor entries and caches with a context identifier prevents cross-domain training. The cost is that a context switch invalidates the ability to reuse predictions and cached data, which matters most for workloads with many short-lived processes.
Every one of these is a performance cost paid to restore a property the architecture claimed to provide without them. The asymmetry is unavoidable: the machine’s performance comes from overlapping work whose effects are not yet committed, and the security property is that uncommitted effects must not be observable. The two are in direct tension, and the resolution is always a partial one — bound the leak rate rather than eliminate the channel.
What the model of “architectural state” misses
An architecture manual specifies the state a program can observe: registers, memory, and the effects of instructions on them. It says nothing about caches, predictors, and translation structures, because those are implementation details, and the manual’s contract is defined over the architectural state alone.
That contract was always a statement about which values the program sees, not about how long it takes to see them. Two implementations of the same architectural state may differ arbitrarily in timing. Constraining timing is a different kind of specification, and it is the one that side-channel mitigations impose: not “this load returns the right value” but “this load takes the same time whether or not a given line is resident”.
The gap between those two specifications is where the whole class of attacks lives, and it is not closed by fixing any particular mechanism. Closing it fully would require constant-time behaviour across the cache hierarchy, the predictor, and the execution units, which is incompatible with the purpose of all three. What is achievable is a bound on what can be learned per unit of time, enforced by making the timing less informative: keeping attacker and victim states separate, limiting speculation at trust boundaries, and making fine-grained timing harder to obtain.
The cost of a predictor is now measured twice
Branch prediction research optimised a single quantity for decades: mispredictions per instruction, at a given storage cost. That is still the primary metric. What changed is that a predictor’s behaviour under adversarial training is now a second-order criterion, and it can conflict with the first. A predictor that shares entries across contexts is more accurate on a multiprogrammed workload and more exposed. A predictor that isolates contexts is slightly less accurate and measurably more expensive in storage.
The same tension appears in recovery. A core that squashes quickly and refills immediately recovers performance fastest, and the speed of recovery is exactly what makes the residual state easier to observe: a fast squash means the wrong-path work was deep and its cache effects were widespread. A core that delayed speculative loads until their address was known to be safe would close the channel and give up the latency hiding that motivates load speculation in the first place.
The mechanisms in this cluster — a fetch unit betting on a direction, a table of counters learning which way, a checkpoint that unwinds the bet — are all answers to the same structural problem: the program counter must advance before the program’s control flow is known. Every answer improves the odds, and none removes the residual that a wrong bet leaves in the machine.
References
- Spectre Attacks: Exploiting Speculative Execution — Kocher, Horn, Fogh, Genkin, Gruss, Haas, Hamburg, Lipp, Mangard, Prescher, Schwarz, Yarom; IEEE S&P 2019
- Meltdown: Reading Kernel Memory from User Space — Lipp, Schwarz, Gruss, Prescher, Haas, Mangard, Kocher, Genkin, Yarom, Hamburg; USENIX Security 2018
- Cache Missing for Fun and Profit — Colin Percival, BSDCan 2005
- Computer Architecture: A Quantitative Approach, 6th edition — Chapter 3 — Hennessy & Patterson; Morgan Kaufmann