Home · ASPLOS 2025 · Memory Systems & Caches

ShadowLoad: Injecting State into Hardware Prefetchers

Lorenz Hetterich, Fabian Thomas, Lukas Gerlach, Ruiyi Zhang, Nils Bernsdorf, Eduard Ebert and 1 other

Conference
ASPLOS 2025
Published
2025
Pages
1060-1075
Citations
9

Every modern processor guesses which memory a program will read next and fetches it early, and it remembers those guesses in a small table indexed by a partial, undocumented mix of address bits that is never tagged with who is running; this paper (CISPA) shows that an ordinary program can therefore write entries into that table which the kernel or a hypervisor will later act on — pulling data the attacker cannot read into the cache where other attacks can reach it — and can run the same trick backwards to watch which byte within a cache line a victim touched, which defeats code written to be safe at cache-line granularity.

The argument in one glance

  1. Problem

    Processors keep hidden state that outlives a context switch

    Every prediction structure inside a processor is a small memory recording what happened recently so it can guess better next time. That memory is a performance feature, invisible to programs, and historically it has not been part of what an operating system saves, restores or clears when it switches between programs.

  2. What people did

    Attack the branch predictor, and then defend it

    This is what Spectre did — poison the record of which way branches went, so the victim speculates down a path the attacker chose. Years of mitigations followed: microcode controls to restrict cross-domain prediction, flushing on context switch, and hardened code.

  3. Why it fell short

    The prefetcher is the same kind of structure and got far less attention

    Recent work showed the prefetcher can leak, treating it as something to observe. What had not been asked is the Spectre question: what happens if you write into it rather than read out of it, and what does its lookup actually use as a key?

  4. This paper's idea

    The key is partial and untagged, so entries can be planted for someone else to use

    The history table is indexed by a fraction of the load instruction address combined with a fraction of the address it read, and on most processors tested it carries no marker for which security domain wrote it. So an ordinary program can create an entry that the kernel will later match against, and a single kernel load will then fetch whatever the attacker aimed it at.

Why this problem is worth solving

The structure at the centre of this is more stateful than most people picture.

The unit that notices a program reading memory at regular intervals and starts fetching ahead of it. To do that it must remember things: for each load instruction it has seen, what address that instruction read and what the gap was between successive reads. That record lives in a small table, and the table is looked up using an undocumented combination of some bits of the load instruction’s own address and some bits of the address it read. Three or four accesses in a pattern are enough to establish an entry. Everything about which bits, how many, and how they are combined is a vendor implementation detail with no architectural specification, which is why this paper has to build a tool to work it out.
is a small cache of behavioural history, and history is exactly the kind of thing that leaks.

The technique being transplanted has a name and a precondition.

Training a prediction structure using addresses that are not the victim’s, but that land in the same table entry because the index throws away most of the address. It matters because it removes the requirement that attacker and victim share anything. No shared library, no shared page, no common code. The attacker simply arranges their own load instruction and their own buffer at addresses whose surviving index bits match the victim’s, trains a pattern there, and the entry is waiting when the victim’s load arrives. On some processors examined here as few as the lowest eight bits of the instruction address decide the match.
is what turns an implementation shortcut into a cross-domain channel.

