MTE 是 ARMv8.5-A 引入的硬件内存安全机制,通过为每 16 字节内存和指针附加 4 位标签并在访存时自动校验,能实时捕获越界读写并触发 SIGSEGV,具备低开销、运行时检测特性。

什么是 MTE,它真能抓到越界读写吗
MTE(Memory Tagging Extension)是 ARMv8.5-A 起引入的硬件特性,给每 16 字节内存块附加 4 位标签(tag),指针也带同样 4 位 tag;CPU 在访存时自动比对——不匹配就触发 EXC_RETURN 异常(通常转为 SIGSEGV)。它不是静态分析,也不是 ASan 那种插桩,而是运行时、低开销(典型性能损耗
但它只对启用 MTE 的内存生效,且仅检查「tag 匹配性」,不验证逻辑合法性。比如:malloc 返回的指针默认无 tag,必须显式用 __builtin_arm_mte_create_tag 或 __builtin_arm_mte_set_tag 打标;否则即使越界,也不会触发异常。
- 只对 16 字节对齐的内存区域有效(
mmap分配时需指定MAP_SYNC或用posix_memalign+__builtin_arm_mte_enable) - 栈上变量默认不启用 MTE(需编译器支持
-march=armv8.5-a+memtag并开启-fsanitize=memtag) - 全局变量、静态变量无法打标(链接时已固定地址,MTE 要求运行时动态 tag 管理)
怎么让 new/delete 支持 MTE 标记
标准 new 不会自动打标,必须拦截并注入 tag 操作。Clang 14+ 提供 -fsanitize=memtag,它会重写 operator new 和 operator delete,在分配后调用 __builtin_arm_mte_set_tag,并在释放前校验 tag。但注意:该模式要求整个程序用 Clang 编译,且所有依赖库(包括 libc++)也得用相同选项构建,否则 std::vector 内部 malloc 可能绕过检查。
更可控的做法是自定义分配器:
立即学习“C++免费学习笔记(深入)”;
void* operator new(size_t n) {
void* p = malloc(n);
if (p) {
// 对齐到 16 字节起点,生成随机 tag
void* aligned = (char*)p - ((uintptr_t)p & 0xf);
__builtin_arm_mte_set_tag(aligned, n);
}
return p;
}
- 务必对齐:MTE 要求 tag 作用于 16 字节块起始地址,
(char*)p - ((uintptr_t)p & 0xf)是安全对齐方式 - 别用
__builtin_arm_mte_create_tag(p, 0)直接包装原始指针——它不保证对齐,可能跨块污染相邻 tag -
operator delete必须先调用__builtin_arm_mte_check_tag,再free,否则释放未校验的指针会漏掉 use-after-free
为什么开了 MTE 还收不到 SIGSEGV
最常见原因是没启用 MTE 控制寄存器。ARM 上 MTE 默认关闭,需在进程启动早期执行 __builtin_arm_mte_enable(对应写 TCO 位和 TCR_EL1.TBI0),否则所有 tag 比对被忽略。Clang 的 -fsanitize=memtag 会在 _start 后自动调用,但手写代码或混合编译时容易遗漏。
- 检查是否启用:
cat /proc/self/status | grep Mte—— 应显示Mte: enabled - 内核需开启 CONFIG_ARM64_MTE,且启动参数含
arm64.memtag=on - QEMU 模拟需加
-cpu cortex-a76,mem-tag=on,否则即使代码正确也静默失败 - Android 上需 target SDK ≥ 31,且
android:debuggable="true",否则 SELinux 会禁用 MTE 系统调用
ASan 和 MTE 能一起用吗
可以,但不推荐同时启用。ASan 插桩会大幅拖慢速度(10–20×),而 MTE 的价值恰恰在于轻量级线上检测;两者原理不同:ASan 捕获所有越界(包括非 16 字节对齐),MTE 只捕获 tag 不匹配的访存(对齐要求严,但能抓到 ASan 漏掉的某些栈溢出场景)。
实际建议分阶段使用:
- 开发期用 ASan 全覆盖(含未对齐访问、释放后重用等)
- 测试/预发环境切 MTE(
-fsanitize=memtag -march=armv8.5-a+memtag),验证高频路径稳定性 - 生产环境若 CPU 支持,可保留 MTE(开销小),但需确保信号处理不干扰业务——
SIGSEGVhandler 里别调malloc或锁,否则可能死锁
MTE 不是银弹:它不报告越界多少字节,也不区分读写;错误现场的寄存器里 far_el1 地址是触发异常的虚拟地址,但 tag 值已丢失,得靠 core dump 或 /proc/PID/maps 结合地址反推哪块内存被误用。



















