Meltdown - review
This is a summary of the Meltdown(Lipp et al. 2018) attack.
Meltdown is a fairly simple but very effective attack using out-of-order execution that primarily affects Intel CPUs (2010-2017), but may impact other CPUs too.
Terminology
out-of-order execution
When executing program instructions, CPUs are often faced with having to wait for portions of memory to be loaded so that some of the operations can be performed. Modern CPUs often look ahead to the instructions queued to execute, and execute those instructions that do not have a dependence on the result of the operation waiting for the memory to be loaded. This ahead-of-time execution of certain portions of code is called out-of-order execution. In the below example, the call to myfn
does not depend on the result of memory fetch of *myaddr
. Hence, myfn may be executed out-of-order (i.e before *myaddr
memory fetch completes).
var_a = *myaddr;
myfn(var_c, var_d);
Building blocks
The main idea in meltdown is that modern CPUs that use out-of-order execution leaves traces of the transient (micro-architectural) effects such as loaded cache-lines even when the out-of-order execution is later rolled back.
The second observation is that in current unpatched systems such as Linux, for performance reasons, the entire kernel memory is mapped to the memory space of the user program. This mapped memory is only accessible when executing in the kernel mode. The kernel memory also maps the entire physical memory. So, if there was no kernel access protection, any user program would have access to the entire physical memory (that may contain parts of currently executing processes), and the kernel memory.
The third observation is that during out-of-order execution, the CPUs have no concept of protected memory when executing in out-of-order mode.
The final observation is that the transient (micro-architectural) effects such as loaded cache-lines are visible to the executing program even though the out-of-order instructions that caused the cache-lines to be loaded was never committed (retired), which can be checked by timing the time taken to load any given data from a given cache line. Hence, an operation that was carried out out-of-order but never committed can transmit the result in a bit-by-bit fashion by accessing different cache lines. (The other end – which may be the same process or a child process with access to same locations – has to first flush all cache lines, and then just check which cache lines were loaded by checking the time difference in access.)
A toy example
Say one has a program as below (from (Lipp et al. 2018))
raise_exception()
access(probe_array[data * 4096])
The access
call has no dependence on raise_exception
. Hence, the access
can get executed earlier through out-of-order execution. However, it will never get committed because raise_exception
will always drive the execution to the exception handler. What happens when the data*4096
is referring to a kernel location? Due to out-of-order execution not actually verifying the permissions, the data is actually fetched, which means that the cache line containing that location is fetched into the cache. Once the raise_exception
finish executing, the out-of-order execution results of access
which may be stored in a register is thrown away. However, the fetched cache remains. The particular cache-lines that remains can be checked later to determine what the value inside data
was.
The attack
Say one has the instruction sequence as below (from (Lipp et al. 2018))
; rcx = kernel address
; rbx = probe array
retry:
mov al, byte [rcx]
shl rax, 0xc
jz retry
mov rbx, qword [rbx + rax]
Here, the rcx
contains an inaccessible kernel address, on which access an exception will be raised. However, the following instructions are executed out-of-order anyway before the exception is raised. Hence, the cache line that gets accessed leaks the information about the contents of memory location at rcx
. The particular cache lines accessed are checked later.
This means that one can dump the kernel memory and the physical memory of a system.
Mitigation
- The KAISER patch ensures that the kernel does not get mapped in user space.