What people did before, and where it broke

  • Poison the branch predictor. The Spectre family, and the reason microarchitectural injection is a familiar idea at all. Where it broke. Nothing broke, but it needs a gadget — a specific sequence in the victim that loads something and then does something observable with it. Finding those gadgets is the hard part of a real Spectre attack.
  • Inject a value rather than a prediction. Load Value Injection passes attacker-controlled data to a faulting victim load. Where it broke. It is a different mechanism entirely, mistraining nothing, and it depends on faulting loads.
  • Read the prefetcher instead of writing it. Recent work uses the prefetcher’s state as a side channel — inferring which branch a victim took, or building a covert channel in a trusted execution environment. Where it broke. These observe. Nobody had asked what happens when you plant state for a different privilege level to consume, which is a strictly stronger capability.
  • Exploit the data-dependent prefetcher on one vendor’s cores. A striking result, since a prefetcher that dereferences values it sees is a much sharper weapon. Where it broke. It is specific to particular cores, and it is again about what the prefetcher does with the victim’s own data rather than about planting state.
  • Reverse-engineer prefetchers to catalogue them. Existing tooling classifies which prefetchers a processor has and recovers parameters like stride limits. Where it broke. It recovers what the prefetcher does, not the conditions under which one context’s training applies to another’s loads. Those conditions — which address bits form the index, and whether any domain marker exists — are precisely what an injection attack needs.
  • Write constant-time code. The standard defence: make the addresses a program touches independent of secrets, or at least independent at the granularity anyone can observe. Where it broke. The granularity assumption. A well-known hardened routine here touches both cache lines of a lookup table and masks the result, which is airtight if a cache line is the smallest observable unit, and not airtight if it is not.
  • The gap. Two questions had gone unasked. What is the actual index function of the stride prefetcher’s history table, and is anything in it tagged by security domain? The answers turn out to be “a small number of low address bits” and, on most of the sixteen processors tested, “no”.

How planted history becomes a fetch

From an untagged history table to data at rest in the cache

  1. new

  2. new

  3. new

  4. new

  5. new

Sixteen processors examined with the reverse-engineering tool, thirteen found exploitable, five case studies against a hypervisor, a kernel, a hardened cryptographic library and a speculative gadget.

The idea

Injection is a general technique, not a branch-prediction technique. Spectre established the shape: find a structure that records history, write into it from a context you control, and let a more privileged context act on what you wrote. That shape was then treated as being about branch predictors, and a decade of mitigations followed the same assumption. The observation here is that a prefetcher is the same kind of structure — a small memory of recent behaviour consulted automatically — and it is missing the isolation that branch predictors eventually acquired. The consequence is a capability that is in one respect weaker than Spectre and in another respect stronger: it cannot redirect control flow, but it needs no gadget beyond a single ordinary load anywhere near the data of interest, whereas Spectre needs a specific sequence that reads a secret and then does something detectable with it. Treating the stride prefetcher as an attack surface rather than a performance feature is the direction the control-flow leakage work covered elsewhere on this site opened, and this paper turns it from a way to watch into a way to act.

The vulnerability is that the index is partial, which is a design choice made purely for area. Storing a full address per entry would make the table enormous, so implementations keep a hash of a few bits, and unrelated loads that agree on those bits share an entry. The measurements are the striking part: on several widely deployed processor generations only the lowest eight bits of the load instruction address participate, giving 256 equivalence classes for every load instruction in the system. One vendor’s earlier cores use twelve input bits but compress them through an exclusive-or function down to about a thousand classes, which is fewer distinctions than the input bits would suggest. Collisions are therefore not a rare accident but a routine and easily arranged occurrence. There is a certain irony in reading this alongside the datacentre work covered elsewhere on this site that switches these same prefetchers off when memory bandwidth gets tight, since disabling them is also the only complete mitigation offered here — the same lever, pulled for opposite reasons.

The observation side breaks constant-time code by being finer than the model it was written against. Side-channel-hardened implementations are written to a threat model, and the usual model says an attacker can learn which 64-byte cache line was touched and no more. A hardened routine in a widely used cryptographic library follows that model correctly: its lookup table spans two cache lines, so it reads both and selects the right value with arithmetic masking, leaving no cache-line-level trace. But the prefetcher learns gaps at byte resolution, and it only fetches ahead when a pattern is followed exactly — so an attacker who can supply the rest of the pattern learns the victim’s offset within the line. The hardening is not defeated by being wrong; it is defeated by an assumption about the hardware that the hardware never promised. That mismatch between what software assumes about leakage and what a processor actually does is exactly what the speculation-contract testing work covered elsewhere on this site exists to find.

