LLVM 是构建 Android 所使用的编译器基础架构,包含多个用于执行静态和动态分析的组件。在分析 Android 时,得到广泛使用的一组组件是擦除器,特别是 AddressSanitizer、UndefinedBehaviorSanitizer 和 SanitizerCoverage。
这些擦除器是包含在 compiler-rt 中的基于编译器的仪器测试组件,可在开发和测试过程中用于消除错误和优化 Android。Android 中当前提供的这些擦除器可以发现和诊断多种内存滥用错误和未定义的行为,还可以提供代码覆盖指标,确保您的测试套件尽可能面面俱到。
本文将详细介绍现有 Android 擦除器(AddressSanitizer、UndefinedBehaviorSanitizer 和 SanitizerCoverage)的内部结构,并演示可以如何在 Android 构建系统中使用它们。
Address Sanitizer
AddressSanitizer (ASan) 是一项基于编译器的仪器测试功能,可在运行时检测 C/C++ 代码中的多种内存错误。在 Android 中,已经测试了对下列内存错误类型的检查功能:
- 堆、堆叠和全局变量的越界访问
- 释放后使用
- 返回后使用(运行时标志 ASAN_OPTIONS=detect_stack_use_after_return=1)
- 范围后使用(clang 标志 -fsanitize-address-use-after-scope)
- 重复释放,无效释放
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 上的各种模糊测试工作。
UBSan
UndefinedBehaviorSanitizer (UBSan) 执行编译时仪器测试,检查各种未定义的行为。设备制造商可通过将 LOCAL_SANITIZE:=default-ub 包含到生成文件或将 default-ub: true 包含到 blueprint 文件的 sanitize 块中,将 UBSan 加入其测试构建中。UBSan 可以检测多种未定义的行为,而 Android 的构建系统直接支持:
- bool
- integer-divide-by-zero
- return
- returns-nonnull-attribute
- shift-exponent
- unreachable
- vla-bound
Android 的构建系统还使用了 UBSan 的整数溢出检查功能。UBSan 还支持 unsigned-integer-overflow,这不是严格意义上的未定义行为,但它包含在擦除器中。在生成文件中,可以将 LOCAL_SANITIZE 设置为 signed-integer-overflow、unsigned-integer-overflow 或 combination flag integer,启用 signed-integer-overflow、unsigned-integer-overflow、integer-divide-by-zero、shift-base 和 shift-exponent,以启用这些行为。在 blueprint 文件中,可以将 Misc_undefined 设置为所需的标志,启用这些行为。这些 UBSan 目标,尤其是 unsigned-integer-overflow,广泛用于 mediaserver 组件中,以用来消除任何潜在的整数溢出漏洞。
在 Android 中,当出现未定义的行为时,默认的做法是中止程序。但是,从 2016 年 10 月开始,Android 中的 UBSan 将提供一个可选的运行时库,其报告的错误信息将更加详细,包括出现的未定义行为类型、文件和源代码行信息。
在 Android.mk 文件中,可通过以下方式启用该库:
- LOCAL_SANITIZE:=unsigned-integer-overflow signed-integer-overflow
- LOCAL_SANITIZE_DIAG:=unsigned-integer-overflow signed-integer-overflow
而在 Android.bp 文件中,可通过以下方式启用该库:
- sanitize: {
- misc_undefined: [
- "unsigned-integer-overflow",
- "signed-integer-overflow",
- ],
- diag: {
- misc_undefined: [
- "unsigned-integer-overflow",
- "signed-integer-overflow",
- ],
- },
- },
下面是 UBSan 运行时库提供的信息示例:
- external/icu/icu4c/source/common/ucnv.c:1193:23: runtime error: unsigned integer overflow: 4291925010 + 2147483647 cannot be represented in type 'unsigned int'
- external/icu/icu4c/source/common/cstring.c:288:16: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'uint32_t' (aka 'unsigned int')
- external/harfbuzz_ng/src/hb-private.hh:894:16: runtime error: unsigned integer overflow: 72 - 55296 cannot be represented in type 'unsigned int'
- external/harfbuzz_ng/src/hb-set-private.hh:82:24: runtime error: unsigned integer overflow: 32 - 562949953421312 cannot be represented in type 'unsigned long'
- system/keymaster/authorization_set.cpp:500:37: runtime error: unsigned integer overflow: 6843601868186924302 * 24 cannot be represented in type 'unsigned long'
SanitizerCoverage
Sanitizer 工具内置一个非常简单的代码覆盖工具。SanitizerCoverage 可实现调用级、基本块级和边缘级的代码覆盖。此 Sanitizer 可用作独立的仪器测试工具,也可与其他任何擦除器配合使用,包括 AddressSanitizer 和 UndefinedBehaviorSanitizer 等。要使用这一基于 Guard 的新覆盖工具,请设置 fsanitize-coverage=trace-pc-guard。编译器将在每个边缘插入 __sanitizer_cov_trace_pc_guard(&guard_variable)。每个边缘均有各自的 uint32_t guard_variable。
此外,还会生成模块构造函数,即 __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop)。所有 __sanitizer_cov_ 函数均应由用户提供。您可以按照使用 Guard 跟踪计算机中的示例操作:
https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards
除了跟踪控制流外,SanitizerCoverage 还可以跟踪数据流。此功能可通过 fsanitize-coverage=trace-cmp 激活,并通过 __sanitizer_cov_trace_* 函数检测所有开关和比较指令来实现。对于整数除法和 GEP 指令,也存在类似的功能,可分别通过 fsanitize-coverage=trace-div 和 fsanitize-coverage=trace-gep 激活。这是一个实验性界面,可能危及线程安全,可能随时更改,但在 Android 构建中提供并可运行此界面。
在擦除器覆盖会话期间,覆盖信息记录在两个文件中,即 .sancov 文件和 sancov.map 文件。前一个文件包含程序的所有仪器测试点,而后一个文件包含在前一个文件中用一系列索引表示的执行跟踪。默认情况下,这两个文件存储在当前工作目录中,系统将为执行过程中运行的每个可执行的共享对象创建一个目录。
结论
ASan、UBSan 和 SanitizerCoverage 仅仅是 LLVM 擦除器在 Android 中应用的开端。目前,正在向 Android 构建系统中集成更多的 LLVM 擦除器。本文所介绍的擦除器可用作代码运行状况和系统稳定性检测工具,Android 安全团队甚至正在用它们来发现和预防安全错误!
【本文是51CTO专栏机构“谷歌开发者”的原创稿件,转载请联系原作者(微信公众号:Google_Developers)】