<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Spectre | Lukas Gerlach</title><link>https://lukasgerlach.me/tag/spectre/</link><atom:link href="https://lukasgerlach.me/tag/spectre/index.xml" rel="self" type="application/rss+xml"/><description>Spectre</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0000</lastBuildDate><image><url>https://lukasgerlach.me/media/icon_hu_ff1d8021a950795d.png</url><title>Spectre</title><link>https://lukasgerlach.me/tag/spectre/</link></image><item><title>Spectre on RISC-V Silicon</title><link>https://lukasgerlach.me/blog/2026-spectre-on-riscv-silicon-usenix/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://lukasgerlach.me/blog/2026-spectre-on-riscv-silicon-usenix/</guid><description>&lt;p&gt;Our paper &lt;strong&gt;Spectre on RISC-V Silicon: Attacks and Defenses on Commercial Out-of-Order Processors&lt;/strong&gt; has been accepted at &lt;strong&gt;USENIX Security &amp;lsquo;26&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="spectre-paper-actions"&gt;
&lt;figure class="spectre-logo-mark"&gt;
&lt;img src="spectre-v.svg" alt="Spectre-V logo"&gt;
&lt;/figure&gt;
&lt;div class="spectre-action-stack" aria-label="Paper resources"&gt;
&lt;a class="spectre-action-button" href="https://lukasgerlach.me/publication/2026-speculative-execution-attacks-on-risc-v-silicon/riscv_spectre_sec26.pdf"&gt;Paper&lt;/a&gt;
&lt;span aria-hidden="true"&gt;/&lt;/span&gt;
&lt;a class="spectre-action-button" href="https://github.com/s8lvg/riscv-spectre-artifact" target="_blank" rel="noopener"&gt;Artifact&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This post is the short version of what we found.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/RISC-V" target="_blank" rel="noopener"&gt;RISC-V&lt;/a&gt; is an open, modular instruction set architecture that anyone can implement without a license fee.
Many independent vendors build their own cores from it, ranging from tiny in-order microcontrollers to superscalar out-of-order designs.&lt;/p&gt;
&lt;p&gt;For a long time, RISC-V had a reputation for being out of Spectre&amp;rsquo;s reach.
That reputation was reasonable in 2018.
Because the RISC-V silicon you could actually buy back then was in-order.
Both the &lt;a href="https://riscv.org/2018/01/more-secure-world-risc-v-isa/" target="_blank" rel="noopener"&gt;RISC-V Foundation&lt;/a&gt; and &lt;a href="https://web.archive.org/web/20251207154543/https://www.sifive.com/blog/sifive-statement-on-meltdown-and-spectre" target="_blank" rel="noopener"&gt;SiFive&lt;/a&gt; said so publicly at the time, and they were right about the hardware that existed.
The problem is that the myth that RISC-V is somehow not affected by Spectre is quite persistent.
Commercial out-of-order RISC-V cores now use the same kind of speculation that made Spectre relevant on x86 and ARM, while the software stack was never revisited.
We wanted to know what that means for the RISC-V ecosystem.&lt;/p&gt;
&lt;h2 id="1-spectre-works-on-risc-v-silicon"&gt;1. Spectre Works on RISC-V Silicon&lt;/h2&gt;
&lt;p&gt;We evaluated the T-Head Xuantie C910/C920 and the SiFive P550.
These were the only commercially available out-of-order RISC-V processors when we did the study.
But there are many different out-of-order RISC-V processors coming out, and our work should make it easy to test them all.
The takeaway from these experiments was that all Spectre variants that worked on x86 before also work on RISC-V, but sometimes with a few extra tricks.
A notable exception is Spectre-BTB (v2), which does not work out of the box everywhere because whether the indirect branch predictor is enabled depends on the firmware.
The C910 and P550 ship with it disabled, while our C920 had it enabled.
Enabling it made the attack work but sometimes also crashed the machine, which hints at a hardware bug.&lt;/p&gt;
&lt;h2 id="2-from-bpf-gadget-to-file-contents"&gt;2. From BPF Gadget to File Contents&lt;/h2&gt;
&lt;p&gt;We also built an exploit for the paper that uses a Spectre gadget to read arbitrary files.
We used BPF because it was the only feasible exploit path on the machines we had.
Typically, kernel Spectre exploits use Spectre-BTB because it gives a lot of flexibility in gadget selection.
The RISC-V cores we tested split the BTB between userspace and kernel, which makes these exploits hard.&lt;/p&gt;
&lt;p&gt;The basic gadget comes from a missing fence in the RISC-V BPF JIT.
Recent Linux kernels insert &lt;code&gt;BPF_NOSPEC&lt;/code&gt; barriers when verifier-approved programs need a speculation barrier.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;
The RISC-V JIT emits nothing for &lt;code&gt;BPF_NOSPEC&lt;/code&gt;, so the verifier considers the program protected while the generated code has no barrier at all.&lt;/p&gt;
&lt;p&gt;The gadget itself is a speculative type confusion.
We write BPF code where a branch decides what ends up in a register that gets dereferenced right after.
One path puts a valid kernel pointer there, the other puts a scalar we control.
Train the branch on the pointer path, then take the scalar path.
Architecturally nothing happens, the scalar is never dereferenced.
Transiently the processor mispredicts and loads from whatever address we picked.
The loaded value then indexes a BPF array map, which is where the covert channel comes in.
Array maps can be mapped into userspace with &lt;code&gt;BPF_F_MMAPABLE&lt;/code&gt;, so Flush+Reload recovers the index.&lt;/p&gt;
&lt;figure class="paper-figure"&gt;
&lt;img src="exploit-flow.svg" alt="BPF Spectre proof-of-concept flow"&gt;
&lt;figcaption&gt;BPF path used for the Spectre-PHT exploit.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This gives an attacker an arbitrary speculative read into the kernel.
To read something useful, previous exploits swept the direct physical map of the kernel until they arrived at the information they wanted to leak.
This is a bad choice for the CPUs we tested, because while the leakage is very precise it is also kind of slow.
Instead we use the fact that Linux keeps recently read files in the page cache.
We can get the inode number of the target file through the public &lt;code&gt;stat&lt;/code&gt; interface.
Then we leak a filesystem pointer, which we recover by walking pointers from &lt;code&gt;init_task&lt;/code&gt;.
After we have both the inode number and the filesystem pointer we can compute the page-cache hash, find the cached page, and read the target file contents directly.&lt;/p&gt;
&lt;figure class="paper-figure"&gt;
&lt;img src="kernel-traversal.svg" alt="Targeted page-cache lookup through kernel structure traversal"&gt;
&lt;figcaption&gt;Translating a file name to the location of its contents with our exploit.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="3-which-instruction-stops-speculation"&gt;3. Which Instruction Stops Speculation?&lt;/h2&gt;
&lt;p&gt;The missing fence raises an obvious question.
Which instruction should the JIT emit instead?&lt;/p&gt;
&lt;p&gt;RISC-V does not really have an answer.
x86 has &lt;code&gt;lfence&lt;/code&gt; and ARM has &lt;code&gt;CSDB&lt;/code&gt;, but the RISC-V &lt;code&gt;fence&lt;/code&gt; only orders memory operations.
It says nothing about speculation.
Linux assumes such an instruction exists anyway, so &lt;code&gt;barrier_nospec()&lt;/code&gt; just compiles to a no-op.&lt;/p&gt;
&lt;p&gt;So we tested 12 candidate instructions on both cores, and went back to the vendors with what we found.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fence.i&lt;/code&gt; stops speculation on both cores, in user code and in kernel code.
CSR reads are the cheaper option, and T-Head confirmed that they terminate speculation on the C910/C920.
On the P550 we measured the same for &lt;code&gt;rdtime&lt;/code&gt;, though that one is our own result rather than a vendor statement.
SiFive proposed a third option for kernel code on the P550, a CSR write to &lt;code&gt;stvec&lt;/code&gt; that leaves the register unchanged.
We verified it on the board, and it stops speculation both after faults and after branch mispredictions.&lt;/p&gt;
&lt;p&gt;Here are all three as inline assembly you can drop into your own code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Both cores, user and kernel. Safe default, flushes the icache.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;spec_barrier_fencei&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;asm&lt;/span&gt; &lt;span class="k"&gt;volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fence.i&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;:::&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Lower overhead. Unprivileged, so it also works in user code.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;spec_barrier_rdtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;asm&lt;/span&gt; &lt;span class="k"&gt;volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;rdtime t0&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;:::&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;t0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// SiFive P550, kernel only. stvec is a supervisor CSR.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;spec_barrier_p550_smode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;asm&lt;/span&gt; &lt;span class="k"&gt;volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;li t0, 0&lt;/span&gt;&lt;span class="se"&gt;\n\t&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;csrrs x0, stvec, t0&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;:::&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;t0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that &lt;code&gt;fence.i&lt;/code&gt; is the expensive one, at 46.6x geometric mean on SPEC CPU 2017 against 2.69x for &lt;code&gt;rdtime&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;None of these are architecturally guaranteed, so they may not hold on future cores.
The RISC-V Speculation Barrier Task Group is working on a real one, the draft &lt;a href="https://github.com/riscv/riscv-spec-barrier" target="_blank" rel="noopener"&gt;&lt;code&gt;Zisb&lt;/code&gt; extension&lt;/a&gt;.
The paper has the full table and the methodology behind it.&lt;/p&gt;
&lt;h2 id="4-finding-missing-mitigations-by-diffing-architectures"&gt;4. Finding Missing Mitigations by Diffing Architectures&lt;/h2&gt;
&lt;p&gt;The mitigation story was the part that surprised me most.
For x86 and ARM, many Spectre fixes have been added piecemeal since 2018.
For RISC-V, many equivalent paths simply had not received the same treatment.
The annoying part is that the matching code often does not have the same names or live in the same files.
So we used the existing fixes as search queries.
We first collected 44 mitigation sites from kernel hardening work on other architectures.
Then we extracted 2187 RISC-V kernel functions and embedded the source code with UniXcoder.
The embedding search ranked RISC-V functions by semantic similarity to the already-hardened x86 and ARM locations.
We then checked the high-ranking matches manually to see whether the RISC-V path had an equivalent barrier, mask, or dispatch hardening.
Among the 15 sites with clear RISC-V equivalents, none had the corresponding mitigation in the unmodified kernel.
We found missing hardening in the BPF JIT, syscall dispatch, user copy, futex, and KVM paths.&lt;/p&gt;
&lt;figure class="paper-figure"&gt;
&lt;img src="mitigation-diffing.svg" alt="Mitigation diffing embedding search workflow"&gt;
&lt;figcaption&gt;Mapping existing x86 mitigations to RISC-V code.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We contributed Linux kernel patches for the issues we found.
Some are already merged, while others are still part of ongoing hardening work.&lt;/p&gt;
&lt;h2 id="5-branchless-dispatch-instead-of-retpolines"&gt;5. Branchless Dispatch Instead of Retpolines&lt;/h2&gt;
&lt;p&gt;The compiler side has a similar porting problem.
Retpolines sound like the obvious answer for Spectre-BTB, but on RISC-V they are awkward.
They rely on the return stack buffer not falling back to BTB predictions when it underflows.
RISC-V also keeps the return address in &lt;code&gt;ra&lt;/code&gt;, so the only known design is skip-prologue, which needs a strict two-phase prologue.
The existing LLVM retpoline prototype fails on SPEC CPU 2017 because prologue generation does not consistently produce that layout.
So we also tried a different design: remove the indirect branch from dispatch entirely.&lt;/p&gt;
&lt;p&gt;The prototype is a branchless JIT dispatch mechanism.
When the interpreter resolves the next handler, it asks a tiny JIT stub to encode a direct &lt;code&gt;jal&lt;/code&gt; to that handler.
The stub computes the PC-relative offset, packs it into the immediate fields of the &lt;code&gt;jal&lt;/code&gt; instruction, writes the instruction into a small executable buffer, and executes &lt;code&gt;fence.i&lt;/code&gt;.
The buffer then contains a direct jump to the handler and a direct jump back to the caller.
The predictor now sees direct jumps instead of an attacker-controlled indirect target.
The JIT compiler writes the jump, and execution then transfers into the JIT buffer.&lt;/p&gt;
&lt;figure class="paper-figure"&gt;
&lt;img src="jit-dispatch.svg" alt="Branchless JIT dispatch"&gt;
&lt;figcaption&gt;Preventing indirect jumps by instead JIT compiling a direct jump.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We evaluated this idea in a uBPF interpreter prototype.
The overhead depends on how dispatch-heavy the program is, ranging from 1.2x to 22x in our experiments.&lt;/p&gt;
&lt;h2 id="6-artifact-and-reproducibility"&gt;6. Artifact and Reproducibility&lt;/h2&gt;
&lt;p&gt;The artifact is on GitHub.
We tried to make it useful both for reproducing the paper&amp;rsquo;s results and for building tests for new RISC-V CPUs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paper: &lt;a href="https://lukasgerlach.me/publication/2026-speculative-execution-attacks-on-risc-v-silicon/riscv_spectre_sec26.pdf"&gt;riscv_spectre_sec26.pdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Artifact: &lt;a href="https://github.com/s8lvg/riscv-spectre-artifact" target="_blank" rel="noopener"&gt;https://github.com/s8lvg/riscv-spectre-artifact&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;VeriFence changed BPF from rejecting these Spectre-PHT gadget patterns to accepting them with inserted &lt;code&gt;BPF_NOSPEC&lt;/code&gt; barriers.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item></channel></rss>