Turning a planted entry into a cached secret

The five moves, at the level of what each accomplishes

Both proof-of-concept victims are purpose-built kernel modules where the attacker is assumed to know the victim's load instruction address and buffer address.

Step 1 of 5

Establish what the processor uses as a key

Run the reverse-engineering tool to determine which address bits index the history table, whether entries persist across the user-kernel boundary, how many accesses are needed to establish one, the largest gap that can be learned, and whether a fetch may cross a page.

The answers vary widely. Some processor families show no usable collisions for load targets at all, and one vendor generation shows none whatsoever — those are not exploitable by this technique.

Why it works

  • A partial index is a collision generator. Storing full addresses per entry would cost too much area, so implementations keep only a few bits → those bits define equivalence classes, and on several processor generations there are only 256 of them for every load instruction on the machine → so arranging a collision with a chosen victim load is a matter of placing your own code at a convenient address rather than finding a rare coincidence.
  • Nothing in the table says who wrote it. Address translation structures have long carried an identifier for which address space an entry belongs to → prefetcher history tables, on most processors tested, carry no equivalent → so an entry created by an unprivileged program is matched and acted upon when the kernel executes an aliasing load, with the fetch performed at the kernel’s privilege.
  • A single load is a far weaker requirement than a gadget. Spectre needs a sequence that reads a secret and then leaves an observable trace → here the victim only needs to load something, anywhere near the data of interest, because the attacker chooses the offset by choosing the stride → which is why this composes with attacks whose usual obstacle is finding a suitable gadget in the victim.
  • Mitigations that flush caches can be undone by what runs after the flush. The defence against one fault-based attack is to empty the first-level cache before returning to a virtual machine → but flushing is not the last thing that happens, since guest register state must still be restored → and those restore loads are ordinary loads that a planted entry can hijack, refilling the cache the mitigation just emptied.
  • Byte-level gap learning breaks a cache-line-level defence. Hardened code hides which of two cache lines it needs by touching both → but the prefetcher records gaps at single-byte resolution on one vendor and four-byte on the other, and only fetches ahead when a pattern is matched exactly → so completing the pattern at a guessed offset and checking whether a fetch happened reveals the offset within the line that the masking was designed to hide.

What it costs (trade-offs and scope)

  • It is not universal, and the paper maps the boundaries carefully. Of sixteen processors examined, thirteen are found exploitable. One vendor’s third-generation cores show no usable collisions at all. The efficiency cores of two recent generations show none for load targets. The processors from a third vendor show no load-target collisions either, and their prefetchers ignore the load instruction address entirely.
  • The newest tested core is a poor target. On one recent flagship, injection succeeds in only 11 percent of unaligned attempts and 23 percent of aligned ones, against 67.5 to 99.9 percent on older parts. The trend across generations runs against the attack.
  • The attacker must know where the victim’s load and buffer live. Both proof-of-concept victims are purpose-built kernel modules where these addresses are assumed known. There is one demonstration against unmodified kernel code, targeting a load inside a common system call.
  • No leakage across hyperthreads or cores. Training on a sibling thread produced nothing, consistent with earlier work. The attacker needs to be in a position to interleave with the victim on the same core.
  • The power-analysis composition is markedly weaker than a direct read. The exploitable component of the leakage is measured at 11.85 times smaller than simply accessing the data directly, and 1.20 times smaller than a speculative gadget — the honest conclusion is that it is comparable to that gadget rather than better. Its advantage is applicability, not strength. The correlation is 1.33 times stronger, suggesting less noise.
  • That experiment is also idealised. It uses a power interface that is no longer available to unprivileged code, chosen for reproducibility, and places victim data in the attacker’s own address space to remove context-switch noise.
  • The hypervisor case study depends on an older vulnerability. Recent processors are unaffected by the fault-based attacks it composes with, and on the affected ones the prefetch cannot cross a page, so it reaches a 4-kilobyte region — enough to recover a pointer and defeat address randomisation, not enough for arbitrary memory. The authors argue relevance from cloud fleet composition rather than from novelty of the underlying flaw.
  • Observation requires repeating the victim access. Scanning a cache line byte by byte means invoking the victim once per candidate offset — 64 times for full resolution, and about 32 invocations per recovered character in the cryptographic case study.
  • The one complete mitigation is a performance loss. Turning the prefetchers off works and is available through processor registers. The more targeted proposal is to mark secret-holding memory as uncachable, which the authors confirm prevents prefetching, and which also carries a performance cost. Everything else — domain tags in the table, microcode controls, state-clearing sequences — requires new hardware or firmware.
  • Constant-time code still helps, but only against half of it. Data-oblivious code does not produce secret-dependent prefetching and so blocks the observation side. It does not prevent an attacker from planting an entry that a victim’s ordinary load will act on.

