Connectionsside channeltimingsecurityisolationmicroarchitecturedefense

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.

Follow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
How can a program leak secrets without ever outputting them, purely through the timing effects of hardware optimisations?
What you wrote
This function returns a boolean and writes nothing else. If the caller is not authorised to see the secret, the secret has not left the function.
What the hardware does
The function left traces: cache lines evicted, branch predictor entries updated, TLB entries filled. Those traces persist after the function returns and change how long *subsequent, unrelated* operations take — which is measurable.
Isolation boundaries — between processes, between tenants, between a browser tab and its host — are enforced on the *architectural* state the ISA defines. Microarchitectural state is not part of that contract and historically was not isolated at all, so a boundary that looks airtight at the ISA level can be porous underneath it.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

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.

leaves tracesprimes, then probesfast or slowinfersVictim: access depends on a secretShared microarchitectural state (cache, predictor, TLB)Observer: times its own accessesSecret-dependent timing → bits
UserLLMAgentToolDataDecisionHumanGuardrail

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.

Common microarchitectural channels and their defensive posture
Shared structureWhat it remembersHow that becomes signalTypical mitigation
Data cacheWhich lines were recently touchedProbe timing reveals the victim's access addressesConstant-time access patterns; partitioning; flushing at boundaries
Branch predictorRecent branch outcomes and targetsMispredict rate reveals secret-dependent control flowBranchless code for secrets; predictor isolation or flushing
TLBRecent address translationsProbe timing reveals which pages were touchedPage-granular isolation; flushing across boundaries
Shared execution ports (SMT)Contention from the sibling threadThroughput variation reveals the sibling's instruction mixDo not co-schedule mutually distrusting work on one core
Frequency and power stateRecent activity levelsFrequency changes reveal workload characteristicsFixed 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.

  1. 1
    Secret → control or address: a branch condition or a memory index depends on secret data.
  2. 2
    Control or address → microarchitectural state: the access fills a cache line, trains a predictor or fills a TLB entry.
  3. 3
    State → residency change: the observer's previously primed state is displaced by exactly the amount the secret determined.
  4. 4
    Residency change → timing: the observer's subsequent accesses are measurably slower or faster depending on that displacement.
  5. 5
    Timing → inference: repeated over many trials, the timing distribution resolves into bits of the secret.
What people conclude from this — wrongly
  • "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

What it causes
  • • 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.
What you can do
  • • 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.
How to see it
  • • 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.
What it costs
  • • 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.

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.

Misconceptions

Claim
“Side channels are exotic and only matter to cryptographers.”
Reality
The hardware channels largely are specialist territory, but the underlying principle — that duration is an output — applies to ordinary application code. Token comparison that exits early, or a lookup that is fast only for existing accounts, leaks information with no hardware knowledge required.
Claim
“If the OS enforces process isolation, one process cannot learn anything about another.”
Reality
The OS enforces isolation of *architectural* state: memory, registers, files. Caches, predictors and translation buffers are shared microarchitectural resources that were never part of that contract, and their state can carry information across a boundary the architecture considers intact.
Claim
“Constant-time code just means avoiding obvious branches on secrets.”
Reality
It also means no secret-dependent memory addresses, since which cache line is touched is itself observable — and it means verifying the compiler did not reintroduce a branch, because timing is not an observable effect under the as-if rule and the optimiser is free to change it.