Nearly every Intel CPU carries a small "IP-stride prefetcher" that watches each load instruction, learns the regular gap between the addresses it fetches, and pre-loads the next one to hide memory latency — and it identifies instructions by only the lowest 8 bits of their address, with no check that the instruction is even the same program; AfterImage weaponizes this by having an attacker pre-train a prefetcher entry that a victim's secret-dependent load will collide with, so that whether the victim took the if-branch or the else-branch is imprinted in the prefetcher's state and read back afterward — leaking control flow across threads, processes, the user-kernel boundary, and even SGX enclaves, with no speculative execution and, in one variant, no cache tricks at all, recovering a full 1024-bit RSA key in 188 minutes.
The argument in one glance
-
Problem
Shared performance hardware leaks the secrets it was never meant to see
A CPU is shared among mutually distrusting programs, and many of the tricks that make it fast — caches, branch predictors, speculative execution — quietly remember what recently ran. A microarchitectural side-channel turns that shared memory-of-the-recent-past into a leak: an attacker who ran on the same core can observe traces the victim left behind and reconstruct secrets — encryption keys, control flow, user data. The most famous of these (Spectre and its kin) are now the subject of years of defensive work: they need SPECULATIVE execution to reach the secret and CACHE state to transmit it, so defenses target exactly those two ingredients.
-
What people did
Defend the known channels: block speculation, harden the cache
The security community has built a large arsenal against speculation-plus-cache attacks: control-flow-integrity checks, cache partitioning and randomization, monitors that watch for the flurry of cache misses these attacks produce, and secure timers that add noise to the timing measurements the attacker relies on. If an attack needs speculative execution and cache primitives, one of these defenses can usually catch it.
-
Why it fell short
One important piece of shared hardware was never examined: the prefetcher
The hardware prefetcher sits in nearly every Intel CPU, learning the address pattern of each load instruction and pre-fetching ahead to hide memory latency — and it had barely been studied as an attack surface. When the authors reverse-engineered Intel's IP-stride prefetcher on modern chips, they found it alarmingly leaky: it indexes each instruction by only the LOWEST 8 BITS of its address (no full check that it is even the same instruction, or the same program), and it PERSISTS its learned state across threads, processes, and even the user-kernel boundary. A victim's secret-dependent load can therefore land in — and modify — a prefetcher entry an attacker set up in advance. No speculation is involved, and no defense was watching this structure.
-
This paper's idea
Train a collision, let the victim imprint its secret, read it back
AfterImage (National University of Singapore) turns the prefetcher into a side channel in four steps. PREPARE: find the victim's secret-dependent load (from its binary, or by a search over the 256 possible 8-bit indices) and craft an attacker load whose low 8 bits collide with it. TRAIN: run the attacker load with a known stride until the shared prefetcher entry is confident. TRIGGER: hand control to the victim; if its secret sends it down the branch containing the colliding load, that load fires the trained entry, prefetching a predictable address. OBSERVE: read back the imprint — either via a cache primitive (was the predicted line prefetched?) or, cache-free, by checking whether the entry is now "spent" and can no longer trigger. This leaks control flow across threads, processes, kernel, and SGX with 91–99% success, defeats timing-constant RSA (full key in 188 minutes), and serves as a precise timing marker to sharpen power attacks — all patched by a proposed prefetcher-flush instruction costing 0.7%.
Why this problem is worth solving
The modern CPU is a machine that never fully forgets. Every
structure built to make it fast — the cache that remembers
recent data, the branch predictor that remembers recent
decisions, the
a performance structure that
watches memory accesses and pre-loads data it predicts will
be needed, hiding the hundreds-of-cycles latency of a DRAM
fetch. Intel’s IP-stride variant, present in nearly every one
of its chips, tracks each load INSTRUCTION (identified by its
address, the instruction pointer or IP) and learns the
regular STRIDE between the addresses that instruction
requests — a table look-up walking an array at 64-byte steps,
say. Once it has seen the same stride enough times to gain
CONFIDENCE, it prefetches the next predicted address
automatically. It is small, cheap, and a real performance
win, which is why it is nearly universal
The paper’s unsettling contribution is to show that a
structure everyone overlooked is a first-class leak on its
own. The prefetcher was assumed to be, at worst, a covert
channel between two cooperating attacker programs, or an
algorithm-specific curiosity. AfterImage’s reverse
engineering shows it is far more dangerous: because the
IP-stride prefetcher indexes on only 8 bits of the
instruction address, an attacker’s load can be deliberately
made to COLLIDE with a victim’s load — two different
instructions, in different programs or even different
privilege levels, sharing one prefetcher entry — and because
the learned state survives every isolation boundary the OS
erects, whatever the victim does to that shared entry is
visible afterward. Crucially, this works with
the attack reads the
victim’s ACTUAL, committed control flow, not a mispredicted
speculative path. This matters two ways. It sidesteps the
entire class of Spectre defenses, which target speculation.
And it defeats “timing-constant” code — the defensive style
that makes both branches of a secret-dependent if/else take
equal time so an attacker cannot tell them apart by a
stopwatch — because AfterImage does not measure time; it
observes WHICH load instruction ran, and the two branches
still contain different instructions at different
addresses
What people did before, and where it broke
- Spectre-family attacks (speculation + cache). The dominant paradigm and the most defended: mistrain a predictor, speculatively touch secret memory, exfiltrate via cache timing. Powerful, but every ingredient — the speculation, the cache transmission — now has countermeasures, and none of them watch the prefetcher.
- Prior prefetcher covert channels. Earlier work used the IP-stride prefetcher as a COVERT channel: two attacker- controlled programs flush and observe prefetcher history to communicate. Useful for exfiltration, but not a SIDE channel — it cannot leak an unwitting victim’s secret.
- Algorithm-specific prefetcher footprints (Shin et al.). Captured a cache footprint left by a victim with very regular access patterns (table look-ups) to recover a key — but tied to that specific algorithm and access shape, and slow (19 CPU-hours for one ECDH key).
- The gap. No prior work turned the prefetcher into a general, algorithm-agnostic SIDE channel that leaks an arbitrary victim’s control flow across arbitrary isolation boundaries, without speculation, and (in one variant) without cache primitives at all. That required first reverse-engineering the prefetcher’s index, confidence, and replacement behavior on modern chips — which this paper did.
How AfterImage works
Reverse-engineer the prefetcher, then train a collision and read it back
-
Reverse-engineer the IP-stride prefetcher new
Custom microbenchmarks on Haswell and Coffee Lake reveal, for the first time in detail: the entry is indexed by only the low 8 bits of the load's address (an instruction whose low 8 bits match another shares its entry — there is no full-IP tag); the confidence counter rises when the stride repeats and falls otherwise, updating the stride when it drops too far; the table holds 24 entries with a known replacement policy; and state persists across threads, processes, and privilege switches. These facts are the attack's foundation.
-
Prepare — find and collide with the victim load new
The attacker locates the victim's secret-dependent load. If the victim binary is available (a shared crypto library), objdump gives the address directly. If not (a kernel routine), an IP-SEARCH tries the 256 possible low-8-bit values in groups until one collides — cheap because that is the entire index space, and unaffected by address randomization (ASLR only shifts higher bits). The attacker then crafts a local load whose low 8 bits equal the victim's.
-
Train — saturate the shared entry new
The attacker runs its colliding load repeatedly with a chosen, distinctive stride (a prime like 7, 11, or 13, spaced past four cache lines to avoid confusion with the other three simpler prefetchers) until the confidence counter saturates. The entry now firmly predicts "next address = current + stride." Only two to three iterations are needed — fast enough that performance-counter monitors sampling too slowly miss it.
-
Trigger — the victim runs and imprints its secret new
The attacker yields the core to the victim. The victim executes its secret-dependent branch; if the secret sends it down the path containing the colliding load, that load hits the attacker-trained entry and the prefetcher automatically fetches current+stride. Whether or not this happened IS the secret bit. The victim did nothing wrong — it made an ordinary, committed memory access.
-
Observe (A) — via cache: AfterImage-Cache new
The classic read-out: before yielding, flush the shared page; afterward, reload and time it (Flush+Reload), or prime-and-probe eviction sets without shared memory. If the predicted current+stride line is now cached, the victim took the load-bearing branch. High accuracy, but leaves cache-attack signatures defenses can catch.
-
Observe (B) — cache-free: AfterImage-PSC new
The standalone read-out, from a reverse-engineered detail: once a trained entry FIRES, its confidence updates and it can no longer immediately re-trigger — it is "spent." So the attacker just re-runs its own load and checks whether the entry still triggers a prefetch (one timing test of a single address). Spent means the victim ran the load; still-live means it did not. No cache primitives, no shared memory — invisible to every cache-focused defense.
The idea
Collision is the whole trick — the prefetcher cannot tell whose instruction it is watching. The IP-stride prefetcher identifies a load by only the low 8 bits of its address and keeps no full-IP tag, so two entirely different instructions whose addresses happen to share those 8 bits map to one shared entry. AfterImage manufactures that coincidence deliberately: it places an attacker load at an address that collides with the victim’s secret-dependent load, so the two programs unknowingly share a single piece of learned state. Everything else follows. The attacker writes the entry (trains a stride), the victim modifies it (fires or does not fire it based on the secret), and the attacker reads the result. It is the oldest confused-deputy shape in security — make the shared resource conflate your identity with the victim’s — realized in a structure nobody thought to isolate, and cheap to arrange because the entire index space is 256 values, small enough to brute-force and immune to address randomization.
Persistence across boundaries is what upgrades a covert channel into a side channel. Prior prefetcher attacks were covert channels because they needed both endpoints to be attacker-controlled. What makes AfterImage a true side channel — leaking an UNWITTING victim — is the reverse- engineered fact that the prefetcher’s learned state survives thread switches, process switches, and even the transition into and out of kernel mode. The OS painstakingly isolates address spaces and privilege levels, but the prefetcher’s little confidence counters carry information straight through those walls. The victim’s secret leaks not because it did anything unusual but because the hardware it shares with the attacker refuses to forget across the very boundaries the OS relies on to contain secrets.
It reads instructions, not timing — so timing-constant defenses are irrelevant. The subtlest consequence is what AfterImage defeats. Security libraries adopt “timing-constant” coding precisely to beat stopwatch-style side channels: make the if-path and else-path of a secret-dependent branch take equal time, execute the same number of loads, call the same functions, so an attacker timing the whole operation learns nothing. AfterImage sidesteps this entirely because it never times the operation — it observes WHICH load instruction executed. The two balanced branches are timing-identical but still made of DIFFERENT instructions at different addresses, and only one of them collides with the trained entry. The paper demonstrates this against a real timing-constant RSA engine, extracting the full key: a defense built for one threat model is simply outside the model of the new one.
Leaking one RSA key bit
AfterImage-PSC against timing-constant Montgomery-Ladder RSA
The victim's RSA loop branches on each private-key bit; both branches are balanced to take equal time, but the if-path contains a load at a distinct instruction address. Cache-free PSC read-out.
Step 1 of 5
The victim looks safe — and is not
The RSA engine processes the key bit by bit. For each bit it takes an if or else branch; the developers made both paths execute the same number of loads and call the same function, so total time is identical regardless of the bit. Against a stopwatch this is airtight. But the load in the if-path lives at its own instruction address, and objdump hands the attacker its low 8 bits.
timing-constant style beats time-measurement attacks — the operation takes the same duration either way but "same duration" is not "same instructions" — the branches contain different loads at different IPs
Why it works
- The prefetcher indexes on only 8 bits and keeps no full-IP tag → an attacker load can be made to share one entry with a victim load in another program → the shared resource conflates the two identities, the essence of the leak.
- Learned prefetcher state survives thread, process, and privilege boundaries → the victim’s effect on the shared entry is observable from the attacker’s context → what the OS isolates, the prefetcher carries through, turning a covert channel into a side channel.
- The attack reads which committed instruction ran, not how long anything took → speculation defenses (which target mispredicted paths) and timing-constant code (which equalizes duration) both miss it entirely → an orthogonal threat model the existing stack does not cover.
- A fired entry becomes temporarily unable to re-trigger → the attacker can read the secret by testing a single address for “spent” vs. “live” → cache-free extraction (PSC) invisible to every cache-primitive defense.
- The full index space is 256 values, and randomization only moves higher address bits → the victim load can be found by brute-force IP search even in the kernel → the attack reaches targets whose addresses the attacker never learns.
What it costs
- Same-core co-residence required. The attacker and victim must share the physical core (and thus the prefetcher), typically via time-slicing or SMT — a real but achievable requirement, and the standard assumption for this attack class.
- Synchronization and disassembly assumptions. The attacker must interleave with the victim around the target branch (via sched_yield, SMT, interrupts, or a scheduler attack) and, for the binary-known case, be able to disassemble the victim; the target page is assumed already in the TLB (common for streaming code).
- Imperfect, so iterated. Per-bit success is ~82% for the cache-free variant, so bits are read several times and voted — which is why a 1024-bit key takes 188 minutes rather than seconds; cross-process Prime+Probe is noisy enough that context-switch memory traffic touches half the eviction sets.
- Bounded channel capacity. Hardware limits cap the covert stride (and thus bits per transfer), and only three training strides are practical against the other prefetchers’ noise.
- Cheaply patchable. The proposed clear-ip-prefetcher instruction on context switch closes the cross-domain leak at ~0.7% average slowdown (0.2% across all SPEC apps) — meaning the exposure is a design oversight, not a fundamental tax; the danger is unpatched deployed silicon.
The evaluation
Reverse-engineering plus end-to-end attacks on real Intel Haswell (i7-4770) and Coffee Lake (i7-9700) machines with ASLR/KASLR enabled, against proof-of-concept gadgets, a real timing-constant RSA library, OpenSSL, and SGX, with a mitigation modeled analytically and in the ChampSim simulator.
AfterImage versus the side-channel status quo
| Property | Spectre-family (speculation + cache) | Prior prefetcher attacks (covert / algorithm-specific) | AfterImage |
|---|---|---|---|
| Requires speculative execution | No | ||
| Requires cache primitives to transmit | Often yes (cache footprint) | ||
| Leaks an unwitting victim (true side channel) | Yes | ||
| Defeats timing-constant code | Not the target | No | |
| Mitigation | Extensive, costly, ongoing (microcode, ISA, recompilation) | Disable prefetcher (large slowdown) or algorithm-specific fixes |
Check yourself
Check your understanding
1. Timing-constant coding — making both branches of a secret-dependent if/else take equal time — is a standard defense, and a real timing-constant RSA library uses it. AfterImage extracts the full key anyway. Predict why the defense fails against this attack.
2. The IP-stride prefetcher indexes each load instruction by only the low 8 bits of its address, with no full-instruction tag. Predict how AfterImage weaponizes this, and why address-space randomization (ASLR) does not help.
3. AfterImage offers two read-out methods: AfterImage-Cache (via Flush+Reload / Prime+Probe) and AfterImage-PSC (Prefetcher Status Checking). Predict what PSC does differently and why it is the more dangerous variant.
4. The proposed defense is a clear-ip-prefetcher instruction run on every context switch, costing ~0.7%. Predict why flushing on context switch specifically is the right place, and what this says about the vulnerability's nature.
Sources
- ACM DL: AfterImage: Leaking Control Flow Data and Tracking Load Operations via the Hardware Prefetcher — ASPLOS 2023 (open access)