Most serious errors and security threats in software originate from pointer overflows, pointer overwrite and memory miss-management. This causes invalid/un-authorised memory addresses to be interpreted as valid references which in turn results in applications accessing restricted or unavailable memory. Such access can be exploited by an attacker to inject malicious data into memory, take control of the program flow and pose a serious security threat for any device and its users. This is especially a problem in an IoT driven world where everything is interconnected. Data breaches can also be very expensive to any business, both economically and in reputation.
ARM shipped a prototype board with the Morello architecture in January 2022 that is addressing these common problems through compartmentalization and protection of memory. The Morello board runs a customized quadcore Neoverse N1 processor which is based on the Armv8.2-A architecture (only the AArch64 execution state is supported). Secure memory management, if done in software, infers an overhead associated with the secure-compute, which could be expensive, hard to maintain and of limited effectiveness. The aim with Morello is to have the memory security features built into the silicon to mitigate the most common threats and flaws out there.
Morello is an implementation of the CHERI architecture (Capability Hardware Enhanced RISC Instructions) which was researched and developed for 10 years by Cambridge University and SRI International – a total of 150 researcher years of effort! The CHERI architecture is a 129-bit wide instruction set that introduces a new data type: the capability, a 129-bit wide integer which can either be stored in capability-enabled registers or in normal memory as a 128-bit / 16 byte integer (there is a bit of a difference here in bit size but more on that below). Let’s take a look at the capability in further detail:
That new capability data type is used to replace pointers, which thus far has been represented as integer address values. Capabilities are 64-bit integer values extended with metadata that enables fine grained control over their usage, they are the “tokens of authority” within the system. The capability registers are 129-bits wide to hold the new datatype – the program counter (PC) is also extended and is of a capability type (Capability PC). When stored in memory, the most significant bit (bit 128) of the capability (the tag) is stored separately in an area of memory not accessible by the usual store/load instructions and hence is invisible to the application software, the rest of the capability data is stored in regular memory. The key feature is that the tag table lives in its own memory partition and can have its own cache. Every access to virtual memory in the system will be checked against a capability, if an access does not involve a pointer then the architecture will check that memory access against Default Data Capability registers. The tag indicates if the capability is valid or not.
Capabilities can be loaded/stored only using a set of new Morello instructions. Every store instruction to any 128-bit/16 bytes of normal memory location will update the associated tag bit in the tag partition. Any non-capability instruction write to the normal memory containing the pointer and its metadata will clear the tag bit to 0, thus protection is provided against illegal modifications. Conversely, upon a read of the capability from normal memory the 1-bit tag is loaded into the 129-th bit of the general purpose register atomically from the tag partition.
Bounds is the base address value and length/size limit of the virtual memory covered by the capability. Data write/read will be checked against this field to evaluate if the requested operation on the memory area is valid. Capabilities can be used as code or data pointers. Code capabilities will typically have permissions set to instruction fetch and are used to control the available jump and branch destinations. Data capabilities on the other hand will typically have permissions set to data load/store. This mutual exclusion allows for the constraint that the data pointers cannot be used for execution and that function pointers cannot be interpreted as data.
Every load/store instruction or instruction fetch using the PC is checked against a capability. If the result after inspection of the metadata is not a pass an exception is generated. Likewise, if the valid tag of the capability is 0 an exception is generated. Thus, return or jump addresses can be protected using capability metadata which according to ARM guards against Return-Oriented and Jump-Oriented programming. The way the metadata can be changed is policed by the architecture: pointers must be obtained from other pointers, range/permissions cannot be increased and corrupted pointers cannot be de-referenced. The range/permissions of the capability can be lowered, which enables specializing/constraining further some derivative pointers if required.
If a memory region with a certain capability is under attack, then all areas with different associate capabilities are harder to access and thus shall remain protected. Compartmentalization of the memory access using capabilities of a single thread for their code and data, can reduce and mitigate the effects of a malicious code targeted at that thread for the rest of the system.
In conclusion, ARM’s Morello looks like a promising technology that will allow to protect pointers and thus increase the security in the system by treating all pointers as a completely new datatype at the architecture level.
Want to find out more? Read our second part to this post where we boot and play with Morello in a simulator.