The evaluation

What each primitive adds to an attack that already exists

PropertyThe attack on its ownWith planted prefetcher stateWhat limits it
Reading data the victim never touchedA fault-based attack reaches only data already in the first-level cache, so uncached secrets recover nothing
Defeating a cache-flush mitigation
Power-analysis leakage
Observing hardened code
Speculative gadgets previously unusable
Sixteen processors reverse-engineered across four vendor generations plus two from a third vendor, with disclosure to both major vendors before publication.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. What property of the prefetcher history table makes cross-domain influence possible?

  2. 2. How does this differ from Spectre in what it demands of the victim?

  3. 3. Why does a cache-flush mitigation fail against this?

  4. 4. Why does hardened code that touches both cache lines and masks the result still leak?

Sources

The paper is “ShadowLoad: Injecting State into Hardware Prefetchers” by Lorenz Hetterich, Fabian Thomas, Lukas Gerlach, Ruiyi Zhang, Nils Bernsdorf, Eduard Ebert and Michael Schwarz, published at ASPLOS 2025, pages 1060-1075, DOI 10.1145/3676641.3716020. The authors disclosed to both major processor vendors before publication; one is publishing a security bulletin and the other upstreamed kernel patches. The code is public and an artifact appendix is included.

This explainer is written from the full 16-page text. Everything above comes from the paper’s own sections: the background on caches, cache attacks, power-analysis attacks, virtualisation and transient-execution attacks; the description of the two primitives, including how mistraining is arranged, how the victim load is triggered from a system call or a trapped instruction, and how the inverse construction turns the same mechanism into an oracle for whether a specific address was accessed; the reverse-engineering framework covering what parameters it recovers, the single-bit-flip and random-address tests for detecting which bits index the table, the tests across same-process, hyperthread and kernel targets, and its use of a reload-timing measurement; the evaluation across sixteen processors with the table of index bit masks, access counts, maximum strides, page-crossing behaviour and stride granularity per microarchitecture, the explicit index function for one vendor’s earlier cores, the manual analysis of a third vendor’s cores including their sixteen-entry fully-associative structure with least-recently-used replacement, the injection success rates and timing overheads, and the accuracy and rate figures for both observation variants; the five case studies covering the hypervisor pointer recovery with its trapped instruction and negative stride, the fault-based leakage of data at rest with its cached and uncached reference conditions and its demonstration against unmodified kernel code, the power-analysis composition with its regression coefficients and correlation figures, the attack on the hardened library routine with its success rate and residual brute-force cost, and the speculative-gadget encoding with its bit recovery and gadget-scanner results; and the countermeasure discussion spanning domain tagging, microcode controls, disabling prefetchers, marking memory uncachable, state-clearing sequences and data-oblivious code, together with the discussion of other prefetcher types, virtualisation and trusted execution boundaries, and the absence of cross-thread leakage.

Related papers on Memory Systems & Caches

See all 118 →