Home · ASPLOS 2024 · Security, Privacy & Trusted Execution

Pathfinder: High-Resolution Control-Flow Attacks Exploiting the Conditional Branch Predictor

Hosein Yavarzadeh, Archit Agarwal, Max Christman, Christina Garman, Daniel Genkin, Andrew Kwong and 4 others

Conference
ASPLOS 2024
Published
2024
Pages
770-784
Citations
20

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

  1. 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.

  2. 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.

  3. 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?

  4. 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
is far more informative than anything earlier attacks could read. The problem is worth solving — from a defender’s standpoint, worth understanding — because it turns a performance feature into a high-resolution control-flow spy.

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
. This indirect readout is the engine underneath everything, and it is why a register never meant to be observed becomes fully legible — a mechanism defenders need to understand to reason about which mitigations actually close it.

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

Demonstrated on Intel Core i9-13900KS (Raptor Lake), i9-12900 (Alder Lake), and i7-6770HQ (Skylake); the reverse-engineered structure generalizes to Intel flagship CPUs back to Ivy Bridge (2012). Disclosed to Intel and AMD in November 2023 (AMD advisory AMD-SB-7016). Presented as a threat assessment for defenders.

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

PropertyPrior CBP channelsPrior precise CFG (privileged)Pathfinder (this work)
Control flow recovered
History depth
Privilege required
Predictor structure used
Breaks constant-time AES?
Pathfinder reads the rich, ordered Path History Register that prior branch-predictor channels could not touch, without the privilege that prior precise control-flow attacks required — and composes read and write primitives to recover a full control-flow trace, a secret image, and a constant-time AES key. The lesson for defenders is that the leak lives in shared predictor state that constant-time code and Intel's current branch defenses do not clear.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 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. 2. The Path History Register cannot be read by any instruction. Predict how Pathfinder reads its hidden value anyway.

  3. 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. 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

Related papers on Security, Privacy & Trusted Execution

See all 153 →