Buffer overflows have been on the CWE Top 25 for twenty years. We have stack canaries, ASLR, DEP, control-flow integrity, and memory-safe languages, yet they keep showing up in critical code. The reason is simple: every one of those mitigations runs in software, and software can be bypassed, misconfigured, or simply not used.
What if the hardware itself refused to let you read past the end of an array?
Why software mitigations keep losing
A buffer overflow happens when a program writes beyond the bounds of an allocated memory region. In C, this is trivial because a pointer is just an address. The compiler and the CPU trust that the programmer knows what they are doing. If you allocate 64 bytes and write to offset 80, the CPU will do it. It has no idea where the buffer ends.
Software mitigations try to catch this after the fact. Stack canaries place a known value before the return address and check it before returning. ASLR randomizes memory layout to make exploits harder to construct. Control-flow integrity restricts where indirect jumps can land. These help, but they are probabilistic or incomplete. A determined attacker with enough time and an infoleak can usually work around them.
Memory-safe languages like Rust solve the problem at the language level, but they do not help the billions of lines of C and C++ already in production. Rewriting the Linux kernel, OpenSSL, or your legacy backend in Rust is not happening this decade. We need a defense that protects existing code without requiring a rewrite.
What CHERI actually does to a pointer
CHERI, which stands for Capability Hardware Enhanced RISC Instructions, is an ISA extension developed at the University of Cambridge and now supported by Arm in the form of Morello. It changes what a pointer is.
In a conventional 64-bit system, a pointer is 64 bits: just an address. In a CHERI system, a pointer becomes a 128-bit or 256-bit capability. The extra bits store metadata: the base address of the allocation, the bounds (how far it extends), and permissions (read, write, execute). The capability is protected by a hardware integrity tag so that tampering with the metadata invalidates it.
When you compile code for CHERI, malloc does not just return an address. It returns a capability whose bounds are exactly the size you requested. When you increment that pointer, the hardware checks every access against those bounds. If you try to read or write outside them, the CPU raises a synchronous exception. There is no way to forge a capability with wider bounds. The hardware simply will not let you.
This is the key difference. Software bounds checking inserts checks around memory operations, which compilers can optimize away, programmers can forget, and attackers can bypass. Hardware bounds checking happens on every load and store, unconditionally, with zero instructions added to your program.
How bounds enforcement works at the instruction level
Here is what a typical buffer overflow looks like in C:
#include <string.h>
void vulnerable(char *input) {
char buf[64];
strcpy(buf, input); // No bounds check. Classic overflow.
}
On a normal architecture, this corrupts the stack. On CHERI, buf is not a raw address. It is a capability whose bounds are exactly 64 bytes. When strcpy tries to write past byte 64, the hardware raises a Capability Bounds Violation exception. The program crashes immediately at the exact offending instruction.
The same protection applies to heap allocations:
#include <stdlib.h>
#include <cheri.h> // CHERI intrinsics header
void heap_example(void) {
char *buf = malloc(32);
// buf is a capability with base=buf, length=32
// This works fine
buf[0] = 'A';
buf[31] = 'Z';
// This traps at the hardware level
buf[32] = 'X'; // Capability bounds violation
}
The crash is precise. You get the exact instruction, the exact capability, and the exact bounds that were violated. Debugging this is easier than chasing a corrupted stack frame three frames later.
CHERI also prevents pointer forgery. You cannot cast an integer to a pointer and start dereferencing arbitrary memory. The hardware only recognizes capabilities created by legitimate instructions like malloc, stack allocation, or explicit capability derivation. Integer-to-pointer casts produce untagged values that trap on use.
The compatibility problem is real
If CHERI is this good, why isn’t every server running it? Because changing what a pointer is breaks assumptions that decades of C code rely on.
The first issue is size. Capabilities are larger than raw pointers. On Morello, a capability is 128 bits plus a 1-bit tag stored separately by the hardware. This increases memory usage for pointer-heavy data structures. A linked list or a tree full of pointers gets measurably bigger. For many applications the overhead is single-digit percentages, but for pointer-chasing workloads it can be worse.
The second issue is casting. C code casts pointers to uintptr_t, does arithmetic, and casts back. On CHERI, uintptr_t is actually a capability type, not an integer. Code that assumes it can do arbitrary integer math on pointer values will fail to compile or trap at runtime. The fix is usually to use ptrdiff_t for offsets and apply them with cheri_address_set, but that requires changing the source.
The third issue is the ecosystem. CHERI hardware is rare. Arm Morello boards exist but are not commodity servers. CheriBSD and CHERI-enabled Linux are mature enough to run real software, but most Linux distributions do not ship CHERI packages. Your container registry, your CI runners, and your cloud provider do not support it yet.
You can try it today without custom silicon
You do not need a Morello board to experiment with CHERI. The CHERI project maintains QEMU support, so you can run a CHERI userspace on any Linux or macOS host.
Here is how to get a CHERI toolchain and run a simple example in QEMU:
# Clone the CHERI SDK setup
$ git clone https://github.com/CTSRD-CHERI/cheribuild.git
$ cd cheribuild
# Build the CheriBSD disk image and QEMU
$ ./cheribuild.py cheribsd-riscv64-purecap -d
$ ./cheribuild.py qemu -d
# Boot CheriBSD in QEMU
$ ./cheribuild.py run-cheribsd-riscv64
Once inside CheriBSD, the compiler is clang with CHERI target support. You compile with bounds checking enforced by default:
$ clang -o test test.c
$ ./test
# Buffer overflows trap immediately with a clear message
If you want to test existing software, cheribuild can compile popular packages from FreeBSD ports with CHERI support. The CHERI team has ported OpenSSH, nginx, PostgreSQL, and large parts of the FreeBSD base system. Many programs work without changes. The ones that break usually do so because of unsafe pointer casts or assumptions about pointer size.
For a quicker start without QEMU, the University of Cambridge also publishes a Docker image with the CHERI LLVM toolchain preinstalled. You can compile CHERI binaries and inspect the generated capability-aware assembly without booting a full OS.
What hardware memory safety means for existing codebases
CHERI is not a replacement for writing safe code. It is a safety net for the code you already have. A CHERI system running legacy C will still have logic bugs, race conditions, and use-after-free errors. But it will not have buffer overflows that overwrite return addresses, corrupt adjacent heap metadata, or leak secrets across allocation boundaries.
The progression is already visible. Arm has incorporated CHERI-derived features into its Memory Tagging Extension (MTE), which is shipping in production Android devices today. MTE is coarser than CHERI (it tags 16-byte granules rather than individual allocations), but it catches many of the same bugs with lower overhead. Full CHERI is likely to follow as the ecosystem matures.
If you maintain C or C++ code that processes untrusted input, the question is not whether hardware memory safety will arrive. It is whether you will be ready when it does. Start by auditing your code for pointer-to-integer casts, pointer arithmetic on unrelated objects, and assumptions about sizeof(void*). Those are the patterns that break under CHERI, and fixing them makes your code cleaner even on conventional hardware.
The hardware is finally willing to say no. We should let it.