Every time your CPU reaches an if-statement it guesses which way the branch will go, and to guess well it keeps a hidden internal register — the Path History Register — that fingerprints the exact ordered sequence and addresses of the last ~194 branches a program took; this paper (UC San Diego and collaborators) shows an unprivileged attacker sharing the CPU can READ and WRITE that register and the predictor's tables as if they were scratch memory, reconstruct a victim's entire control-flow path branch by branch (even further back than 194), and use it to recover a secret image from a JPEG decoder and to steal an AES key from a constant-time crypto library by poisoning one branch so the victim speculatively returns a half-finished ciphertext — a far higher-resolution leak than earlier branch-predictor attacks, working across process, kernel, and SGX boundaries and through Intel's existing branch defenses, and reminding defenders that constant-time code and today's mitigations do not cover this.
The argument in one glance
-
Problem
A shared CPU predictor could leak one program's control flow to another
To run fast, a CPU predicts which way every conditional branch (every if and loop) will go before it knows the answer, so the pipeline never stalls. The unit that does this, the conditional branch predictor, keeps history about past branches — and that history state is SHARED by every program running on the core and survives context switches. In principle that makes it a side channel: if one program's branching leaves a trace another program can observe, the second program learns something about the first's secret-dependent control flow, with no shared memory and no permission. The question is how MUCH it can learn.
-
What people did
Prior attacks read only the simplest, coarsest part of the predictor
Earlier branch-predictor side channels — BranchScope, BranchSpec, Bluethunder, and Spectre-style mistraining — targeted only the simplest structure in the predictor, the base (per-address) table. They could leak or inject COARSE information: the direction of a single branch, or the bias of the last few instances of one branch. Reconstructing a victim's actual ordered control-flow path was out of reach this way. The one prior technique that could track precise control flow — the controlled-channel and single-stepping attacks against Intel SGX — required OS-level privilege (root), so it did not apply to an ordinary unprivileged attacker sharing a machine.
-
Why it fell short
The rich signal — the Path History Register — had never been used, because it was hidden and undocumented
The truly valuable state is the Path History Register (PHR): a register that fingerprints the last ~194 TAKEN branches, capturing not just their directions but their ADDRESSES and their exact ORDER — essentially the program's recent path. That is far richer than the "last branch's direction" prior work extracted. But nobody had used it, for two reasons: it is a large internal register that software cannot read directly, and its structure (how it is updated, how many branches it holds, how it indexes the tables) was undocumented. So the high-resolution control-flow trace an attacker would most want stayed locked inside the chip. The opening: what if that structure were reverse-engineered, and the register could be read and written indirectly?
-
This paper's idea
Turn the predictor into a read/write scratchpad, recover the full path, and compose attacks
This paper (UC San Diego, Purdue, Georgia Tech, UNC Chapel Hill, and Google — Yavarzadeh et al.) builds on recent reverse engineering that exposed the exact PHR update rule (each taken branch shifts the register by a 2-bit "doublet" and XORs in a footprint made of branch- and target-address bits — a known, deterministic, invertible function). On top of it they build clean PRIMITIVES that treat the predictor as scratch memory: READ and WRITE the PHR, and READ and WRITE the prediction tables. Reading the PHR recovers the victim's exact ordered branch history; because the update is invertible and aged-out branches leave residue in the tables, they even recover branches from further back than 194. A tool, Pathfinder, then reconstructs the victim's control-flow graph and the precise path that produced the observed PHR. They compose these into two very different attacks: recover a secret image from a JPEG decoder by capturing the full control flow of its transform, and steal an AES key from a CONSTANT-TIME crypto library by poisoning one loop-exit branch so the victim speculatively returns a reduced-round ciphertext — showing constant-time code and Intel's existing branch defenses do not cover this.
Why this problem is worth solving
Microarchitectural side channels matter to defenders because they leak
secrets across boundaries the software stack believes are airtight —
between processes, between user and kernel, into and out of secure
enclaves — using only shared hardware, no shared memory, and no
permission. The branch predictor is one of the richest shared units,
and the heart of the danger here is one specific piece of it. The
a hidden internal CPU register
that summarizes the recent control-flow PATH of whatever is running on
the core. On the Intel chips studied (Alder Lake and Raptor Lake) it
folds together the last 194 TAKEN branches (93 on the older Skylake),
recording for each not just whether it was taken but a fingerprint of
its ADDRESS and its position in the sequence. Each taken branch updates
it deterministically — the register shifts left by one 2-bit unit (a
“doublet”) and then XORs in a 16-bit “footprint” built from bits of the
branch’s own address and its target address — so the PHR is essentially
a heavily compressed, ordered trace of where the program has recently
been. The predictor uses it to index its prediction tables, which is
why it exists; but that same richness is exactly what makes it a
devastating thing to leak, because unlike a cache line or a page fault
it reveals the exact sequence and identity of branches, not just that
some memory was touched
The reason this stayed theoretical for so long is that the PHR cannot
be read with a normal instruction; you can only observe its EFFECT on
predictions. The paper’s core measurement trick is to turn a
the way the attack
reads state it cannot see directly. The branch predictor gives the same
prediction for two situations only when their history state matches. So
the attacker sets up a “test branch” whose behavior is random, arranges
two paths to it — one that runs the victim (imprinting the victim’s PHR)
and one that sets a KNOWN candidate value — and then measures how often
the test branch is mispredicted. If the two states match, the predictor
carries the correlation across and the misprediction rate collapses
toward 0%; if they do not match, the prediction is uncorrelated and the
rate sits at 50%. So “50% versus near-0% misprediction” becomes a
yes/no answer to “does the victim’s hidden state equal my guess?” — and
by guessing the four possible values of one 2-bit doublet at a time, the
attacker reads the whole register out
What people did before, and where it broke
- BranchScope, BranchSpec, Bluethunder (base-predictor attacks). Force the predictor to use its simplest per-address table (by firing hundreds of thousands of random branches), then alias branch addresses to leak or poison a single entry. Works without privilege, but only reveals the direction of one branch — coarse, and blind to the rich global path history.
- Spectre-style mistraining. Poison a branch to cause a misspeculation and leak data through a covert channel. Powerful, but classic versions influence only the FIRST (or first few) instances of a branch, so they cannot target, say, a specific late iteration of a loop.
- Controlled-channel / single-stepping against SGX. Track a victim’s control flow precisely by watching page faults or single-stepping the enclave. This gives fine control flow, but requires OS-level privilege (a malicious operating system), so it does not apply to an unprivileged attacker sharing a machine.
- Cache and instruction-cache control-flow channels. Infer branches from which cache lines or pages were touched. Limited to cache-line (64-byte) or page (4KB) granularity, often need single-stepping or an SMT sibling, and miss branches whose code sits within one line.
- The gap. The Path History Register — the exact, ordered, address-aware trace of the last ~194 taken branches — was never exploited, because it could not be read directly and its structure was unknown. No unprivileged, high-resolution reconstruction of a victim’s full control-flow path existed. That is the gap this work closes.
How the branch predictor becomes a control-flow spy
Reverse the update rule, read and write the hidden state, rebuild the path, and compose attacks
-
The shared predictor and its hidden path register
The conditional branch predictor keeps a Path History Register that fingerprints the last ~194 taken branches (their directions, addresses, and order) and uses it to index prediction tables. This state is shared across all programs on the core and persists across context switches — the raw material for a leak, but locked inside the chip.
-
Reverse-engineer the deterministic update rule
Building on recent reverse engineering (and confirming it on the latest Raptor Lake), each taken branch updates the PHR by shifting it left one 2-bit doublet and XORing in a 16-bit footprint derived from the branch and target address bits. Because the rule is known and deterministic, it is also INVERTIBLE — you can run it backwards.
-
Read the PHR one doublet at a time new
Using a misprediction rate as a one-bit oracle, the attacker recovers each 2-bit doublet of the victim's PHR by shifting the target doublet to the top and testing which of its four possible values yields a 50% (matching) rather than near-0% misprediction. Repeated 194 times, this reads out the entire register — the exact ordered branch history.
-
Write the PHR and the prediction tables to any chosen state new
Because the footprint is a known function of address bits, the attacker can execute a crafted sequence of branches that sets the PHR to ANY value, and can then poison any prediction-table entry (accessed by a chosen PHR and address) to a taken or not-taken verdict of their choice — turning the predictor into a writable scratchpad.
-
Recover the full path, even past 194, and rebuild the CFG new
An extended read exploits that branches which have aged out of the PHR still left residue in the prediction tables, plus the invertible update rule, to recover branches further back than 194. The Pathfinder tool then takes the recovered PHR and the victim binary and reconstructs the exact control-flow path — which branches were taken, and how many loop iterations ran — almost always a single unambiguous path.
-
Compose the primitives into concrete attacks new
Reading the full control flow of a JPEG decoder's transform recovers a secret image; poisoning a single loop-exit branch (a write) makes a constant-time AES implementation speculatively return a reduced-round ciphertext, from which the key is recovered. The same small primitive set drives two unrelated, high-resolution attacks.
The idea
Turn a private internal register into a read/write scratchpad. The conceptual move is to stop treating the branch predictor as an opaque performance box and start treating it as MEMORY the attacker can read and write. The PHR and the prediction tables persist across protection boundaries and are updated by ordinary branch execution, so the paper builds four clean primitives — read the PHR, write the PHR, read a prediction-table entry, write a prediction-table entry — and everything else is composition. This reify-the-hidden-state discipline is one the site keeps rewarding: the value is in exposing implicit machine state as an explicit, addressable interface, after which powerful behavior falls out of combining simple operations. Here the twist is adversarial — the “scratchpad” belongs to the victim — but the shape is the same build-clean-primitives-then-compose structure that good abstractions have, and it is what lets one toolkit drive attacks as different as image theft and key recovery.
The signal is far richer than anyone had exploited — read the whole ordered path, not one branch. Prior branch-predictor attacks extracted a bit or two: the direction of a single branch. The insight here is that the PHR already contains the exact ordered sequence and addresses of the last ~194 taken branches — the program’s recent path — and that this fingerprint is legible if you can read the register out. Reading it is a matter of using the predictor’s own matching behavior as an oracle: the predictor treats two histories the same only when they are equal, so a 50%-versus-0% misprediction gap answers “is the victim’s hidden doublet equal to my guess?” Doing that across all doublets recovers the whole register, and inverting the known update rule (plus reading the residue that aged-out branches left in the tables) pushes the recovery even further back. Extracting a much larger, structured signal from state others treated as coarse is the same extract-more-from-the-signal discipline the last-level-cache side-channel page uses when it pulls a victim’s access out of cloud noise with frequency analysis.
Constant-time code is not enough, because the attack recruits speculation and data-dependent control flow. The AES result is the sharpest defensive lesson. The target is a CONSTANT-TIME AES-NI implementation — no secret-dependent branches in the arithmetic — yet it still falls, because the attacker poisons the loop-exit branch (a prediction-table write) so the CPU SPECULATIVELY exits the round loop early and computes a reduced-round ciphertext, which leaks through a cache channel; a two-round ciphertext has so little diffusion that it maps back to the round key and then the full key. The image attack, likewise, reads control flow that constant-time ARITHMETIC never removes: the JPEG transform genuinely branches on whether a row or column is all zero. So “we made the crypto constant-time” and “we enabled Intel’s branch defenses” both fail to cover this — the same the-defense-you-trust-does-not-cover-this warning the last-level-cache side-channel page delivers when it shows that multi-tenant noise is not isolation. Defenders have to reason about the actual state that leaks, not the mitigation’s name.
From a hidden register to a stolen key
Composing the primitives into a full attack
Reading the Path History Register and poisoning the prediction tables, on Intel Raptor Lake / Alder Lake. Numbers from the paper.
Step 1 of 5
The raw material
The Path History Register fingerprints the last ~194 taken branches — directions, addresses, and order — and is shared across programs on the core. Prior attacks could read only one branch's direction; the whole ordered path was never recovered because the register is internal and its structure was undocumented.
each taken branch shifts the PHR by one 2-bit doublet and XORs in a footprint of branch/target address bits 194 taken branches on Alder/Raptor Lake, 93 on Skylake — confirmed identical across Alder and Raptor Lake
Why it works
- The Path History Register already encodes the exact ordered sequence and addresses of the last ~194 taken branches → reading it recovers a victim’s recent control-flow PATH, not just one branch’s direction → a qualitatively higher-resolution leak than prior branch-predictor attacks.
- The predictor carries a correlation between two branches only when their history state matches → a 50%-versus-near-0% misprediction rate is a reliable one-bit equality oracle → the attacker reads out a register it cannot access with any instruction.
- The PHR update (shift by a doublet, XOR an address-derived footprint) is a known deterministic function → it is invertible, and aged-out branches still imprint the prediction tables → the recovered history extends further back than the register’s own 194-branch window.
- The attacker can set the PHR to any value and thus poison any specific prediction-table entry → they can mistrain an ARBITRARY instance of a branch, such as a chosen late loop iteration → far more precise than classic Spectre, which only mistrains the first few instances.
- A constant-time AES round loop can be made to speculatively exit early by poisoning its exit branch → a reduced-round ciphertext leaks through a cache channel → because a two-round ciphertext barely diffuses, it maps back to the key even though the code had no secret-dependent branch.
What it costs (scope, and the defensive reading)
- It is Intel-specific and rests on reverse engineering. The exact PHR structure and update rule were reverse-engineered for Intel CPUs (validated Ivy Bridge through Raptor Lake); each new microarchitecture or vendor needs the effort repeated. AMD was notified and plans to address it (AMD-SB-7016); the paper’s measurements are on Intel.
- The attacker needs the victim binary and deterministic branching. Pathfinder reconstructs the path using the victim’s executable, and assumes the victim’s branches behave deterministically for the same inputs. Reconstruction can be ambiguous for loops running more than 194 iterations with invariant control flow (it reports “more than 194” without the exact count).
- Co-location on the same core is assumed. The attacker and victim must share a physical core (as co-resident SMT threads or time-sliced execution); the paper focuses on the leakage mechanism, not on achieving co-location.
- The defenses have real costs. Fully flushing the PHR on every context switch takes 194 unconditional branches; fully flushing the prediction tables takes on the order of 100000 branch instructions — prohibitive without hardware support. Cheaper randomization only raises the attacker’s effort rather than closing the channel.
- This is a capability demonstration, framed for defense. It shows what is leakable — a full control-flow trace, a recovered image, a recovered AES key at 98.43% — so that defenders understand that constant-time code, Intel’s IBPB/IBRS, and predictor designs that isolate only the tables are all insufficient against reading the PHR.
The evaluation
Reverse-engineering validation across three Intel generations, a correctness evaluation of the read/write primitives, an accuracy evaluation of the Pathfinder control-flow reconstruction, and two end-to-end attacks (image recovery from libjpeg, key recovery from Intel-IPP AES), plus an attack-surface analysis across user/kernel, SGX, SMT, and Intel’s branch defenses.
What a branch-predictor side channel can leak
| Property | Prior CBP channels | Prior precise CFG (privileged) | Pathfinder (this work) |
|---|---|---|---|
| Control flow recovered | |||
| History depth | |||
| Privilege required | |||
| Predictor structure used | |||
| Breaks constant-time AES? |
Check yourself
Check your understanding
1. Prior branch-predictor side channels could read only one branch's direction, yet Pathfinder recovers a victim's entire ordered control-flow path. Predict what made the richer leak possible.
2. The Path History Register cannot be read by any instruction. Predict how Pathfinder reads its hidden value anyway.
3. The AES target is a CONSTANT-TIME implementation with no secret-dependent branches, yet Pathfinder still recovers the key. Predict how, and what it says about "constant-time" as a defense.
4. The PHR holds only the last ~194 taken branches, yet Pathfinder recovers control flow from further back. Predict how it reaches past the register's window.
Sources
- Pathfinder: High-Resolution Control-Flow Attacks Exploiting the Conditional Branch Predictor (author PDF) — ASPLOS 2024 (full text read).
- ACM DL: Pathfinder (DOI 10.1145/3620666.3651382) — published version.
- Pathfinder_Attack (artifact / source) — authors’ released code.