Welcome to LWN.net
The following subscription-only content has been made available to you by an LWN subscriber. Thousands of subscribers depend on LWN for the best news from the Linux and free software communities. If you enjoy this article, please consider accepting the trial offer on the right. Thank you for visiting LWN.net!
Free trial subscription
Try LWN for free for 1 month: no payment or credit card required. Activate your trial subscription now and see why thousands of readers subscribe to LWN.net.
By Jonathan Corbet
November 18, 2019
The arm64 architecture is found at the core of many, if not most, mobile devices; that means that arm64 devices are destined to be the target of attackers worldwide. That has led to a high level of interest in technologies that can harden these systems. There are currently several such technologies, based in both hardware and software, that are being readied for the arm64 kernel; read on for a survey on what is coming.
The Meltdown vulnerability enables an attacker in user space to read kernel-space data by making use of a combination of speculative execution and cache-based side channels. The kernel's defense against Meltdown is kernel page-table isolation — removing the kernel's page tables from the user-space mapping entirely. That works, but it has a significant performance cost and it can interfere with the use of other processor features. Nonetheless, it is fairly widely accepted that address-space isolation will be increasingly necessary to protect systems for some time.
There is an alternative, though: fix the hardware instead. One initiative in this area appears to be the E0PD feature, which was added as part of the Arm v8.5 extensions. Documentation on E0PD is scarce to the point of nonexistence; not even the patch set supporting it from Mark Brown describes how it works or what the acronym stands for. That said, the most informative bit of text about E0PD can be found there:
E0PD, introduced in the ARMv8.5 extensions, [...] ensures that accesses from userspace to the kernel's half of the memory map to always fault with constant time, preventing timing attacks without requiring constant unmapping and remapping or preventing legitimate accesses.
E0PD, thus, doesn't prevent speculative execution from going off into memory that user space should not be able to access, but it does block the side channel normally used to extract the data exposed by incorrectly speculated operations. Systems that support E0PD do not need to enable kernel page-table isolation and should, thus, regain the performance that it took away; no benchmark results were included with the patch set, though. E0PD support for the kernel is apparently close to ready, but the availability of processors with E0PD support may take rather longer.
Arm pointer authentication is a mechanism for applying cryptographic signatures to pointers used in running code. A special instruction creates a signature for a given pointer value using a secret key; the signature is stored in the unused bits at the upper end of the pointer itself. A separate instruction verifies that a given pointer was indeed signed using a specific key. This mechanism can be used to prevent attackers from fooling the kernel into using an ill-advised pointer value.
The return-address signing patch set from Amit Daniel Kachhap uses this feature for a specific purpose: protecting the return addresses for function calls on the stack. In particular, it uses the ‑msign‑return‑address flag added to GCC 7 to build the kernel with this protection. On entry to a function, the return address is signed; when the time comes for the function to return, the signature is verified. Should the verification fail, a kernel oops will be generated and the running process will be killed.
The intent behind this work, of course, is to protect the kernel against buffer overflows or other attacks that overwrite the stack. An attacker may be able to corrupt the stack, but they should not be able to place return addresses there that will pass the verification step. That should protect the kernel against a wide range of potential attacks, since many common techniques depend on placing crafted return addresses on the stack.
Another approach to protecting return addresses can be seen in the shadow call stack support patch set from Sami Tolvanen. Rather than signing return addresses, this patch set uses the Clang ‑f‑sanitize=shadow‑call‑stack option to cause return addresses to be placed on a separate "shadow" stack located somewhere in memory. Before a function returns, it restores the return address from the shadow stack.
The current call stack tends to be some of the easiest memory for an attacker to corrupt; any buffer overflow of an automatic variable will do. With the shadow call stack, though, this sort of corruption is rendered less harmful, since return addresses no longer live on the stack. The shadow stack will typically be much harder for an attacker to modify, or to even know where it might be located. The result should, once again, be a system that is more secure against buffer-overflow attacks.
Return-address signing and shadow call stacks appear to be two different approaches to the same problem; one probably does not want to use both of them. Tolvanen addresses the question of which should be used in the cover letter:
[The shadow call stack] has a minimal performance overhead, but allocating shadow stacks increases kernel memory usage. The feature is therefore mostly useful on hardware that lacks support for PAC instructions.
In other words, processors that can do pointer authentication should use that feature; shadow call stacks are there for those without that support. This patch set seems to be about ready; it is currentlyearmarked for the 5.6 merge window.
The last of the arm64 features under consideration is branch-target identification (BTI), which is intended to trap wild jumps. The idea is simple enough: if BTI is enabled, the first instruction encountered after an indirect jump must be a special BTI instruction. That instruction is a no-op on systems without BTI; with BTI, it has the added benefit of not throwing a fault should it be jumped to. Jumps to locations that do not feature a BTI instruction, instead, will lead to the quick death of the process involved.
BTI, thus, is a way of marking code that is meant to be the target of an indirect jump, thwarting attacks that somehow convince the kernel to jump to some random spot. That should block a range of attacks based on, for example, overwriting a structure full of function pointers called by the kernel. It is interesting to note that BTI does not check the target of a return from a function; the intent is that return-address signing should be used to protect returns. The GCC 9 release includes support for BTI.
Each of these technologies addresses one piece of the problem of protecting arm64 systems from attackers. Put together, they should have the effect of making these systems into significantly harder targets. The arms race will not end, and attackers will certainly find ways of getting around these techniques, at least some of the time. But, with luck, they will find themselves being frustrated more often in the future.(
to post comments)