AddressSanitizer (ASan) 是一项基于编译器的仪器测试功能,可在运行时检测 C/C++ 代码中的多种内存错误。在 Android 中,已经测试了对下列内存错误类型的检查功能:
Android 可通过 ASan 执行全面的构建仪器测试,还可以通过 asanwrapper 执行应用级的 ASan 仪器测试。关于这两种仪器测试技巧的说明均可在 source.android.com 中找到。
AddressSanitizer 基于以下两个高级概念。第一个概念是针对与内存有关的所有函数调用(包括 alloca、malloc 和 free 等)执行仪器测试并输出用于跟踪内存分配、释放和使用情况统计的信息。通过此仪器测试,ASan 可检测无效的内存使用错误,包括重复释放、范围后使用、返回后使用和释放后使用等错误。ASan 还可以检测在定义的内存区域边界外发生的读写操作。为完成此检测,它填充所有分配的内存缓冲区和变量。如果对此填充区域进行读或写,ASan 将捕获此操作,并输出有助于诊断内存违例的信息。在 ASan 术语中,此填充被称为中毒内存。
下面是包含堆叠分配变量的中毒内存填充布局示例:
▲ ASANified 堆叠变量示例,此变量包含一个由 8 个元素组成的 int8_t 数组、一个 uint32_t 数组和一个由 16 个元素组成的 int8_t 数组。右侧显示使用 ASAN 编译后的内存布局,其中每个变量之间插入填充。对于每个堆栈变量,变量前后有 32 个填充字节。如果一个变量的对象大小不是 32 个字节,则插入 32 - n 个额外的填充字节,其中 n 是对象大小。
ASan 使用影子内存跟踪哪些字节为正常内存,哪些字节为中毒内存。字节可以标记为完全正常(在影子内存中标记为 0)、完全中毒(设置对应影子字节的高位)或前面 k 个字节未中毒(影子字节值为 k)。如果影子内存显示某个字节中毒,则 ASan 会使程序崩溃,并输出有用的调试信息,包括调用堆栈、影子内存映射、内存违例类型、读取或写入的内容、导致违例的计算机以及内存内容。
AddressSanitizer: heap-buffer-overflow on address 0xe6146cf3 at pc 0xe86eeb3c bp 0xffe67348 sp 0xffe66f14
WRITE of size 39 at 0xe6146cf3 thread T0
#0 0xe86eeb3b (/system/lib/libclang_rt.asan-arm-android.so+0x64b3b)
#1 0xaddc5d27 (/data/simple_test_fuzzer+0x4d27)
#2 0xaddd08b9 (/data/simple_test_fuzzer+0xf8b9)
#3 0xaddd0a97 (/data/simple_test_fuzzer+0xfa97)
#4 0xaddd0fbb (/data/simple_test_fuzzer+0xffbb)
#5 0xaddd109f (/data/simple_test_fuzzer+0x1009f)
#6 0xaddcbfb9 (/data/simple_test_fuzzer+0xafb9)
#7 0xaddc9ceb (/data/simple_test_fuzzer+0x8ceb)
#8 0xe8655635 (/system/lib/libc.so+0x7a635)
0xe6146cf3 is located 0 bytes to the right of 35-byte region [0xe6146cd0,0xe6146cf3)
allocated by thread T0 here:
#0 0xe87159df (/system/lib/libclang_rt.asan-arm-android.so+0x8b9df)
#1 0xaddc5ca7 (/data/simple_test_fuzzer+0x4ca7)
#2 0xaddd08b9 (/data/simple_test_fuzzer+0xf8b9)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/system/lib/libclang_rt.asan-arm-android.so+0x64b3b)
Shadow bytes around the buggy address:
0x1cc28d40: fa fa 00 00 00 00 07 fa fa fa fd fd fd fd fd fd
0x1cc28d50: fa fa 00 00 00 00 07 fa fa fa fd fd fd fd fd fd
0x1cc28d60: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd
0x1cc28d70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1cc28d80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x1cc28d90: fa fa fa fa fa fa fa fa fa fa 00 00 00 00[03]fa
0x1cc28da0: fa fa 00 00 00 00 07 fa fa fa 00 00 00 00 03 fa
0x1cc28db0: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
0x1cc28dc0: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd
0x1cc28dd0: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd
0x1cc28de0: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
有关报告各个部分的含义以及如何提高其易读性的更多信息,可查看 LLVM 网站:
https://clang.llvm.org/docs/AddressSanitizer.html
和 Github:
https://github.com/google/sanitizers/wiki/AddressSanitizer
有时,错误发现过程可能无法确定问题所在,当错误需要特殊设置或更高级的技巧(例如堆填充或利用争用条件)才能发现时,更是如此。其中许多错误并不能即时发现,可能需要检查数千条指令才能找到内存违例的真正原因所在。ASan 可针对所有与内存有关的函数执行仪器测试并为必须触发 ASan 相关回调才可访问的区域填充数据,可在发生内存违例时立即捕获违例,而不是等待崩溃导致数据损坏。这对于错误发现和根源诊断极为有用。此外,ASAN 还是一个非常有用的模糊测试工具,一直用于 Android 上的各种模糊测试工作。