21

Reading the whitepaper, it sounds like doom and gloom. The main webpage states “Spectre is harder to exploit than Meltdown, but it is also harder to mitigate. However, it is possible to prevent specific known exploits based on Spectre through software patches.”

This would seem to imply that it is not possible to prevent unknown exploits based on Spectre through software patches, is that true?

Shelvacu
  • 2,333
  • 4
  • 16
  • 29
  • 2
    FYI, exploits (or at least POCs) are public. https://raw.githubusercontent.com/HarsaroopDhillon/SpectreExploit/master/Test1.c – n00b Jan 04 '18 at 15:46

2 Answers2

19

The core of the Spectre attack is to use mis-training of the CPU's branch predictor to cause the CPU to speculatively branch to an attacker-selected fragment of code while executing the target program, then observe indirect effects of running that code. This is only possible because current CPUs share branch-predictor state across all threads running on the computer.

It is possible to write x86/amd64 code that is immune to Spectre by inserting instructions that prevent speculative execution after each branch (eg. the cpuid or mfence instructions), but this comes at the cost of a fairly severe loss of performance, and can only be applied to new software.

A CPU that permits flushing the branch-predictor state could be made immune to Spectre by resetting the state on each context switch (at the cost of some performance). Neither Intel's nor AMD's implementation of the x86/amd64 architecture appears to have such an instruction (yet), but I expect it to show up in a few years. This would have far less of a performance impact than preventing speculative execution, because even an uninitialized branch predictor is about 70% accurate.

Mark
  • 34,390
  • 9
  • 85
  • 134
  • What instructions are possible to use that already exist on an Intel 80486? (For when you can’t break backwards compatibility.) – mirabilos Jan 05 '18 at 17:48
  • 4
    By my understanding of Spectre, there's no need for a context switch. Running `if (*p1 && p2[256 * *p3) foo=1;` a few times with `*p1` equal to a non-zero value, `p3` pointing to some valid object, and `p2` pointing to an arbitrary array, and then running it with `*p1` holding a non-cached zero, p3 pointing to illegitimate storage, and p2 pointing to a non-cached array, will cause one item of p2 to get cached based upon the value in `*p3`. – supercat Jan 06 '18 at 00:03
  • 1
    @mirabilos, if you need compatibility clear back to the original 80486? Indirect branches can be replaced with the [retpoline construct](https://stackoverflow.com/q/48089426/3063736) -- the `lfence` instruction would trigger an "illegal instruction" trap on the 80486, except that it's never actually executed; it's there to improve efficiency on later CPUs. For direct branches, there's nothing you can do: the `cpuid` instruction wasn't introduced until the second-generation 80486, while the `fence` instructions were added with the Pentium 4. – Mark Jan 16 '18 at 22:29
-1

There are two different situations here. One is running untrusted machine code on a CPU. The other is running untrusted byte code or scripts in a JIT compiler, such as Javascript.

edit: I changed some things due to my misunderstanding of the code that Spectre uses to work properly. It uses existing code with higher privileges with conditional branches where the CPU is deliberately made to execute the wrong path in the branch with malicious data given to it, which results in memory reads which are cached and then thrown out. Except for proof of concept code, the exploit uses code already on the system, not its own code like I said previously.

Meltdown is related to running untrusted machine code on an Intel CPU in a restricted environment, such as a limited user account. The code can use Meltdown to access read protected memory such as kernel memory that is supposed to be off limits. It is not related to JIT compilers.

Spectre is related to: 1) Like Meltdown, running untrusted machine code in a restricted environment and accessing restricted memory such as kernel memory. What can be accessed depends on what is mapped in to the virtual address space when the Spectre vulnerable code is run. 2) JIT compiers such as Javascript. Javascript and other JIT compiled restricted environments that are running untrusted code can be exploited by Spectre to gain access the address space of the Javascript JIT compiler, the browser, and likely more than that.

Code that is running in a JIT compiler or interpreter has the advantage of just needing patches to the JIT compiler or interpreter to fix the problem. Making it so that the attacking script can't probe the cache can defeat Spectre. I believe patching against Spectre for native code is where it gets harder.

