Enabling ARM Memory Tagging Extension (MTE) on AArch64 Linux to Catch Heap Buffer Overflows
Learn how to enable ARM MTE on AArch64 Linux to detect heap buffer overflows, including hardware checks, kernel config, userspace opt‑in, and validation steps.
16 Oct 2025, 02:21 UTC

Problem and takeaway
Heap buffer overflows are a common class of vulnerabilities that can corrupt data or lead to code execution. On ARMv8.5‑A and newer CPUs, the Memory Tagging Extension (MTE) adds hardware‑assisted tag checks to pointers and memory allocations, causing a synchronous or asynchronous fault when a tag mismatch occurs. Enabling MTE gives you a low‑overhead way to detect many overflows at runtime without recompiling every library with sanitizers.
Decision and constraints
Decision: enable MTE for runtime detection of tag mismatches in user‑space programs.
Constraints:
- CPU must implement ARMv8.5‑A with the MTE feature.
- Linux kernel ≥5.10 with
CONFIG_ARM64_MTE=y. - Glibc ≥2.33 (provides the
prctlwrapper). - Userspace must opt‑in via
prctl(PR_SET_TAGGED_ADDR_CTRL, …)before allocations.
Options comparison
| Mode | Tag check | Typical overhead | Compatibility | Notes |
|---|---|---|---|---|
| Disabled | none | 0% | full | No protection. |
| Synchronous | Immediate SIGSEGV on mismatch | ~5‑10% | Requires tagged pointers | Deterministic fault; useful for debugging. |
| Asynchronous | Delayed exception via async abort | ~1‑2% | Works with untagged code | Lower overhead; fault may occur at next async abort point. |
Trade‑offs
Synchronous mode gives an instant fault, making it ideal for reproducing bugs in a development environment, but the added cost can be noticeable on allocation‑heavy workloads. Asynchronous mode reduces the overhead to a few percent while still catching most overflows; the fault is postponed until the kernel delivers an asynchronous abort, which may slightly delay detection but is often acceptable for production testing or fuzzing.
Implementation steps
1. Verify hardware support
Run the following command on the target system (no special privileges needed):
grep -i tagged /proc/cpuinfo
You should see a line containing the Tagged feature flag. If the output is empty, the CPU does not support MTE and the remaining steps will have no effect.
2. Ensure kernel is configured
Check the running kernel’s configuration:
zgrep CONFIG_ARM64_MTE /proc/config.gz
or, if the config is not compressed:
grep CONFIG_ARM64_MTE /boot/config-$(uname -r)
The output should show CONFIG_ARM64_MTE=y. If it is missing or set to n, you need to rebuild the kernel with that option enabled.
3. Enable MTE at runtime
MTE can be toggled via a sysctl. This requires root:
sudo sysctl kernel.mte=1
Verify the change:
sysctl kernel.mte
Expected output: kernel.mte = 1. The setting persists until the next reboot; to make it permanent, add kernel.mte=1 to /etc/sysctl.d/99-mte.conf.
4. Opt‑in from userspace
Before any heap allocations, your program must tell the kernel to enable tagged addresses for the calling thread:
#define _GNU_SOURCE
#include
#include
#include
int main(void) {
/* Enable tagged address control for this thread */
if (prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0) != 0) {
perror("prctl");
return 1;
}
/* Normal allocations will now receive random tags */
char *buf = malloc(64);
if (!buf) return 1;
/* Example: intentional overflow with a mismatched tag */
buf[64] = 0xFF; /* write one byte past the end */
free(buf);
return 0;
}
Compile with a standard toolchain (no special flags required):
gcc -Wall -O2 -o mte_test mte_test.c
Run the binary:
./mte_test
If MTE is active and the write triggers a tag mismatch, the program will terminate with a SIGSEGV. You can catch the signal to confirm the cause:
#define _GNU_SOURCE
#include
#include
#include
#include
static void handler(int sig, siginfo_t *si, void *unused) {
if (si->si_code == SEGV_TAGGED) {
printf("Caught tag‑ mismatch fault (si_code == SEGV_TAGGED)\n");
} else {
printf("Other segfault, si_code=%d\n", si->si_code);
}
_exit(0);
}
int main(void) {
struct sigaction sa = { .sa_sigaction = handler, .sa_flags = SA_SIGINFO };
sigaction(SIGSEGV, &sa, NULL);
/* same prctl and overflow code as above */
/* ... */
}
When the handler runs, it prints the message indicating a tag‑mismatch fault, confirming that MTE detected the overflow.
Limitations and practical checks
MTE only protects against out‑of‑bounds writes that cause a tag mismatch; it does not detect use‑after‑free, stack overflows, or logical errors. Performance impact varies: synchronous mode can add 5‑10% on allocation‑intensive workloads, while asynchronous mode typically stays under 2% but depends on how often the kernel delivers asynchronous aborts.
To verify that MTE is active on a running system, you can:
- Confirm the
Taggedflag in/proc/cpuinfo. - Check that
CONFIG_ARM64_MTE=yis set in the kernel config. - Read
sysctl kernel.mteand see it returns1. - Run a simple test program (as shown) and observe a
SIGSEGVwithsi_code == SEGV_TAGGEDwhen an out‑of‑bounds write occurs.
If any of these checks fail, MTE is not providing protection for that session.
Conclusion
Enabling ARM Memory Tagging Extension on a compatible AArch64 Linux system gives you a practical, low‑overhead way to catch many heap buffer overflows at runtime. By following the hardware, kernel, and userspace steps outlined above, you can decide whether synchronous or asynchronous mode fits your workflow, deploy the feature, and validate its operation with a small test program.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.