In part 1 of this two-part series of articles about security gaps, I mainly wrote about the Spectre attack. I also gave you a solid introduction so you could better understand this subject. If you missed it, read it here before diving into today’s part.
In contrast to the Spectre, the Meltdown attack was successfully conducted only on Intel processors, but this group of devices is so big that we can say that this security gap is common. Meltdown is very similar to Spectre, but it’s way more dangerous because it also allows for reading memory that should be available only for processes working in kernel mode.
It should be mentioned that the virtual memory of a process consists of two parts: one that’s available in user mode, which can be accessed by a normal program, and the other that’s dedicated for processes in kernel mode, which are essential processes from the operating system’s standpoint.
A portion of memory available for system processes contains a lot of private data, the disclosure of which could endanger the safety of our system. Of course, this memory is protected against unauthorised access at the CPU level. Addresses of the user’s memory and kernel’s memory differ in terms of the first byte’s value.
Before referring to a memory, the processor checks the process mode and whether it has authorisation to refer to the desired memory address. If the processor detects unauthorised access, an exception will show up.
Before diving deeper into Meltdown, I have to describe briefly how exception handling works. It happens that one of the instructions of our program will perform a non-authorised action, but then the processor will call up the exception handling procedure, which will terminate the program.
We have explained the fundamental issues, so we can move on to Meltdown itself and how it works. Below, you can see what the basic attack code looks like.
- ; rcx = kernel address
- ; rbx = probe array
- retry:
- mov al, byte[rcx]
- shl rax, 0xc
- jz retry
- mov rbx, qword[rbx+rax]
The right logic begins at line number 4 – the mov instruction loads a single byte from the rcx address that belongs to the memory in kernel mode. Of course, access to this memory is forbidden in user mode, and as I wrote before, it will cause an exception.
However, if this instruction is called in advance by a processor in Out-Of-Order mode, the processor won’t check any safeguards for optimisation purposes and will allow for referring to that memory cell.
In the next lines of code, we save the value of that cell in the “rax” registry. In the last line of our code, we refer to the “rbx” table by downloading an element of that table. The index of this element is equal to the value that we read from memory that should only be available for processes in kernel mode.
This reference will cause the given table element to be saved in CPU memory, and we will be able to achieve the value of its index (which is also the value of the byte of kernel mode memory) in the same way as in the case of Spectre.
Of course, the processor has to execute all of the above steps in Out-Of-Order mode in advance before the real execution goes to line number 4, causing an exception which will terminate the program. This quasi-race allows us to read memory that won’t be available under standard conditions.
How to defend?
Although the risk of a successful attack is minor at the moment, its effects may be extremely severe. Because of this, we must not disregard this situation, and as users, we should do everything we can to protect ourselves.
First, we should update our software. And I don’t mean just operating systems but also visualisation software, containers like docker, and internet browsers. Firefox, Chrome and Internet Explorer presented updates that protect us from the attacks described in this article. Moreover, Chrome users should enable the “Strict Site Isolation” option which isolates individual websites in separate processes, protecting us from the Spectre attack in this way.
KASLR (Kernel Address Space Layout Randomization) has been presented to reduce the dangerous effects of the Meltdown attack. As I mentioned before, virtual memory consists of two parts: the user’s part and kernel’s part. The attack is possible if we know the address of the memory that we want to read.
Up till now, the origin of the memory available for system processes was always in the same place, so the attack was simplified. Because of KASLR, the offset for the kernel’s memory is taken at random every time, and this makes the attack more difficult to perform (but it’s still possible!).
The KAISER patch was presented to make the defence against the Meltdown attack as efficient as possible. It allows for better isolation of memory available for processors in kernel mode from the user’s memory. KAISER doesn’t map kernel memory within the virtual memory provided for user’s processes, with the exception of the few fragments essential for the proper functioning of every process, i.e. interrupt handlers. In this case, only a small part of the kernel’s memory is exposed to a breakdown. Combining KASLR with KAISER, it’s possible to reduce the effects of the Meltdown-type attack considerably.
It should be said that the real problem lies in the processor itself and its working methods. Because of this, any attempt to eliminate this problem with the use of software is a battle against symptoms, and it doesn’t remove the cause. The problem can be eliminated only at the CPU level by installing microcodes, new software or, in the worst case, by changing the architecture of processors.
It’s difficult to say how this would affect performance, but it’s highly likely that there will be a temporary slowdown in the processor development process. For now, this is merely speculation, and we must wait cautiously for further development of events.