Side Channels and Architectural Security

A safe-cracker in an old film presses an ear to the dial and listens for the tumblers. She never reads the combination off a label — she infers it from a physical side effect of the lock doing its job. That is a side channel, and modern processors are riddled with them. Spectre and Meltdown were dramatic special cases; this lesson steps back to the general principle. Almost every trick that makes a CPU fast — caches, speculation, shared execution units, shared DRAM — creates a physical signal that leaks information across a boundary that was supposed to be airtight.

The unifying idea: isolation is a software abstraction, but the hardware underneath is shared, and shared hardware carries whispers between its users. We will look at timing side channels, at Rowhammer (where the leak becomes an active attack on memory), at how to write code that keeps its mouth shut, and at the deep tension this all reveals between speed and secrecy.

Timing side channels: the cache tells tales

The most productive side channel is the cache, because a hit and a miss differ by an order of magnitude in latency, and that timing is measurable from another program. If a victim's memory access pattern depends on a secret — say, an AES table lookup indexed by a key byte — then which cache lines it touches is the secret, exposed to anyone who can watch the cache. Three classic techniques harvest it:

TechniqueAttacker's moveWhat the timing reveals
Flush+Reloadflush a shared line, later time reloading itthe victim accessed that exact line (fast reload)
Prime+Probefill a cache set with your own data, later re-time itthe victim evicted one of your lines (a slot they used)
Evict+Timeevict a line, then time the victim as a wholewhether the victim needed that line this run

Flush+Reload needs shared memory (e.g. a shared library, or deduplicated cloud pages) and is precise; Prime+Probe needs no sharing and works across virtual-machine boundaries, which is what makes it a cloud-tenant's nightmare. Both have been used to lift full cryptographic keys from a co-located victim.

Constant-time programming: don't let secrets steer the machine

The defence at the software layer is a discipline called constant-time programming. The name is slightly misleading — it is not about being fast, but about making execution independent of secret data. Two rules: a secret must never decide which way a branch goes (branch timing and predictor state leak it), and a secret must never decide which memory address is touched (cache state leaks it). Consider the archetypal bug — a password check that bails out at the first wrong byte:

// Why early-exit comparison leaks: the number of iterations depends on the secret. // A constant-time compare always scans the whole buffer, revealing nothing via timing. function earlyExitEqual(guess: string, secret: string): boolean { if (guess.length !== secret.length) return false; for (let i = 0; i < secret.length; i++) { if (guess[i] !== secret[i]) return false; // BAILS as soon as a byte is wrong } return true; } // Count how many byte-comparisons run — a proxy for the timing an attacker observes. function iterationsUsed(guess: string, secret: string): number { let n = 0; for (let i = 0; i < secret.length; i++) { n++; if (guess[i] !== secret[i]) break; } return n; } const secret = "SWORDFISH"; for (const g of ["AAAAAAAAA", "SAAAAAAAA", "SWORAAAAA", "SWORDFISH"]) { console.log(`guess ${g}: ${iterationsUsed(g, secret)} comparisons ->`, earlyExitEqual(g, secret)); } console.log("The more prefix bytes are correct, the longer it runs — a timing oracle!"); // Constant-time compare: fold every byte into an accumulator, no early exit, no secret-indexed access. function constantTimeEqual(a: string, b: string): boolean { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) { diff = diff | (a.charCodeAt(i) ^ b.charCodeAt(i)); // OR of all byte-differences } return diff === 0; // one comparison, timing is flat } console.log("constant-time result:", constantTimeEqual("SAAAAAAAA", secret));

The early-exit version leaks the secret one byte at a time: guess the first byte until the check takes a touch longer, lock it in, move to the second. This is exactly how naive \texttt{memcmp}-based token checks have been broken. The constant-time version folds every byte into an accumulator with no branch and no secret-dependent index, so its running time is flat regardless of the secret.

Rowhammer: when reading is writing

Side channels leak information; Rowhammer goes further and corrupts it. As DRAM cells shrank, adjacent rows became so tightly coupled that repeatedly activating one row — "hammering" it thousands of times before the memory's periodic refresh — leaks enough charge to flip a bit in a neighbouring row you never had permission to touch. It is a DRAM disturbance effect turned into an exploit.

The consequences are startling: by hammering carefully chosen rows an attacker can flip a bit in a page-table entry and hand themselves kernel privileges, or flip a bit in another tenant's data — all without any software bug in the victim. Defences include Target Row Refresh (TRR), doubling the refresh rate, and — coming full circle — ECC memory, though researchers have since shown patterns ("many-sided" hammering, TRRespass) that defeat many of these in turn. Rowhammer is the clearest proof that a reliability margin and a security boundary are the same wall seen from two sides.

The fundamental tension: speed versus isolation

Step back and a pattern emerges that will outlive any single vulnerability. Every performance optimisation in this course is a form of sharing or prediction, and every act of sharing or prediction is a potential side channel. Caches share fast memory between contexts — cache-timing attacks. Speculation predicts the future and acts early — Spectre. Simultaneous multithreading shares execution ports between two threads — port-contention attacks. Shrinking DRAM shares charge between rows — Rowhammer. Shared page deduplication saves RAM — and enables Flush+Reload.

Perfect isolation would mean partitioning every resource so no two security domains ever share state — which throws away most of the speed those shared resources bought. So architects live on a spectrum: partition the most sensitive resources (per-domain cache ways, flushing predictors on context switch, disabling deduplication for secrets) and accept a measured performance cost, rather than chase an unattainable absolute. Security, like reliability, is not a feature you bolt on — it is a budget you spend against performance, deliberately.

Long before Spectre, in 2005, cryptographer Daniel J. Bernstein demonstrated a full AES key recovery from cache timing on a real server, exploiting the fact that AES's software implementation used secret-key-dependent table lookups. Around the same time Osvik, Shamir and Tromer formalised Prime+Probe. The response reshaped cryptography: modern ciphers are designed to be implemented constant-time, AES got hardware instructions (\texttt{AES-NI}) that do the mixing in fixed time with no data-dependent table lookups, and "is it constant-time?" became a standard question in any crypto code review. A whole sub-field grew from noticing that a lookup table has a shadow.

Two misconceptions sink beginners. First, constant-time is not about speed: a constant-time routine may be slower than a branchy one — the point is that its timing (and memory-access pattern) does not vary with the secret. Making code faster can make it less secure if the speed-up adds a secret-dependent shortcut. Second, you cannot fix a cache side channel by "flushing the cache now and then": the leak is in the difference between a hit and a miss during the sensitive operation, and an attacker sets the timing of their own probes. The real remedies are architectural (partitioning) or algorithmic (data-oblivious code), not a periodic cache wipe.