Understanding Spectre and Meltdown Vulnerability – Part 3

In this blog post, we are going to talk about spectre vulnerability and how does this vulnerability affect current systems all around the globe. As already discussed in previous blog posts, the processor uses speculative execution along with branch predictor to speculatively execute instructions for better utilization of CPU cycles.

Spectre attacks involve speculatively executing some of the instructions which would otherwise never get executed. These speculatively executed instructions bring about some changes in microarchitectural state i.e. CPU cache which might lead to side channel attacks. These side channel attacks include Flush-Reload Attack (the same attack which is used in Meltdown vulnerability). 

Understanding Spectre

The basic difference between spectre and meltdown attacks is that in spectre we trick the process into revealing its own data or secret, unlike meltdown where we trick the kernel into revealing the data present in kernel memory.

Think about, you are running a web browser. On that web browser, you are running multiple apps and all those apps share some common address space. Now how does underlying VM makes sure that these apps cannot access the contents of this common address space which might contain some secret data or passwords? This is made sure by having relevant checks before accessing any memory location.

Say I have an app A on a browser, which creates and uses some arrays in javascript
( Just FYI This javascript code segment gets executed on our browser 😀 😀 )

var fruits = ["Banana", "Orange", "Apple", "Mango"];
var fLen = fruits.length;
var text = "<ul>";
for (i = 0; i < fLen; i++) {
    text += "<li>" + fruits[i] + "</li>";
}

Question: What stops this piece of code from accessing fruits[1000]
Answer:    If this piece of code tries to access anything beyond the array length, the VM returns undefined ( javascript specifics ) because internally the VM for every array access makes sure that access made is within the array bounds i.e. less than the array size. So internally every array access made encapsulates this piece of code getting executed:

var value = if ( x < fruits.size()) {
 fruits[x];
}

So in this way, the underlying VM prevents the apps running on a single VM from accessing the secret data or passwords stored in the common address space.

Spectre attacks provide just a way to break this isolation provided by VMs, browsers in our case. With this attack, we can get hold of any secrets or passwords stored in this common memory space, given we know the address at which these secrets/passwords are stored beforehand.

Deep Dive into Spectre Attack

Spectre attacks are first accompanied by training the branch predictor to take a particular branch in a particular code segment. This is done by invoking the target code with enough values which result in that particular branch being taken. After training the branch predictor, the processor starts speculatively executing the instructions in the predicted branch. At this moment, the attacker passes a malicious value to the target code. The processor still tries to speculatively execute instructions even for that malicious value.

As soon as the processor realizes that it has taken the incorrect branch, it quickly discards the state ( registers etc ) of the speculatively executed instructions. But during this speculative execution window, some microarchitectural changes have already been done which could be used by the attacker to gain information about some secret information hidden inside process memory.

Lets’ understand this with an example:

if ( x < array1.size()) {
  int value = array2[array1[x] * 4096] // branch 1
}

Note: In this simple example, we are simply checking whether X is within the array limits. If it is then we will fetch the value from that particular location which is at X offset in array array1. Also, array2 is a very large enough array to accommodate many values.

So initially the attacker executes this code segment with valid values of which are inside the array limits and during this, the processor with the help of branch predictor starts speculatively executing the branch 1 i.e. array2[array1[x] * 4096]. During this phase, when the processor speculatively executes the branch 1, the attacker does following things:

  • Attacker passes a value of X which is way outside the array limits of array1 i.e. X > array1.size()
  • Before starting the spectre attack, the attacker makes sure that CPU cache is flushed. This will make sure that the processor is idle during the time this value i.e. array1.size() is fetched from memory and hence the processor will start speculatively executing the branch 1 instructions.
  • During this speculative execution window, the processor gathers the value at location (array1 + x) address. As this X offset is way beyond the array limits, this (array1 + x) address may easily contain some hidden secrets or passwords within the process’s memory.
  • Now this memory location is brought onto registers and rest of the instructions in the branch are executed i.e. array2 [ Secret * 4096 ]. After the computation of these instructions, we will be sure that this value array2 [ Secret * 4096 ] would now be present in CPU cache.
  • Rest of the steps are more or less similar to already explained steps in the previous meltdown attack in which we iterate over all the possible values of Secret and check how much time it takes to load the value from memory. See [LINK] for more details about the steps.

In this way, we can gain access to some secret information stored inside the process’s memory ( passwords or documents ) byte by byte.

Note: In javascript, we don’t have CLFFLUSH to ensure CPU cache is flushed, but this can be made sure indirectly by using the Evict+Reload technique.

See this link, for more details

In Evict+Reload, eviction is achieved by forcing contention on the cache set that stores the line, e.g., by accessing other memory locations which get bought into the cache and (due to the limited size of the cache) cause the processor to discard the evict the line that is subsequently probed.

Dealing with Spectre Vulnerability

There is no straightforward way to mitigate this vulnerability. One of the few ways ( discussed in this paper ) by which we can mitigate this is by disabling the speculative executions of instructions in critical sections of code. These code segments can be decided by the VMs on the basis of how much this code segment on two things:

  • Could these segments be potentially used to speculatively execute instructions and then get hold of the secrets or keys in protected areas of process memory.
  • How much of performance hit the application would take if we disable the speculative execution in those code segments.

Instructions like LFENCE makes sure that we block the execution of further instructions till all the instructions till that point have been computed.

Performs a serializing operation on all load-from-memory instructions that were issued prior the LFENCE instruction. Specifically, LFENCE does not execute until all prior instructions have completed locally, and no later instruction begins execution until LFENCE completes.

 

References:

One thought on “Understanding Spectre and Meltdown Vulnerability – Part 3

Leave a Reply