代码 9 (SEGV_MTESERR) 或代码 8 (SEGV_MTEAERR) 的 SIGSEGV 崩溃是内存标记故障。内存标记扩展 (MTE) 是 Android 12 及更高版本中支持的 Armv9 功能。MTE 是标记内存的硬件实现。它为检测和缓解内存安全漏洞提供细粒度的内存保护。
在 C/C++ 中,从调用 malloc() 或运算符 new() 或类似函数返回的指针只能用于访问该分配边界内的内存,并且只能在该分配处于活动状态时(未释放或删除)。MTE 在 Android 中用于检测违反此规则的行为,在崩溃报告中称为“缓冲区溢出”/“缓冲区下溢”和“释放后使用”问题。
MTE 有两种模式:同步(或“sync”)和异步(或“async”)。前者运行速度较慢,但提供更准确的诊断。后者运行速度更快,但只能提供近似的详细信息。我们将分别介绍这两种模式,因为诊断结果略有不同。
同步模式 MTE
在 MTE 的同步(“sync”)模式下,SIGSEGV 崩溃的代码为 9 (SEGV_MTESERR)。
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)
所有 MTE 崩溃报告都包含检测到问题时的常用寄存器转储和回溯。“原因:”行(对于 MTE 检测到的错误)将包含“[MTE]”(如以上示例所示)以及更多详细信息。在本例中,检测到的具体错误类型是“释放后使用”,而“在地址 0x7ae92853a0 的 32 字节分配中偏移 0 字节”告诉我们分配的大小和地址,以及我们尝试访问的分配偏移量。
MTE 崩溃报告还包含额外的回溯,而不仅仅是检测点的回溯。
“释放后使用”错误会在崩溃转储中添加“由...取消分配”和“由...分配”部分,显示此内存被取消分配时(在使用之前!)以及之前分配时的堆栈跟踪。这些信息还会告诉您哪个线程执行了分配/取消分配。在此简单示例中,检测线程、分配线程和取消分配线程是相同的,但在更复杂的实际案例中,情况并非一定如此,知道它们不同可能是查找与并发相关的错误的重要线索。
“缓冲区溢出”和“缓冲区下溢”错误仅提供额外的“由...分配”堆栈跟踪,因为根据定义,它们尚未被取消分配(否则它们将显示为“释放后使用”)
Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0 [...] backtrace: [...] allocated by thread 13949:
请注意此处“右侧”一词的用法:这意味着我们正在告诉您不正确的访问超过分配末尾多少字节;下溢将显示“左侧”,并且是不正确的访问在分配开始之前的字节数。
多个潜在原因
有时,SEGV_MTESERR 报告包含以下行
Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.
当有多个可能导致错误的原因,而我们无法判断哪个是真正的原因时,就会发生这种情况。我们最多打印 3 个此类可能原因(按可能性的大致顺序排列),并将分析留给用户。
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x400007b43063db5 backtrace: [stack...] Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability. Cause: [MTE]: Use After Free, 5 bytes into a 10-byte allocation at 0x7b43063db0 deallocated by thread 6663: [stack...] allocated by thread 6663: [stack...] Cause: [MTE]: Use After Free, 5 bytes into a 6-byte allocation at 0x7b43063db0 deallocated by thread 6663: [stack...] allocated by thread 6663: [stack...]
在上面的示例中,我们检测到在同一内存地址处最近的两次分配,这两次分配都可能是无效内存访问的预期目标。当分配重用空闲内存时,可能会发生这种情况 - 例如,如果您有诸如 new、free、new、free、new、free、access 之类的序列。最近的分配会先打印出来。
详细的原因确定启发法
崩溃的“原因”应显示访问指针最初来源于哪个内存分配。遗憾的是,MTE 硬件无法从标记不匹配的指针转换为分配。为了解释 SEGV_MTESERR 崩溃,Android 会分析以下数据
- 故障地址(包括指针标记)。
- 最近堆分配的列表,其中包含堆栈跟踪和内存标记。
- 附近的当前(活动)分配及其内存标记。
故障地址处任何最近取消分配的内存(其中内存标记与故障地址标记匹配)都是潜在的“释放后使用”原因。
任何附近的活动内存(其中内存标记与故障地址标记匹配)都是潜在的“缓冲区溢出”(或“缓冲区下溢”)原因。
在时间或空间上更接近故障的分配被认为比远离故障的分配更可能。
由于取消分配的内存经常被重用,并且不同标记值的数量很少(少于 16 个),因此找到几个可能的候选原因并不罕见,并且无法自动找到真正的原因。这就是为什么有时 MTE 报告列出多个潜在原因的原因。
建议应用开发者从最有可能的原因开始查看潜在原因。根据堆栈跟踪,通常很容易排除不相关的原因。
异步模式 MTE
在 MTE 的异步(“async”)模式下,SIGSEGV 崩溃的代码为 8 (SEGV_MTEAERR)。
当程序执行无效的内存访问时,SEGV_MTEAERR 故障不会立即发生。问题会在事件发生后不久被检测到,程序会在那时终止。此点通常是下一个系统调用,但也可能是计时器中断 - 简而言之,任何用户空间到内核的转换。
SEGV_MTEAERR 故障不会保留内存地址(始终显示为“-------”)。回溯对应于检测到条件的时刻(即,在下一个系统调用或其他上下文切换时),而不是执行无效访问时。
这意味着异步 MTE 崩溃中的“主要”回溯通常不相关。因此,异步模式故障比同步模式故障更难调试。最好将它们理解为显示给定线程中附近代码中存在内存错误。tombstone 文件底部的日志可能会提供有关实际发生情况的提示。否则,建议的操作是在同步模式下重现错误,并使用同步模式提供的更好的诊断信息!
高级主题
在底层,内存标记的工作原理是为每个堆分配分配一个随机的 4 位(0..15)标记值。此值存储在与分配的堆内存对应的特殊元数据区域中。相同的值分配给从 malloc() 或运算符 new() 等函数返回的堆指针的最高有效字节。
当进程中启用标记检查时,CPU 会自动将指针的最高字节与每次内存访问的内存标记进行比较。如果标记不匹配,CPU 会发出错误信号,从而导致崩溃。
由于可能的标记值数量有限,因此这种方法是概率性的。任何不应使用给定指针访问的内存位置(例如,超出边界或在取消分配后(“悬空指针”))都可能具有不同的标记值并导致崩溃。对于任何一次错误发生,都有约 7% 的几率无法检测到。由于标记值是随机分配的,因此下次发生错误时,独立检测到该错误的几率约为 93%。
标记值可以在故障地址字段以及寄存器转储中看到,如下突出显示所示。此部分可用于检查标记是否以合理的方式设置,以及查看具有与错误报告中列出的原因以外的潜在原因相同的标记值的其他附近内存分配。我们希望这主要对从事 MTE 本身或其他低级系统组件实施工作的人员有用,而不是对开发者有用。
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0800007ae92853a0 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
崩溃报告中还会出现一个特殊的“内存标记”部分,其中显示故障地址周围的内存标记。在下面的示例中,指针标记“4”与内存标记“a”不匹配。
Memory tags around the fault address (0x0400007b43063db5), one tag per 16 bytes: 0x7b43063500: 0 f 0 2 0 f 0 a 0 7 0 8 0 7 0 e 0x7b43063600: 0 9 0 8 0 5 0 e 0 f 0 c 0 f 0 4 0x7b43063700: 0 b 0 c 0 b 0 2 0 1 0 4 0 7 0 8 0x7b43063800: 0 b 0 c 0 3 0 a 0 3 0 6 0 b 0 a 0x7b43063900: 0 3 0 4 0 f 0 c 0 3 0 e 0 0 0 c 0x7b43063a00: 0 3 0 2 0 1 0 8 0 9 0 4 0 3 0 4 0x7b43063b00: 0 5 0 2 0 5 0 a 0 d 0 6 0 d 0 2 0x7b43063c00: 0 3 0 e 0 f 0 a 0 0 0 0 0 0 0 4 =>0x7b43063d00: 0 0 0 a 0 0 0 e 0 d 0 [a] 0 f 0 e 0x7b43063e00: 0 7 0 c 0 9 0 a 0 d 0 2 0 0 0 c 0x7b43063f00: 0 0 0 6 0 b 0 8 0 3 0 0 0 5 0 e 0x7b43064000: 0 d 0 2 0 7 0 a 0 7 0 a 0 d 0 8 0x7b43064100: 0 b 0 2 0 b 0 4 0 1 0 6 0 d 0 4 0x7b43064200: 0 1 0 6 0 f 0 2 0 f 0 6 0 5 0 c 0x7b43064300: 0 1 0 4 0 d 0 6 0 f 0 e 0 1 0 8 0x7b43064400: 0 f 0 4 0 3 0 2 0 1 0 2 0 5 0 6
tombstone 文件中显示所有寄存器值周围内存内容的部分也会显示其标记值。
memory near x10 ([anon:scudo:primary]): 0000007b4304a000 7e82000000008101 000003e9ce8b53a0 .......~.S...... 0700007b4304a010 0000200000006001 0000000000000000 .`... .......... 0000007b4304a020 7c03000000010101 000003e97c61071e .......|..a|.... 0200007b4304a030 0c00007b4304a270 0000007ddc4fedf8 p..C{.....O.}... 0000007b4304a040 84e6000000008101 000003e906f7a9da ................ 0300007b4304a050 ffffffff00000042 0000000000000000 B............... 0000007b4304a060 8667000000010101 000003e9ea858f9e ......g......... 0400007b4304a070 0000000100000001 0000000200000002 ................ 0000007b4304a080 f5f8000000010101 000003e98a13108b ................ 0300007b4304a090 0000007dd327c420 0600007b4304a2b0 .'.}......C{... 0000007b4304a0a0 88ca000000010101 000003e93e5e5ac5 .........Z^>.... 0a00007b4304a0b0 0000007dcc4bc500 0300007b7304cb10 ..K.}......s{... 0000007b4304a0c0 0f9c000000010101 000003e9e1602280 ........."`..... 0900007b4304a0d0 0000007dd327c780 0700007b7304e2d0 ..'.}......s{... 0000007b4304a0e0 0d1d000000008101 000003e906083603 .........6...... 0a00007b4304a0f0 0000007dd327c3b8 0000000000000000 ..'.}...........