Intel SGX is meant to keep a program's secrets safe even from a malicious operating system, and when a secret decides which way a branch goes, defenders make the two branch paths look identical so no side channel can tell them apart — but this work from Tsinghua University and the National University of Singapore reverse-engineers an obscure, never-flushed CPU unit called the Memory Disambiguation Unit and shows it silently records which balanced branch ran, letting an attacker steal RSA keys and decode images from inside SGX in a single run; they then give a fix, store-to-load coupling, that closes the leak for about 20 percent overhead instead of the 140 percent a naive fence costs.
The argument in one glance
-
Problem
SGX guards against a malicious OS, but not against side channels
Intel SGX runs a program in a protected enclave whose code and data are hidden even from a compromised operating system, which is why it is trusted for security-critical work. But SGX was never built to stop side channels — the faint timing and state changes that any program leaves in the shared CPU hardware. A frequent target is a secret-dependent branch: a branch whose direction is chosen by a secret, common in cryptography, where taking the branch one way or the other reveals a bit of a private key. If an attacker can tell which way the branch went, the secret leaks.
-
What people did
Flush the known leaky units and balance the two branch paths
Defenders responded with software techniques that need no new hardware. For microarchitectural units with known leaks — the branch predictor, the caches, the prefetcher — the enclave flushes their state on every context switch so an attacker learns nothing from them. For everything else, they use branch balancing: make both directions of the branch execute the same instruction types, the same instruction counts between pages, and align their store addresses identically, so the two paths become indistinguishable however the attacker probes.
-
Why it fell short
These defenses only cover the leakage sources already discovered
Flushing works only for units known to leak, and balancing works only against differences an attacker is known to observe. Both are lists of patches against disclosed attacks. If some other part of the processor quietly records which way a branch went, and nobody thought to flush it or to balance against it, then the branch is still exposed. The whole edifice rests on the assumption that every leaky component has been found.
-
This paper's idea
Read the branch from an unflushed, undocumented memory predictor
MDPeek finds one such component: the Memory Disambiguation Unit (MDU), a predictor that lets a load run ahead of an earlier store whose address is not yet computed. It is never flushed, and it keys its state on the load's program-counter address — which branch balancing never aligns. By reverse-engineering exactly when the MDU updates, the attackers make a load along one branch path leave a mark in it, then read that mark back after the branch to learn which way it went, bypassing both flushing and balancing.
Why this problem is worth solving
The setting is Intel SGX under its own worst-case threat model, so it is worth being exact about what the attacker is allowed to do. SGX assumes the operating system itself is hostile: the attacker has root, controls the page tables, handles interrupts, and schedules processes, sharing the same physical CPU as the victim enclave. What SGX still promises is that the attacker cannot read the enclave’s memory directly. So attacks instead infer secrets indirectly, from control flow. A secret-dependent branch is one whose taken-or-not decision is governed by a secret bit — the workhorse of cryptographic code, appearing in the binary extended Euclidean algorithm used for RSA and RSA-CRT key generation, in Montgomery multiplication, and even in non-cryptographic code like the inverse discrete cosine transform that decodes JPEG images. If the attacker can determine, for each such branch, which way it went, they reconstruct the secret one bit at a time. A privileged attacker can single-step the enclave by rigging page faults, so they get to observe the hardware immediately before and after each branch. The defense, then, must make the two branch directions genuinely indistinguishable in every observable piece of state — a very demanding requirement when the observer controls the whole machine around the enclave.
The reason the standard defenses fall short is that they are enumerations of known dangers, not guarantees. The two practical, software-only defenses each cover a specific class of leak. Flushing targets units already known to leak: on each context switch the enclave clears the branch predictor, the caches, and the prefetcher, so their state cannot carry information about the victim’s execution to the attacker. Branch balancing targets known observable differences: it makes both branch directions execute the same instruction types (defeating attacks that count instruction types), keep the same instruction count between pages (defeating attacks that count instructions), and align their store program-counters to sixteen bytes (defeating attacks that watch store addresses). Together these block every attack in the published catalog. But notice the shape of the argument — it is a defense per disclosed leak. It says nothing about a microarchitectural unit that has not been studied, is not on the flush list, and produces a difference nobody is balancing against. Any such unit reopens the branch. The gap is not a bug in the defenses; it is the inherent limit of defending by enumerating known attacks, which is exactly why finding a new leakage source matters — it shows the enumeration was incomplete.
What people did before, and where it broke
- Eliminate secret-dependent branches entirely (data-oblivious programming). Rewrite the algorithm so control flow never depends on a secret, defeating all control-flow attacks at once. Where it broke. It is algorithm-specific and imposes crushing overhead — reported as high as twenty-four times slower — so it is impractical for most real code, especially non-cryptographic libraries.
- Flush the known leaky units on context switch. Clear the branch predictor, caches, and prefetcher when the enclave is interrupted, so the attacker cannot read their state. Where it broke. Flushing only helps for units known to leak and placed on the flush list; a unit nobody flushes keeps its state across the switch and still carries the secret.
- Balance the two branch directions. Make both paths execute identical instruction types and counts, and align their stores, so the attacker cannot distinguish them. Where it broke. Balancing only neutralizes the specific differences an attacker is known to observe; it aligns store program-counters, for instance, but does nothing about a unit keyed on load program-counters.
- Monitor interrupts to stop repeated single-stepping (AEX-Notify). Intel’s newer mechanism notices when an enclave is being interrupted repeatedly and blocks the attacker from stepping through it. Where it broke. It needs microcode updates, so it does not apply to the many older CPUs still in service, and Intel then shifts the burden back onto software developers to write side-channel-resistant code.
- The gap. Every defense either rewrote the algorithm at ruinous cost or patched a specific, already-discovered leak. None could rule out an unexamined hardware unit that survives flushing and slips past balancing — which is precisely the hole a new leakage source like the Memory Disambiguation Unit walks through.
How MDPeek works
Turning an unflushed memory predictor into a branch oracle
-
-
new
-
new
-
new
-
new
-
new
The idea
The leak is a performance feature nobody was guarding. The MDU exists only to make loads faster by letting them skip past a store whose address is still being computed; it is invisible to the programmer and, crucially, never appeared on anyone’s flush list or balancing checklist. MDPeek’s insight is that a defense built as a catalog of known-leaky units is only as strong as the catalog is complete, and here it was not. Closing an entire category of exposure by construction, rather than swatting each disclosed instance, is exactly the ambition of the hardware-enforced memory-safety work covered elsewhere on this site, which gives C pointers capabilities the hardware checks so a whole class of memory bugs simply cannot happen — the opposite pole from the patch-the-known-leak posture that MDPeek exploits.
Understand the hidden hardware precisely, then weaponize the exact conditions. The MDU had been only partially characterized before, so the real work was determining, by careful experiment, precisely when it updates. The authors induced delays at each pipeline stage with port contention, TLB misses, and cache flushes, watched for pipeline rollbacks through a performance counter, and thereby isolated the four conditions that must all hold for an update. Only with that map could they engineer real code that reliably marks a counter. Reconstructing a hardware component’s true behavior by studying just the paths that actually matter, rather than trusting the documentation, is the same empirical discipline the GPU-kernel study work covered elsewhere on this site follows, learning what the machine really does before building on top of it.
Defend by making the dependence explicit, cheaply. Once you know the MDU only fires when the store-load dependence is unresolved, the fix writes itself: insert one arithmetic instruction that ties the load’s address to the store’s, so the CPU sees the dependence immediately and never predicts. This store-to-load coupling delays the store’s address generation slightly but leaves out-of-order execution and caching intact, costing about twenty percent where a blunt fence costs well over a hundred. Buying security at a small fraction of the naive overhead through a targeted, mechanism-aware trick is the same spirit as the low-overhead memory-sanitization work covered elsewhere on this site, which detects the dangerous cases with a clever encoding instead of paying full price on every access.
Following an RSA-CRT key attack through MDPeek
Leaking a private key from MbedTLS inside SGX
Tracing one balanced branch shows how the MDU betrays the secret.
Step 1 of 5
The enclave generates an RSA-CRT key
MbedTLS computes part of the private key with the binary extended Euclidean algorithm, whose inner sub-step contains a secret-dependent branch: which direction it takes depends on the large secret primes being generated. The code is fully defended — balanced branches, flushed units, no hyperthreading.
Because the key is fresh each run, the attacker gets only one trace and cannot average away noise.
Why it works
- The MDU is never flushed, so it carries the secret across the context switch. Defenses clear the branch predictor, caches, and prefetcher when the enclave is interrupted → but nobody added the MDU to that flush list, so its counters keep the state a victim load left in them → so the attacker who regains control reads a mark made by the enclave’s own execution.
- The MDU is keyed on the load’s program-counter, which balancing never aligned. Branch balancing aligns store program-counters to sixteen bytes to defeat store-address attacks → but the MDU selects its counter from the lowest 8 bits of the load’s program-counter, an address the defense leaves untouched → so a load on one branch path selects a counter the attacker can single out and read.
- The update rule makes the two branch directions leave different marks. A counter is set high when a store-load pair truly aliases and ticked toward zero when it does not → but only one branch direction contains the delayed store-load pattern that updates the chosen counter → so the counter’s value after the branch encodes which direction the secret sent it.
- The counter is readable by timing, and stable enough for one trace. Reading zero lets the probe load run out of order and finish fast, while a nonzero count blocks it for about 150 more cycles → but the MDU state barely changes under interrupts and context switches, with most counters stable at 95 percent → so a single execution recovers the bit with high accuracy, even for a freshly generated key.
- Coupling the store and load resolves the dependence, so the MDU stops predicting. The MDU only updates when the store-load dependence is genuinely unresolved → but inserting one arithmetic instruction that derives the load address from the store address makes the dependence explicit at once → so the predictor never fires and the leak closes for about a fifth of the cost of a fence.
What it costs (trade-offs and scope)
- The attack needs a specific code pattern, not just any branch. MDPeek requires a store followed by a dependent load with delayed, unresolved addressing along the branch path. Branch-predictor attacks need only a branch, and cache attacks need only a load, so MDPeek has a narrower attack surface and finding exploitable loads in real code is harder.
- It assumes the full SGX privileged-attacker model. The attacker must control a root operating system, drive single-stepping with SGX-Step through page faults, and share the physical core with the victim, which also disables hyperthreading. This is the standard SGX threat model, but it is not a remote or unprivileged attack.
- The automated hunt for vulnerable code is incomplete. The authors build a tool to find exploitable store-load patterns, but acknowledge it is limited and leave more accurate software analysis for identifying MDPeek-usable loads to future work.
- Single-load accuracy is high but not perfect, so probing is repeated. A single load recovers a branch at roughly 95 to 98 percent, and reaching near-100 percent requires probing several loads and voting — cheap here because multiple exploitable loads exist per branch, but a real dependence.
- The mitigation is per-load and software-only, and still costs about 20 percent. Store-to-load coupling must be applied by the compiler to each exploitable load, and while far cheaper than serialization (140 to 170 percent) or load-address balancing (about 160 percent), it is not free; a proper hardware fix is left to future CPUs.
- The demonstration is Intel-specific. MDPeek is shown on SGX across eight Intel microarchitectures, focused on an i7-6700K; the authors note the MDU is unchanged through recent Intel generations and may threaten Intel TDX, but leave TDX, AMD, and Arm — whose disambiguation units differ — to future work.
The evaluation
Four ways to stop the MDU leak, and what each costs
| Property | Serialization (fence) | Load-PC balancing | Data-oblivious rewrite | Store-to-load coupling |
|---|---|---|---|---|
| How it blocks MDPeek | Fence stops the load running past the store | Align loads to 256 bytes so counters collide | Remove the secret-dependent branch entirely | |
| Performance overhead | ||||
| What it disrupts | All out-of-order execution after the store | Cache and prefetcher locality | The whole algorithm, rewritten by hand | |
| Applicability | General, but very slow | General, but very slow | Cryptographic code, case by case |
Check yourself
Check your understanding
1. Why do flushing and branch balancing fail to stop MDPeek, even though they block the previously published SGX attacks?
2. What is the Memory Disambiguation Unit, and how does MDPeek read a secret from it?
3. How did the authors determine exactly when the MDU updates its state?
4. Why is store-to-load coupling a better defense than serialization or load-address balancing?
Sources
The paper is “MDPeek: Breaking Balanced Branches in SGX with Memory Disambiguation Unit Side Channels” by Chang Liu, Shuaihu Feng, Yuan Li, Dongsheng Wang, Wenjian He, Yongqiang Lyu, and Trevor E. Carlson, published at ASPLOS 2025, DOI 10.1145/3676641.3716004. The authors are at Tsinghua University, Zhongguancun Laboratory, Huawei Technologies, and the National University of Singapore.
This explainer is written from the full 17-page text. Everything above comes from the paper’s own sections: the introduction and background framing SGX control-flow attacks on secret-dependent branches, the three software defenses (data-oblivious programming, flushing known leakage sources, and branch balancing), and Table 1’s catalog of prior branch attacks and their suggested defenses; the overview of the Memory Disambiguation Unit (256 four-bit counters selected by the load program-counter’s lowest 8 bits, set high on aliasing and decremented otherwise); the threat model of a privileged untrusted OS single-stepping the enclave via SGX-Step; the reverse-engineering of the MDU’s enable and update logic through delay-type, data-dependence, and reorder-buffer experiments using the machine-clears performance counter; the attack primitive built from a timing probe with an approximately 150-cycle gap and the automated search for exploitable loads; the end-to-end attacks on Libjpeg (98.76 percent pixel accuracy, about 7.8 minutes for a 640-by-480 image), MbedTLS RSA-CRT (about 830 milliseconds, over 97 percent per load), and WolfSSL RSA (about 880 milliseconds, over 95 percent per load), confirmed across eight Intel microarchitectures; the impact and limitations discussion (including that constant-time alignment should rise to 256 bytes); and the defenses section evaluating serialization, load-address balancing, and the proposed store-to-load coupling on SPEC 2017 intrate, where coupling costs about 20 percent versus 140 to 170 percent and about 160 percent, roughly a 7-times improvement.