The page table isolation patch that is used to fix Meltdown does not fix Spectre*. Spectre vulnerable code is conditional code that is already on the system. To make use of Spectre, this code is runs with different privileges than the attacking program so that it has access to data that the attacking program does not have access to. After the attacking program prepares the caches and mistrains the branch predictor, the Spectre vulnerable code is called by the attacking program with arguments that cause its speculatively executed instructions to read from memory that it normally protects against. These read instructions are never fully executed by the CPU because it dumps them once it finds out that the branch prediction was wrong. But the read operations affected the caches, and now the attacking program analyzes the caches to find the data.

So yes it is possible to make software patches. Once found to be vulnerable, individual conditionals can be modified such that they are useless to Spectre. A more generic approach of adding machine code after all branch instructions can be done to cause the CPU to not speculatively execute anything. That will cause a noticeable performance impact, since a modern CPU can speculatively execute over 100 instructions while waiting to verify a branch prediction! Maybe some sort of compiler modification can be done to have it disable speculative execution after conditionals which access data based on the value of the variable used for the conditional, or memory accesses based on outside data, or it could require that variables used in conditionals be stored in a register to have speculative execution guaranteed to be enabled.

*What I have said here involves memory accesses to areas that are readable by the Spectre vulnerable code. What I don't know yet is if speculatively executed instructions can access read-protected data like Meltdown does. Maybe this is possible on Intel CPUs and not AMD? I suspect that there is not much discussion about this because most CPUs are Intel and anything Intel that's vulnerable to Spectre is also vulnerable to Meltdown. But could matter for untrusted code that's running in a JIT compiler, since such code would then have access to the read-protected part of the JIT compiler's address space (the kernel and physical memory) while using Spectre vulnerable code that's running with the privileges of the JIT compiler. The page table isolation patches would fix this, but users may think that since they aren't running native code that they don't need it.

Alex Cannon
  • 402
  • 2
  • 7
  • 1
    You're missing one of the key points of Spectre: the attacker's program isn't the one that's doing the incorrect memory accesses. The attacker's program does a whole bunch of *valid* memory accesses in a way that tricks the CPU into making a *different* program perform invalid speculative memory accesses. When seen in isolation, nothing the attacker's program does is invalid, so there's nothing for a JIT compiler to protect against. It's also why KPTI doesn't protect against Spectre: it's the *kernel* that's doing the invalid access, not the attacker's program. – Mark Feb 12 '18 at 21:30
  • Everything that I have read about Spectre talks about speculatively executed instructions reading from protected memory that is supposed to be read-protected, but the security check is not done soon enough due to CPU design flaws and the data gets loaded in to the cache. It can absolutely be exploited by a single program. You mentioned two programs where one program makes the other do an invalid memory access. Are you getting this idea from a JIT interpreter and its script being two programs? – Alex Cannon Feb 14 '18 at 00:00
  • 1
    "speculatively executed instructions reading from protected memory that is supposed to be read-protected, but the security check is not done soon enough due to CPU design flaws" sounds an awful lot like Meltdown, not Spectre. – Mark Feb 14 '18 at 00:33
  • It looks like my understanding of how Spectre works, especially through a JIT compiler, is or was flawed. It's not the JIT compiled or native code (except PoC programs) that does the memory access like I thought, it's existing shared code (even privileged code) that is called by it that does. Thanks for pointing it out. Now a new thing to I'm wondering is if there are differences on AMD and Intel CPUs about whether the memory being accessed by a Spectre exploit can be read-protected. The Spectre paper isn't clear to me. – Alex Cannon Feb 14 '18 at 02:33
  • Your update still has problems. The big one is that Spectre *isn't* restricted by the attack program's virtual address space mapping. A Spectre attack can read anything in the *target program's* address space. For example, an attacker can use it to pull SSL keys out of a webserver running in someone else's paravirtualized shared host. You're also falling into the cache-probe trap that most people do: the cache isn't the only side channel that Meltdown/Spectre can use, it's just the easiest. – Mark Feb 14 '18 at 05:12
  • I thought that's what I corrected. The attacking program can gain access to memory in a different address space after calling vulnerable privileged code that uses that address space. For instance, Javascript can access the browser's address space, and the browser can call the kernel to access kernel address space using Spectre vulnerable conditionals in the kernel. VMs can call the VM host and access memory belonging to the hypervisor. – Alex Cannon Feb 27 '18 at 18:17