Detecting Buffer Overflows with ARM64 Memory Tagging Extension
Learn how ARM64's Memory Tagging Extension (MTE) turns hardware tags into immediate exceptions for out‑of‑bounds accesses, and how to enable and verify it in Linux.
07 Aug 2026, 06:24 UTC

Why ordinary bounds checks miss silent corruptions
Many C/C++ programs rely on runtime bounds checks or tools like AddressSanitizer to catch out‑of‑bounds writes. Those tools work well but add noticeable overhead and can miss bugs that corrupt memory without triggering a check. ARM64’s Memory Tagging Extension (MTE) offers a hardware‑assisted alternative: each 16‑byte memory granule carries a 4‑bit tag, and every load or store compares the tag embedded in the pointer’s top bits with the stored tag. A mismatch raises a synchronous exception before any data is corrupted.
How MTE works at the hardware level
Introduced in ARMv8.5‑A, MTE is visible through the ID_AA64PFR0_EL1 register (bits [20:16] indicate the MTE version). When the kernel enables MTE for a process, it assigns a random tag to each allocation granule. Pointers are tagged using the IRG (Insert Random Tag) instruction or via compiler‑generated code that inserts the tag into bits 56‑59 of the address. On every memory access, the CPU checks the tag; if it differs, a synchronous exception is generated, which Linux delivers as a SIGSEGV with si_code set to SEGV_MTESERR.
Enabling MTE in a Linux user‑space program
The simplest way for developers is to compile with the memtag sanitizer supported by GCC and Clang:
# Compile with MTE sanitizer (requires GCC 11+ or Clang 13+)
gcc -O2 -fsanitize=memtag -g -o test test.c
The compiler instruments allocations, inserts tags into pointers, and adds the necessary runtime support. No special privileges are required; the program runs normally until a tag mismatch occurs.
Worked example: triggering a tag mismatch
The following program deliberately creates a pointer with an incorrect tag and writes through it. The write should provoke a synchronous exception.
#include
#include
#include
int main(void) {
char *buf = malloc(16);
if (!buf) return 1;
/* Force a bad tag by setting the top byte to a non‑matching value */
unsigned long ptr = (unsigned long)buf;
ptr |= 0x1ULL << 56; /* set bit 56 (part of the tag field) */
char *bad = (char *)ptr;
bad[0] = 'X'; /* store triggers tag mismatch */
return 0;
}
When executed on an MTE‑capable CPU with a kernel that has MTE enabled, the process receives a SIGSEGV. The signal’s si_code will be SEGV_MTESERR, indicating a tag‑check failure. No silent corruption occurs; the program stops at the exact instruction that violated the tag.
Verifying hardware and kernel support
- CPU check: read the auxiliary vector at runtime, e.g.,
unsigned long cap = getauxval(AT_HWCAP);and testcap & HWCAP_MTE. Alternatively, parse/proc/self/auxvfor the HWCAP_MTE flag. - Boot log: after kernel start, run
dmesg | grep -i mte. A line like "ARM64: MTE detected" confirms the kernel recognized the feature. - Kernel configuration: ensure
CONFIG_ARM64_MTE=yis set in the kernel config; most recent distro kernels enable it by default on ARMv8.5‑A+ hardware.
Trade‑offs and limitations
- MTE primarily detects spatial errors such as buffer overflows and heap overruns. It does not automatically catch use‑after‑free unless the allocator retags freed memory with a mismatched value—a behavior that depends on the specific malloc implementation.
- Enabling MTE adds runtime overhead: a few percent increase in memory‑access latency and modest extra power consumption. For latency‑critical paths, developers often enable MTE only in debug or staging builds.
- The feature requires hardware that implements ARMv8.5‑A or later. Older ARMv8.0‑A CPUs lack MTE and will ignore the tag bits, silently treating them as part of the address.
Actionable steps for developers
- Confirm MTE presence on the target hardware using the auxiliary vector or
dmesg. - Build test binaries with
-fsanitize=memtag(or manually use IRG/addressing modes if fine‑grained control is needed). - Run the test suite under MTE; treat any SIGSEGV with
si_code == SEGV_MTESERRas a definite bug and prioritize fixing it. - For production releases, consider disabling MTE or enabling it selectively via environment variables (e.g.,
LD_PRELOAD=libmemtag.so) to limit overhead while still gaining safety benefits in critical components.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.