When the Spectre and Meltdown attacks were disclosed earlier this year, the expectation was that these attacks would be the first of many, as researchers took a closer look at the way that the speculative execution in modern processors could be used to leak sensitive information and undermine the security of software running on those processors. In May, we saw the speculative store bypass, and today we have a new variant on this theme: speculative buffer overflows, discovered by Vladimir Kiriansky at MIT and independent researcher Carl Waldspurger.
All the attacks follow a common set of principles. Each processor has an architectural behavior (the documented behavior that describes how the instructions work and that programmers depend on to write their programs) and a microarchitectural behavior (the way an actual implementation of the architecture behaves). These can diverge in subtle ways. For example, architecturally, a program that loads a value from a particular address in memory will wait until the address is known before trying to perform the load. Microarchitecturally, however, the processor might try to speculatively guess at the address so that it can start loading the value from memory (which is slow) even before it’s absolutely certain of which address it should use.
If the processor guesses wrong, it will ignore the guessed-at value and perform the load again, this time with the correct address. The architecturally defined behavior is thus preserved. But that faulty guess will disturb other parts of the processor—in particular the contents of the cache. These microarchitectural disturbances can be detected and measured, allowing a malicious program to make inferences about the values stored in memory.
The new attack is most similar to the array bounds check variant of Spectre. This attack takes advantage of a common coding pattern: before accessing the Nth element of an array, a program first checks that the array has an Nth element to access by comparing N to the size of the array. In most cases, that comparison succeeds, so the processor will speculatively assume that the comparison is OK and blindly try to access the Nth element.
In the Spectre attack, an attempt is made to read an element that doesn’t exist. The speculative access will therefore try to read memory that isn’t part of the array. This speculatively read data can then be used to disturb the processor’s cache in a detectable way. When the processor notices that the element doesn’t exist and that the read shouldn’t be performed, it will cancel the speculative execution. But the cache remains disturbed. This disturbance means that an attacker can make inferences about the data that was speculatively read.
If reads work, then how about writes?
Writing beyond the extents of an array is a well-known attack method, known as a buffer overflow. It can be tremendously powerful, allowing an attacker to execute code of their choosing and completely compromise a buggy application. Buffer overflows gain their power because programs often store addresses of code adjacent to data in arrays, with the processor using these code addresses to determine which instructions to run each time a function call is completed. Overflowing the buffer allows this code address to be overwritten, which in turn means that the attacker can trick a program into running code of their choosing.
The main way of preventing buffer overflows is to check that each attempt to access an element of an array is an attempt to access an element that actually exists. But this is where Spectre comes into play. Even if the program is correctly written and checks that every array access is valid, the processor can speculatively attempt invalid accesses. These invalid accesses can do things such as (speculatively) write outside the bounds of an array. The speculative writes can do things such as overwrite code addresses, in the same way that traditional buffer overflows work.
These speculative buffer overflows can therefore cause the processor to speculatively execute code of an attacker’s choosing. The processor assumes that a write to a buffer is safe (even though it actually overflows the buffer), and it speculatively overwrites a code address. The code at that address is then speculatively executed, causing a measurable disturbance to the processor’s cache. Eventually, the processor will notice that the array access was invalid, and all the speculative execution will be rolled back. The buffer isn’t actually overflowed. But the disturbance to the processor’s state, in particular to its cache, doesn’t get undone.
This speculative execution can even do other things that wouldn’t be allowed: for example, Intel processors allow speculative writes to be made to read-only memory, giving even more power to an attacker. This has some similarity to the Meltdown attack; Intel and certain ARM processors (though not AMD chips) will allow user-mode programs to speculatively read kernel-mode memory because of the way the processors defer checking whether the access is permitted. It turns out that they also defer checking whether a write is permitted, too.
Similar problems have similar fixes
A range of software fixes has been devised for the original Spectre array bounds attack. These fixes work in two main ways. The first is to insert a delay between testing if an array element actually exists in the array and then using that array element. x86 processors already have a suitable function, and ARM has added a new instruction for ARM chips to achieve the same. These instructions essentially act to block speculative execution such that the processor must know definitively whether the array element exists or not before proceeding. This addresses the original Spectre variant, and it’s equally applicable to the new version.
The second approach is to constrain the array elements accessed so that, for example, any attempt to speculatively access an element that doesn’t exist is always directed at the first element of the array. Again, this approach is equally applicable for speculative reads as it is for speculative writes.
Moreover, techniques used to protect against conventional buffer overflows can also be useful against speculative buffer overflows. For example, one approach is to combine any sensitive code addresses with a secret key. The code addresses have to be decoded before being used. This is useful because any attempt to overwrite one of these addresses will write a value that hasn’t been combined with the secret key; decoding the address will produce some invalid value, preventing an attacker from controlling which code is speculatively executed.
The researchers also propose a family of hardware changes that should offer broader protection against this kind of attack. These protections may be possible to implement in a microcode update, offering a way to protect software running on existing processors. Broadly speaking, the changes would prevent the processor from using the speculatively written values in other contexts. For example, they would block a speculatively written code address from being used to control subsequent speculative execution. Such modifications may also prove useful in protecting against the speculative store bypass attack from May.
In response to the new findings, Intel has offered updated guidance to developers on how to avoid speculative execution-based attacks.