@[TOC](Android NDK开发详解调试和减少内存错误之Arm Memory Tagging Extension (MTE))
注意:本文档介绍了如何在 MTE 下运行 Android 应用。如需了解如何使用 MTE 进行 Android 平台开发,请参阅 AOSP 文档。
重要提示:MTE 是用于调试和减少内存错误的众多工具之一。如需简要了解所有工具,请参阅调试和减少内存错误。
为何选择 MTE?
内存安全 bug 是指以原生编程语言处理内存时遇到的错误,是常见的代码问题。这类问题会导致安全漏洞和稳定性问题。
Armv9 引入了 Arm Memory Tagging Extension (MTE),一种可让您捕获原生代码中的释放后使用和缓冲区溢出 bug 的硬件扩展。
检查支持情况
从 Android 13 开始,部分设备支持 MTE。如需检查设备是否在运行时启用了 MTE,请运行以下命令:
adb shell grep mte /proc/cpuinfo
如果结果为 Features : […] mte,则表示设备在运行时启用了 MTE。
有些设备默认不启用 MTE,但允许开发者在启用 MTE 的情况下重新启动。这是一种不建议用于常规用途的实验性配置,因为它可能会降低设备性能或稳定性,但对于应用开发很有用。如需访问此模式,请在“设置”应用中依次前往开发者选项 >“Memory Tagging Extension”。如果系统未列出此选项,则表示您的设备不支持以这种方式启用 MTE。
如果您有意获取支持 MTE 的设备,请填写 MTE Beta 版设备注册表单,我们会与您联系。
MTE 操作模式
MTE 支持两种模式:SYNC 和 ASYNC。SYNC 模式可提供更可靠的诊断信息,因此更适用于开发目的,而 ASYNC 模式的高性能使其可以用于已发布的应用。
同步模式 (SYNC)
此模式针对可调试性而非性能进行了优化,可以在可接受较高性能开销的情况下用作精确的 bug 检测工具。启用后,MTE SYNC 可以作为有效的安全缓解措施。
如果标记不匹配,处理器会在收到违规的加载或存储指令时终止进程,并返回 SIGSEGV(si_code 为 SEGV_MTESERR)以及有关内存访问和故障地址的完整信息。
此模式适合在测试期间用作 HWASan 的更快替代方案,此时您无需重新编译代码;或当您的应用出现存在漏洞的受攻击面时在生产环境中使用。此外,当 ASYNC 模式(详见下文)发现 bug 时,可以通过使用运行时 API 将执行切换到 SYNC 模式,来获取准确的 bug 报告。
此外,在 SYNC 模式下运行时,Android 分配器会记录每次分配和取消分配的堆栈轨迹,并利用这些信息提供更好的错误报告,其中包含对内存错误的解释(例如释放后使用或缓冲区溢出),以及相关内存事件的堆栈轨迹(如需了解详情,请参阅了解 MTE 报告)。与 ASYNC 模式相比,此类报告可提供更多上下文信息,并简化 bug 跟踪和修复。
异步模式 (ASYNC)
此模式专门针对 bug 报告的性能(而非精确度)进行了优化,可用于对内存安全 bug 进行低开销检测。如果标记不匹配,处理器会继续执行,直到达到最近的内核条目(例如,系统调用或计时器中断)。这时,处理器会终止进程并返回 SIGSEGV(代码为 SEGV_MTEAERR),而不会记录错误地址或内存访问。
此模式适合在生产环境中对经过严格测试的代码库(已知其内存安全 bug 的密度较低)降低内存安全漏洞,这将通过在测试期间使用 SYNC 模式来实现。
启用 MTE
对于单台设备
对于实验,应用兼容性更改可用于为未在清单文件中指定任何值(或指定 “default”)的应用设置 memtagMode 属性的默认值。
您可以在全局设置菜单中的“系统”>“高级”>“开发者选项”>“应用兼容性变更”下找到相关更改。设置 NATIVE_MEMTAG_ASYNC 或 NATIVE_MEMTAG_SYNC 可为特定应用启用 MTE。
或者,也可以使用 am 命令进行设置,如下所示:
对于 SYNC 模式:$ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
对于 ASYNC 模式:$ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name
对于任何兼容设备上的 APK
MTE 默认处于停用状态。如果要在应用中使用 MTE,请在 AndroidManifest.xml 中的 或 标记下设置 android:memtagMode。
注意:在面向 Play 商店上架之前,应先在支持 MTE 的设备上对启用 MTE 的应用进行全面测试。否则可能会导致系统只能在支持 MTE 的设备上发现严重 bug,这可能会降低应用的易用性。
android:memtagMode=(off|default|sync|async)
当为 标记设置了该属性时,该属性会影响应用使用的所有进程,并且可以通过设置 标记来为各个进程替换该属性。
运行应用
启用 MTE 后,请照常使用和测试应用。如果检测到内存安全问题,应用将会崩溃,并提供类似于以下内容的 Tombstone(请注意,SYNC 模式下,返回的 SIGSEGV 代码为 SEGV_MTESERR;ASYNC 模式下,代码为 SEGV_MTEAERR):
pid: 13935, tid: 13935, name: sanitizer-statu >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0 0000007cd94227cc x1 0000007cd94227cc x2 ffffffffffffffd0 x3 0000007fe81919c0
x4 0000007fe8191a10 x5 0000000000000004 x6 0000005400000051 x7 0000008700000021
x8 0800007ae92853a0 x9 0000000000000000 x10 0000007ae9285000 x11 0000000000000030
x12 000000000000000d x13 0000007cd941c858 x14 0000000000000054 x15 0000000000000000
x16 0000007cd940c0c8 x17 0000007cd93a1030 x18 0000007cdcac6000 x19 0000007fe8191c78
x20 0000005800eee5c4 x21 0000007fe8191c90 x22 0000000000000002 x23 0000000000000000
x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000
x28 0000000000000000 x29 0000007fe8191b70
lr 0000005800eee0bc sp 0000007fe8191b60 pc 0000005800eee0c0 pst 0000000060001000
backtrace:
#00 pc 00000000000010c0 /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#01 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#02 pc 00000000000019cc /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000487d8 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
deallocated by thread 13935:
#00 pc 000000000004643c /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 00000000000421e4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 00000000000010b8 /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
allocated by thread 13935:
#00 pc 0000000000042020 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 0000000000042394 /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 000000000003cc9c /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#03 pc 00000000000010ac /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#04 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
Learn more about MTE reports: https://source.android.com/docs/security/test/memory-safety/mte-report
如需了解详情,请参阅 AOSP 文档中的了解 MTE 报告。您还可以使用 Android Studio 调试应用,调试程序会在导致内存访问无效的行停止。
高级用户:在您自己的分配器中使用 MTE
注意:如果您不知道自己正在使用自定义分配器,则可以跳过本部分。
如要对并非通过常规系统分配器分配的内存使用 MTE,您需要修改分配器以标记内存和指针。
您需要通过 mmap(或 mprotect)的 prot 标志中的 PROT_MTE 来分配分配器的页面。
所有标记的分配都需要 16 字节对齐,因为标记只能分配给 16 字节的区块(也称为 granule)。
然后,在返回指针之前,您需要使用 IRG 指令生成随机标记并将其存储在指针中。
请按照以下说明标记底层内存:
STG:标记单个 16 字节的 granule
ST2G:标记两个 16 字节的 granule
DC GVA:使用相同的标签标记缓存行
或者,以下说明还对内存进行零初始化:
STZG:标记和零初始化单个 16 字节的 granule
STZ2G:标记和零初始化两个 16 字节的 granule
DC GZVA:使用相同的标签标记和零初始化缓存行
请注意,较旧的 CPU 不支持这些指令,因此启用 MTE 后,您需要有条件地运行这些指令。您可以检查进程是否启用了 MTE:
#include <sys/prctl.h>
bool runningWithMte() {
int mode = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
return mode != -1 && mode & PR_MTE_TCF_MASK;
}
您可以参阅 scudo 实现代码。
了解详情
如需了解详情,请参阅由 Arm 撰写的 Android OS MTE 用户指南。
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2023-11-13。