The vulnerability associated with Meltdown, on the other hand, could be used to read data from kernel space. It may or may not be exploitable remotely, and I think it’s less likely to be encountered in the wild because there are far easier ways to compromise a system. It can be mitigated (though not entirely) by installing a new kernel with KAISER (with KALSR) enabled in the configuration. I think it’s the default in the latest kernel releases.
The main difference between the two attacks is that only Meltdown could be used for accessing kernel space, and Spectre attacks are used to arbitrarily access data elsewhere within the same process. What both attacks have in common is they involve a processor feature that for executing instructions out of sequence and the ability to determine the contents of a CPU cache through a side channel.
Much of this was new territory to me, since I wasn’t well-read on the intricacies of thread/instruction scheduling in modern processors. From what I can gather, the proof-of-concept exploits bypass memory isolation by inserting erroneous data in transient instructions that are executed out of sequence, protected data is dumped into a CPU cache when an unhandled exception occurs, and the contents of the cache are read into a user-space array. Unfortunately that’s pretty much the extent of my understanding. There are numerous things that happen concurrently at a low level behind the exploits, and it isn’t clear precisely what the mechanism is for transferring data from the cache to an array in user space, or whether flushing the cache does this automatically.
The Red Hat advisory is a good place to start, and both research papers are very comprehensive if you want to explore this more deeply, and I’ve found Rendition Infosec’s presentation useful also.
Virtual and Physical Memory Recap
The following is a gross simplification of what happens on a running system, just in case it’s not common knowledge (it’s been a while since I’ve done any memory analysis): Memory is logically divided into user space and kernel space. User space is where processes (running programs) exist. The kernel space is where you’d find the kernel and loaded modules, and anything running in kernel space has access to everything on the system.
When programs and applications are launched, a data structure is initialised as a process – this contains an instance of the executable, a region for dynamic data, another region for static data, links to shared resources and libraries, and a few other things. To get an idea of what the data structure looks like on Windows, one could run VMMap or create a memory dump in Process Explorer. Now, it appears, to the user, that a process is one contiguous data structure, but in reality physical memory usage is actually fragmented. A process exists therefore as a virtualised region of memory, with virtual memory addresses being mapped in a page table to physical addresses.
The next important point is that a process shouldn’t be capable of accessing kernel space directly – if it did, any malicious or exploited process could manipulate the kernel and the entire system would be compromised. If a program attempts this, there’ll be an exception and the kernel would terminate the process if the exception is otherwise unhandled. This is why we’re usually more concerned with vulnerabilities in programs that already run with root/admin/system privileges.
Mechanics aside, the Spectre and Meltdown attacks exploit vulnerabilities in the processor hardware to bypass memory isolation.
The most concise definition I’ve found came from the ExtremeTech site: ‘The CPU does check to see if an invalid memory access occurs, but it performs the check after speculative execution, not before. Architecturally, these invalid branches never execute – they’re blocked – but it’s possible to read data from affected cache blocks even so.‘
The salient point to Meltdown is that it potentially enables an attacker to indirectly access kernel space and escalate privileges. This vulnerability can be mitigated by updating the kernel to the latest version, or by recompiling it with KASLR/KAISER enabled in the configuration.
The attack starts with the creation of a process that initialises a large array, and an attempt to read a byte of data from an arbitrary address in kernel space. Of course, this would result in an unhandled exception, with the kernel terminating the process. The way around this is to use speculative execution of instructions to pre-empt the kernel exception handler, as this would result in the instructions being executed and the cache state changing before the exception happens.
The next stage of the attack probes the cache state to determine what’s stored there, and reads the data into the user-space array. This is a transaction between a process and the hardware layer. The component receiving data from the cache is separate from the transient instructions, and could be another thread or process – a proposed Meltdown attack involves a parent process. To get the cache to reveal its state the researchers flushed or reloaded the cache into a user space array. Once the transient instructions access a valid address, that address is stored in the cache, and this could be confirmed by measuring the access time for that address – obviously the CPU will get data from the cache much faster than from the system memory. By measuring the access times for each address in the cache, it is possible to determine the cache’s contents.
The main difference between this and the previous attack is that Spectre uses the processor’s branch predictor, and it cannot be used to read anything outside the process’ virtual memory. As I’ve already mentioned, this is still potentially a problem if it enables some entity to read data across browser tabs.
In the setup stage, the attacker searches for a set of instructions within a process that could be used to provide a ‘side channel’ for getting information about the cache state. Also, the branch predictor is trained to speculatively execute a branch of another process – the CPU is executing the same JMP instructions during multiple iterations, that gets recorded by the branch predictor, and the branch instructions are executed speculatively. Now it’s a matter of changing the operand of that JMP instruction, which would be a memory address of the instructions providing the ‘side channel’. The researchers searched for ‘flush-and-probe- instructions in shared DLL memory, and swapped an address in speculative instructions to point at it. The sensitive data can then be recovered by sending the flush and reload instructions.