Side Channels: When Performance Optimisations Leak
Every mechanism that makes a CPU fast by remembering something — caches, branch predictors, translation buffers — creates state that outlives the operation and can be observed indirectly through timing. Information leaks not through what a program outputs, but through how long other things take afterwards.
Software view, hardware view
The gap between what you wrote and what the machine does is where this whole domain lives.
The shape of the problem
A side channel is any path that carries information the designer did not intend as a communication channel. In hardware the recurring substrate is shared, stateful, performance-motivated structures: a cache shared between processes on a core, a branch predictor shared between threads, a translation buffer shared across a context switch. Each remembers what recent execution did, and each makes future operations faster or slower depending on what it remembers.
That gives an observer a primitive: prime a structure into a known state, let the victim run, then measure how long your own accesses take. Slow means the victim displaced your state; fast means it did not. Repeated, this converts "which line did the victim touch" into bits — and if the victim's access pattern depends on a secret, the secret is what leaks.
The critical framing for engineers is that nothing is broken in the conventional sense. The cache is behaving exactly as designed. The isolation the OS provides — separate address spaces, permission bits, privilege levels (What Actually Stops One Process Reading Another's Memory, Why Kernel Mode Is Actually Privileged) — is also working exactly as designed. The leak lives in a layer the security model never described, which is why these vulnerabilities were surprising rather than sloppy.
Why data-dependent timing is the root cause
The channel only carries a secret if something the secret controls changes the microarchitectural footprint. If a comparison exits early on the first mismatching byte, its duration reveals how many bytes matched. If a lookup indexes a table by a key byte, which cache line is touched reveals that byte. If a branch is taken or not based on a secret, the predictor's state reveals the condition.
This is why the standard defence for cryptographic code is constant-time programming: no branch and no memory access whose address depends on secret data. The routine performs the same operations, touching the same lines, regardless of the value it is processing. It is slower on average than a data-dependent version — that is precisely the trade being made, and it is why constant-time code must not be "optimised" by a well-meaning later contributor, or by a compiler that decides a branch is cheaper.
Note the interaction with the previous lessons: the compiler is entitled to reintroduce a data-dependent branch while preserving the result, because timing is not an observable effect under the as-if rule (The Compiler Reordered It Before the CPU Did). Constant-time code therefore depends on compiler-specific guarantees or careful inspection of the emitted instructions — a rare case where reading the assembly is not optional.
| Shared structure | What it remembers | How that becomes signal | Typical mitigation |
|---|---|---|---|
| Data cache | Which lines were recently touched | Probe timing reveals the victim's access addresses | Constant-time access patterns; partitioning; flushing at boundaries |
| Branch predictor | Recent branch outcomes and targets | Mispredict rate reveals secret-dependent control flow | Branchless code for secrets; predictor isolation or flushing |
| TLB | Recent address translations | Probe timing reveals which pages were touched | Page-granular isolation; flushing across boundaries |
| Shared execution ports (SMT) | Contention from the sibling thread | Throughput variation reveals the sibling's instruction mix | Do not co-schedule mutually distrusting work on one core |
| Frequency and power state | Recent activity levels | Frequency changes reveal workload characteristics | Fixed frequency for sensitive work; restrict counter access |
What this means for engineers who are not cryptographers
Most engineers will never write a constant-time routine, and should not: cryptographic primitives belong in reviewed libraries written by specialists, and hand-rolling them is a far larger risk than any side channel. The parts that generalise are more mundane and more widely applicable.
First, timing is an output. Any code path whose duration depends on a secret — a token comparison, a username lookup that short-circuits when the account does not exist, a cache that is populated only for valid keys — is leaking something, and this reasoning applies at application level with no hardware knowledge required. Constant-time comparison functions exist in every standard library for exactly this reason.
Second, shared hardware weakens isolation. Co-tenancy on a physical core is a different security posture from separate machines, which is why cloud providers offer dedicated instances and why disabling SMT is a recognised hardening step (SMT: Two Contexts, One Core, What a vCPU Actually Is). If you run mutually distrusting workloads, the topology is part of your threat model.
Third, mitigations cost performance, sometimes a great deal. That trade-off is a real engineering decision requiring a real threat model, not a checkbox — which is the theme Spectre and Meltdown: When Speculation Crossed a Boundary takes up in detail.
- Use vetted libraries for anything cryptographic; constant-time implementation is specialist work.
- Treat duration as an output — use constant-time comparison for secrets and avoid early exit on secret-dependent conditions.
- Model your co-tenancy — mutually distrusting workloads on one physical core is a deliberate risk, not a neutral default.
- Expect the compiler to interfere — it may reintroduce data-dependent branches, since timing is not an observable effect.
- Price the mitigations — hardening is a measurable performance cost that needs a threat model to justify.
Key points
- Side channels arise from shared, stateful, performance-motivated hardware that remembers what recent execution did.
- The leak is indirect: an observer times its own operations and infers what the victim touched.
- Architectural isolation can be intact while microarchitectural state — never part of the ISA contract — carries information across the boundary.
- The root cause is always secret-dependent timing or footprint; constant-time code removes the dependence rather than hiding it.
- Application engineers mainly need three habits: treat duration as output, use vetted crypto, and treat co-tenancy as part of the threat model.
Follow the mechanism
The path through the machine, hop by hop — and the conclusions it invites that are wrong.
- 1Secret → control or address: a branch condition or a memory index depends on secret data.
- 2Control or address → microarchitectural state: the access fills a cache line, trains a predictor or fills a TLB entry.
- 3State → residency change: the observer's previously primed state is displaced by exactly the amount the secret determined.
- 4Residency change → timing: the observer's subsequent accesses are measurably slower or faster depending on that displacement.
- 5Timing → inference: repeated over many trials, the timing distribution resolves into bits of the secret.
- • "Our process boundaries are enforced by the OS, so we are isolated" — architectural isolation says nothing about shared microarchitectural state.
- • "The function returns only a boolean, so nothing leaks" — how long it took to return that boolean is also an output.
- • "This requires physical access" — many of these channels are reachable from co-resident software, which is the normal cloud condition.
- • "We applied the mitigations, so it is solved" — mitigations target known channel families; the class of problem is structural.
Consequences, controls and cost
- • Process and VM isolation can be weaker in practice than the architectural model suggests, particularly under co-tenancy.
- • Naive secret comparison leaks match length through early exit, which is exploitable without any hardware expertise.
- • Mitigations such as flushing shared state at boundaries impose ongoing, sometimes substantial, performance cost.
- • Disabling SMT is a real and sometimes recommended hardening measure with a real throughput penalty.
- • Use reviewed cryptographic libraries rather than implementing primitives; this eliminates the largest class of exposure.
- • Use constant-time comparison for tokens, MACs and passwords so that duration does not depend on how much matched.
- • Avoid secret-dependent branches and secret-indexed table lookups in code handling key material.
- • Do not co-schedule mutually distrusting workloads on the same physical core; treat topology as part of the threat model.
- • Verify the emitted instructions for constant-time routines, because the compiler may legally reintroduce data dependence.
- • Time secret-handling code paths across varying inputs and check the distribution is independent of the secret.
- • Inspect the emitted assembly of constant-time routines to confirm no data-dependent branch or indexed access survived.
- • Review deployment topology for co-residency of mutually distrusting workloads.
- • Benchmark before and after enabling hardening so the performance cost of the security posture is a known number.
- • Constant-time code is slower than data-dependent code by construction, and harder to read and maintain.
- • Flushing shared state at trust boundaries adds cost to every boundary crossing, including hot ones.
- • Disabling SMT can cost significant throughput on workloads that benefit from it.
- • Dedicated hardware removes co-tenancy risk at substantially higher infrastructure cost.
Scope
§224 — what these claims are specific to.
- MICROARCH-SPECIFICWhich structures are shared, at what granularity, and what state survives a context switch differ substantially between CPU generations and vendors; a channel present on one microarchitecture may be absent or differently shaped on another.
- GENERALThe structural point — shared stateful optimisation creates observable side effects — holds for any design that shares performance state across a trust boundary.
- PLATFORM-SPECIFICAvailable mitigations, whether SMT can be disabled, and how much microarchitectural state is flushed at boundaries depend on the CPU, firmware, hypervisor and operating system in use.