A memory-safety bug is what happens when a program touches memory it should not. The two big families are spatial bugs, where a read or write runs past the end of a buffer, and temporal bugs, where the program uses memory after it has been freed and possibly reused for something else. Use-after-free in particular has become the workhorse of modern exploitation, because a freed-and-reallocated object lets an attacker put their own data where the program still expects the old object.
Rewriting everything in a memory-safe language solves this at the source, and a lot of new code is being written that way. But the world runs on hundreds of millions of lines of existing C and C++ that will not be rewritten soon: kernels, media codecs, font parsers, cryptographic libraries, browser engines. MTE is aimed at that code. It does not require a rewrite. It requires a compatible chip and a tag-aware memory allocator.
The Core Idea: Lock and Key
MTE works on 16-byte chunks of memory called granules. Every granule gets a 4-bit tag, sometimes called its color. When the allocator hands out a block of memory, it picks a tag, paints every granule in that block with it, and returns a pointer whose top byte carries the same tag. The tag in the pointer is the key; the tag in the memory is the lock.
On every load and store, the hardware compares the pointer's tag against the memory's tag. If they match, the access proceeds. If they do not, the chip raises a fault. The pointer tag rides in the top byte of the 64-bit address, which works because ARM's Top-Byte-Ignore feature already meant the top byte was not used for addressing. There is room there for the key.
Spatial: an adjacent allocation gets a different tag, so a write that runs off the end of one buffer lands on a granule whose color does not match the pointer, and faults. Temporal: when memory is freed, the allocator re-colors it. The dangling pointer still carries the old tag, so a use-after-free dereferences a key that no longer fits the lock.
Why 4 Bits Is Enough (Mostly)
Four bits gives 16 possible tags. That sounds small, and it is: a random mismatch has a 1-in-16 chance of accidentally matching the wrong color and slipping through. MTE is therefore probabilistic, not absolute. For a single targeted access an attacker has a 1-in-16 chance of guessing right. For an exploit that must perform many illegal accesses in sequence, the odds of getting caught compound quickly, and one fault is enough to crash the process and end the attempt.
The allocator improves the odds by never giving adjacent live allocations the same tag, so an overflow into a neighbor is caught deterministically, and by re-tagging on free so the freed region almost always differs from the stale pointer. The probabilistic gap is real, but in practice it turns reliable exploits into unreliable ones, which is often the whole game.
Three Modes, Different Tradeoffs
MTE can run in different modes, and the choice trades precision against speed:
| Mode | Behavior |
|---|---|
| Synchronous | The fault is raised precisely on the offending instruction. You get the exact location, which is ideal for debugging and for stopping an exploit at the moment of the bad access. Higher overhead. |
| Asynchronous | The check happens but the fault is reported later, at the next kernel entry, without the precise faulting instruction. Lower overhead, suitable for always-on production telemetry. |
| Asymmetric | Synchronous precision on reads, asynchronous speed on writes, a middle setting that catches the most dangerous accesses precisely while keeping overhead down. |
The reason modes matter is that the prior generation of memory-bug detectors, like AddressSanitizer, used software shadow memory and ran far too slowly to ship enabled. They were debugging and fuzzing tools. MTE's overhead is low enough that a vendor can enable it on shipping devices, which moves the protection from the test lab to the field where the actual attacks happen.
Where You Can Use It Today
MTE arrived in the ARMv8.5-A architecture and started showing up in shipping consumer silicon over the last few hardware generations. On the software side, Android's Scudo allocator and the kernel were made tag-aware, and developers can opt applications into MTE. Some hardened mobile platforms go further and enable hardware memory tagging by default for the system and for many apps, treating it as a baseline rather than an opt-in.
For most people MTE is not a setting you manage. It is a property of the device and the operating system you chose, working underneath the apps you run. The practical takeaway is that the hardware you buy now encodes a security posture, and memory tagging is one of the clearer reasons a newer, well-supported device is more defensible than an old one.
What It Does Not Do
MTE is targeted, not total. It does not stop logic bugs, it does not stop bugs in code paths that are not tag-checked, and its probabilistic nature means a patient attacker can sometimes win the 1-in-16 lottery, especially in weaker asynchronous configurations. It is a strong layer, not a final one. The honest framing is the one the vendors use themselves: MTE meaningfully raises the cost and lowers the reliability of memory-corruption exploits against existing C and C++, while the longer-term migration to memory-safe languages continues underneath it.
That layered posture is the same reasoning we apply to the code that protects messages. Where memory safety is achievable by construction, we reach for it: the cryptographic core of Haven is written in Rust, which removes whole classes of these bugs before any hardware check is needed. Where unsafe code is unavoidable, defenses like MTE are exactly the kind of underlying hardening that should be on. Good security is rarely one mechanism. It is several, arranged so that a failure in one is caught by the next.