
Reproduction of the Retbleed (CVE-2022-29900/29901) micro-architectural attack in gem5. RSB underflow, Flush+Reload side-channel leak, and a verified lfence mitigation.
A hands-on reproduction of Retbleed (CVE-2022-29900 / CVE-2022-29901) — the 2022 micro-architectural attack that broke the "Retpoline" defense by proving that ret instructions, long assumed safe, can be hijacked when the CPU's Return Stack Buffer (RSB) underflows.
This project simulates the full attack lifecycle on an x86 Out-of-Order CPU model in gem5: triggering an RSB underflow, leaking secret data one byte at a time through a Flush+Reload cache side-channel, and then verifying a software mitigation (lfence) that closes the leak.
Course project — Advanced Computer Architecture, Fall 2025, CUNY City College Authors: Abdul Kalam Mansoor & Rebiha Selmani
Spectre-style attacks showed that speculative execution leaves cache-level side effects even when the CPU "undoes" a wrong guess. The industry's fix — Retpoline — replaced dangerous indirect jumps with ret instructions, on the assumption that returns are predicted safely from a small hardware stack (the RSB). Retbleed showed that assumption is false: exhaust the RSB with deep recursion, and the CPU silently falls back to the same unsafe predictor Retpoline was designed to avoid.
| Stage | What happens |
|---|---|
| 1. Trigger | rsb_deep_call() recurses 32 levels deep, overflowing the 16-entry RSB. When the recursion unwinds, the RSB is empty. |
| 2. Fallback | With the RSB empty, the CPU falls back to the Branch Target Buffer (BTB) to predict the ret target — which an attacker can poison. |
| 3. Gadget | The hijacked speculative path executes gadget(), which reads a byte of the secret password and uses it to index into probe_array, pulling one page of that array into cache. |
| 4. Flush+Reload spy | Before the attack, every page of probe_array is flushed from cache (_mm_clflush). After the speculative window closes, the program times access to every possible byte value (__rdtscp) — the one that comes back fast (cache hit) reveals the secret byte. |
Repeating this for every byte of ROOT_PASSWORD reconstructs the whole secret without ever architecturally calling gadget().
| Mode | Access latency | Outcome |
|---|---|---|
Vulnerable (SECURE_MODE undefined) | ~49 cycles (cache hit) | Secret leaked byte-by-byte, confirmed by red HIT! indicators |
Patched (SECURE_MODE defined, _mm_lfence() injected) | >150 cycles (cache miss / noise) | Attack fails — output shows SAFE / Found: ??? |
The lfence forces the CPU to resolve the return address before any subsequent instruction can execute, collapsing the speculative window before the gadget ever touches secret-dependent memory.
src/
retbleed.c # Full PoC: trigger, gadget, Flush+Reload spy, and lfence mitigation (toggle via SECURE_MODE)
docs/
Retbleed_Report.pdf # Full written report: methodology, related work, gem5 setup, results
Retbleed_Attack_Demonstration.pptx # Slide deck used to present the project
The PoC was built and measured under gem5 (DerivO3CPU, an out-of-order model — required because in-order models don't implement speculative execution):
gcc -O0 -static -o retbleed src/retbleed.c
./build/X86/gem5.opt configs/deprecated/example/se.py \
--cpu-type=DerivO3CPU --caches --l2cache \
--l1d_size=64kB --l1i_size=64kB --cmd=retbleed
To flip between vulnerable and patched behavior, comment/uncomment this line at the top of retbleed.c:
#define SECURE_MODE
The code uses real x86 intrinsics (
_mm_clflush,__rdtscp,_mm_lfence) and can also be compiled and run natively on x86 hardware (gcc -O0 -o retbleed src/retbleed.c) for a quicker demo — results will vary with the host CPU's own mitigations (eIBRS, microcode patches, etc.), which is itself a useful illustration of how thoroughly this class of bug has been patched since 2022.
lfences) measured 14–39% performance overhead on affected hardware.