The Arm64 memory tagging extension in Linux
As one might expect, the Arm64 architecture uses 64-bit pointers to address memory. There is no need (yet!) for an address space that large, though, so normally only 48 of those bits are actually used by the hardware — or 52 bits if a special large-address-space option is enabled. So there are 12-16 bits that can be used for other purposes. Arm systems have long supported a "top byte ignore" feature that allows software to store arbitrary data in the uppermost byte of a virtual address, but the hardware designers have been busy coming up with other uses for those bits as well. The memory tagging extension (MTE) is one of those uses.
Specifically, MTE allows the storage of a four-bit "key" in bits 59-56 of a virtual address — the lower "nibble" of the top byte. It is also possible to associate a specific key value with one or more 16-byte ranges of memory. When a pointer is dereferenced, the key stored in the pointer itself is compared to that associated with the memory the pointer references; if the two do not match, a trap may be raised. Keys can be managed by the application, or they can be randomly generated by the CPU.
Four bits only allow for 16 distinct key values, but that is enough to do some interesting things. If a function like malloc() ensures that allocations that are adjacent in memory have different key values, then an access that overruns any given allocation will be detected by the processor. Use-after-free bugs can be detected by changing the key value immediately when a range of memory is freed. If each stack frame is given its own key, buffer overruns on the stack will also generate traps. An attempt to dereference a completely wild pointer (or one injected by an attacker) also has a good chance of being detected.
MTE thus has two levels of applicability. If enabled during the normal software-development process, it should help to identify a range of bugs before they ever make it into a release. But it can also be enabled on production systems to add one more obstacle that an attacker must overcome to exploit a known vulnerability.
MTE is disabled by default on Linux systems, even on hardware that supports it. A user-space process can enable MTE for a specific region of memory by specifying the new PROT_MTE flag in the mmap() call creating that region. mprotect() can also be used to enable MTE on already-mapped memory. Only anonymous memory can have PROT_MTE set; attempts to use it with file-backed memory will fail.
The default key associated with all memory is zero; using any other value requires a couple of steps. The first of those is usually to create a tagged address for the memory of interest; that is simply a matter of storing the key value in bits 59-56 of the address. There is a new instruction (IRG) that will generate a random key and store it into an address. The other piece is to associate the key with the memory itself. To that end, another new instruction (STG) takes a pointer value and sets the key for the 16-byte "granule" containing the pointed-to memory to the key found in that pointer. Various other instructions exist for managing tags, setting the contents of memory along with the tag, etc. These are all unprivileged operations that do not require assistance from the kernel.
If a process attempts to access memory with the wrong key, the processor will, by default, do nothing. This can be changed by using the PR_SET_TAGGED_ADDR_CTRL command to the prctl() system call. Providing a value of PR_MTE_TCF_NONE disables tag checking (the default). There are two values (PR_MTE_TCF_SYNC and PR_MTE_TCF_ASYNC) that will cause a SIGSEGV to be delivered on a key mismatch; the former causes the signal to be delivered immediately (synchronously), while the latter queues it asynchronously. A synchronous signal will be delivered immediately to the offending thread and the operation will not be performed; if the signal is not handled the process will be terminated. An asynchronous signal will be queued for later delivery to the process, and the mismatched operation will proceed.
There are some other features associated with MTE that are supported by the kernel, including a set of ptrace() commands for manipulating tags for another process. Some more information (and a sample program) can be found in Documentation/arm64/memory-tagging-extension.rst in the kernel source. Note that, in 5.10, use of MTE is only supported for user space; support for MTE within the kernel itself will come in a future development cycle.
Some readers may note a resemblance to the Arm pointer authentication feature, which stores a short cryptographic signature into the upper bits of pointer values. Pointer authentication can prevent the creation of new pointers by an attacker; it depends entirely on the knowledge of a secret key value and does not associate any sort of key with the memory itself. This feature and MTE can be used together, though MTE will rob some bits and make the pointer-authentication signature shorter. There is value in both; MTE can prevent overruns on the stack, while authentication can prevent the corruption of the stack pointer itself.
While the MTE feature seems useful, the number of applications that will gain direct support for it is likely to be small. Happily, much of the benefit can be had without the need to change applications at all. If the C library (and its memory allocator in particular) supports MTE, then all applications should gain the extra memory-safety checks automatically. MTE patches for the GNU C library have been posted for consideration, so that support should be available eventually. The LLVM compiler has support for stack tagging now; GCC should gain that support eventually.
None of this is helpful to anybody now, though, since hardware with MTE
support is not actually shipping yet. The good news is that, once that
hardware is available, the software side should be ready for it
immediately. That, with
any luck at all, should lead to more secure systems and software with fewer
bugs, even on hardware without the memory-tagging feature.
| Index entries for this article | |
|---|---|
| Kernel | Architectures/Arm |