LLVM 是用于构建 Android 的编译器基础架构,包含可执行静态和动态分析的多个组件。在这些组件中,排错程序(特别是 AddressSanitizer 和 UndefinedBehaviorSanitizer)可以广泛用于分析 Android。排错程序是包含在 external/compiler-rt 中的基于编译器的插桩组件,可用于在开发和测试期间消除错误和改进 Android。Android 目前的排错程序系列可以发现和诊断许多内存滥用 bug 以及可能危险的未定义行为。
Android build 最好在启用排错程序(如 AddressSanitizer 和 UndefinedBehaviorSanitizer)的情况下启动并运行。本页面介绍了 AddressSanitizer、UndefinedBehaviorSanitizer 和 KernelAddressSanitizer,展示了如何在 Android 构建系统中使用它们,并且提供了在启用这些排错程序的情况下构建原生组件的 Android.mk 和 Android.bp 示例文件。
AddressSanitizer
AddressSanitizer (ASan) 是一种基于编译器的插桩功能,可在运行时检测 C/C++ 代码中许多类型的内存错误。ASan 可以检测许多类别的内存错误,包括:
- 出界内存访问
- 双重释放
- 释放后再使用
Android 允许在完整构建级别和应用级别通过 asanwrapper 进行 ASan 插桩。
AddressSanitizer 对所有与内存相关的函数调用(包括 alloca、malloc 和 free)进行插桩,并使用被读取或写入时会触发 ASan 回调的内存填充所有变量和已分配的内存区域。
这种插桩使 ASan 能够检测无效内存使用 bug,包括双重释放以及 UAS (use-after-scope)、UAR (use-after-return) 和 UAF (use-after-free) bug,而内存区域填充会检测出界读取或写入错误。如果出现对此填充区域的读取或写入操作,ASan 会捕获这一情况并输出信息以帮助诊断内存违规行为,包括调用堆栈、影子内存映射、内存违规的类型、读取或写入的内容、导致违规的指令以及内存内容。
pixel-xl:/ # sanitizer-status ================================================================= ==14164==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x0032000054b0 at pc 0x005df16ffc3c bp 0x007fc236fdf0 sp 0x007fc236fdd0 WRITE of size 1 at 0x0032000054b0 thread T0 #0 0x5df16ffc3b in test_crash_malloc sanitizer-status/sanitizer-status.c:36:13 #1 0x5df17004e3 in main sanitizer-status/sanitizer-status.c:76:7 #2 0x794cf665f3 in __libc_init (/system/lib64/libc.so+0x1b5f3) #3 0x5df16ffa53 in do_arm64_start (/system/bin/sanitizer-status+0xa53) 0x0032000054b0 is located 0 bytes to the right of 32-byte region [0x003200005490,0x0032000054b0) allocated by thread T0 here: #0 0x794d0bdc67 in malloc (/system/lib64/libclang_rt.asan-aarch64-android.so+0x74c67) #1 0x5df16ffb47 in test_crash_malloc sanitizer-status/sanitizer-status.c:34:25 #2 0x5df17004e3 in main sanitizer-status/sanitizer-status.c:76:7 #3 0x794cf665f3 in __libc_init (/system/lib64/libc.so+0x1b5f3) #4 0x5df16ffa53 in do_arm64_start (/system/bin/sanitizer-status+0xa53) #5 0x794df78893 (<unknown module>) SUMMARY: AddressSanitizer: heap-buffer-overflow sanitizer-status/sanitizer-status.c:36:13 in test_crash_malloc
有时,bug 发现过程可能看起来具有不确定性,特别是对于需要特殊设置或更先进技术的 bug,例如堆启动或竞态条件利用。其中许多错误并不能即时发现,可能需要检查数千条指令才能找到内存违规的真正原因所在。ASan 对所有与内存相关的函数进行插桩,并为数据填充无法在不触发 ASan 回调的情况下访问的区域。这意味着,内存违规行为一旦出现即被捕获,不用等待出现会导致崩溃的损坏。这在发现 bug 和诊断根本原因过程中非常有用。
为了验证 ASAN 在目标设备上能否正常运行,Android 已经包含了 asan_test 可执行文件。asan_test 可执行文件会在目标设备上测试和验证 ASAN 功能,从而提供包含每个测试的状态的诊断消息。使用 ASAN Android build 时,它默认位于 /data/nativetest/asan_test/asan_test
或 /data/nativetest64/asan_test/asan_test
中。
UndefinedBehaviorSanitizer
UndefinedBehaviorSanitizer (UBSan) 会执行编译时插桩,以检查各种类型的未定义行为。UBSan 能够检测许多未定义行为,而 Android 支持 alignment、bool、bounds、enum、float-cast-overflow、float-divide-by-zero、integer-divide-by-zero、nonnull-attribute、null、return、returns-nonnull-attribute、shift-base、shift-exponent、signed-integer-overflow、unreachable、unsigned-integer-overflow 和 vla-bound。虽然 unsigned-integer-overflow 在技术上不是未定义行为,但其包含在排错程序中,并用在许多 Android 模块(包括 mediaserver 组件)中,以消除任何潜在的整数溢出漏洞。
实现
在 Android 构建系统中,您可以全局或局部地启用 UBSan。如需全局启用 UBSan,请在 Android.mk 中设置 SANITIZE_TARGET。要在每个模块级别启用 UBSan,请在 Android.mk 中设置 LOCAL_SANITIZE 并指定要查找的未定义行为。例如:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_CFLAGS := -std=c11 -Wall -Werror -O0 LOCAL_SRC_FILES:= sanitizer-status.c LOCAL_MODULE:= sanitizer-status LOCAL_SANITIZE := alignment bounds null unreachable integer LOCAL_SANITIZE_DIAG := alignment bounds null unreachable integer include $(BUILD_EXECUTABLE)
Android 构建系统尚不支持像对 Makefile 那样在蓝图文件中进行详细诊断。以下是编写为蓝图 (Android.bp) 的最接近等同项:
cc_binary { cflags: [ "-std=c11", "-Wall", "-Werror", "-O0", ], srcs: ["sanitizer-status.c"], name: "sanitizer-status", sanitize: { misc_undefined: [ "alignment", "bounds", "null", "unreachable", "integer", ], diag: { undefined : true }, }, }
UBSan 快捷方式
Android 还有 integer
和 default-ub
这两种快捷方式,它们可同时启用一组排错程序。integer 会启用 integer-divide-by-zero
、signed-integer-overflow
和 unsigned-integer-overflow
。default-ub
会启用对编译器性能造成很小影响的检查:bool、integer-divide-by-zero、return、returns-nonnull-attribute、shift-exponent、unreachable 和 vla-bound。integer 排错程序类可以与 SANITIZE_TARGET 和 LOCAL_SANITIZE 一起使用,而 default-ub 只能与 SANITIZE_TARGET 一起使用。
更好的错误报告
Android 的默认 UBSan 实现在遇到未定义行为时会调用指定的函数。默认情况下,此函数被中止。但是,从 2016 年 10 月开始,Android 上的 UBSan 有一个可选的运行时库,可以提供更详细的错误报告,包括遇到的未定义行为的类型、文件和源代码行信息。如需对 integer 检查启用此错误报告,请将以下内容添加到 Android.mk 文件中:
LOCAL_SANITIZE:=integer LOCAL_SANITIZE_DIAG:=integer
LOCAL_SANITIZE 值会在构建过程中启用排错程序。LOCAL_SANITIZE_DIAG 用于为指定排错程序开启诊断模式。可以将 LOCAL_SANITIZE 和 LOCAL_SANITIZE_DIAG 设置为不同的值,但系统只启用 LOCAL_SANITIZE 中的检查。如果某个检查未在 LOCAL_SANITIZE 中指定,但在 LOCAL_SANITIZE_DIAG 中指定了,系统不会启用该检查,且不会提供诊断消息。
以下是由 UBSan 运行时库提供的信息示例:
pixel-xl:/ # sanitizer-status ubsan sanitizer-status/sanitizer-status.c:53:6: runtime error: unsigned integer overflow: 18446744073709551615 + 1 cannot be represented in type 'size_t' (aka 'unsigned long')
内核地址排错程序
与针对用户空间组件的基于 LLVM 的排错程序一样,Android 包括内核地址排错程序 (KASAN)。KASAN 是内核与编译时修改的组合,形成了一个插桩系统,可以实现更简单的 bug 发现和根本原因分析。
KASAN 可以检测内核中许多类型的内存违规行为。它还可以检测堆栈、堆和全局变量中的出界读取和写入操作,并可检测释放后再使用和双重释放错误。
与 ASAN 一样,KASAN 将编译时内存函数插桩与影子内存相结合,以便跟踪运行时的内存访问。在 KASAN 中,八分之一的内核内存空间专用于影子内存,以确定内存访问是否有效。
KASAN 在 x86_64 和 arm64 架构中受支持。自 4.0 以来,它一直是上游内核的一部分,并且已经反向移植到基于 Android 3.18 的内核。KASAN 已在基于 4.9.2 通过 gcc 编译的 Android 内核上进行了测试。
除了 KASAN,kcov 是另一个对测试非常有用的内核修改。kcov 旨在允许在内核中进行覆盖率引导模糊测试。它会测量在系统调用输入方面的覆盖率,对于模糊系统(如 syzkaller)非常有用。
实现
如需在启用 KASAN 和 kcov 的情况下编译内核,请将以下 build 标志添加到内核 build 配置:
CONFIG_KASAN CONFIG_KASAN_INLINE CONFIG_TEST_KASAN CONFIG_KCOV CONFIG_SLUB CONFIG_SLUB_DEBUG CONFIG_CC_OPTIMIZE_FOR_SIZE
并移除以下内容:
CONFIG_SLUB_DEBUG_ON CONFIG_SLUB_DEBUG_PANIC_ON CONFIG_KASAN_OUTLINE CONFIG_KERNEL_LZ4
然后照常构建和刷写内核。KASAN 内核比原始内核大得多。考虑到这一点,请修改任何启动参数和引导加载程序设置(如果适用)。
刷写内核后,检查内核启动日志,看看 KASAN 是否已启用并正在运行。内核将启动并显示 KASAN 的内存映射信息,例如:
... [ 0.000000] c0 0 Virtual kernel memory layout: [ 0.000000] c0 0 kasan : 0xffffff8000000000 - 0xffffff9000000000 ( 64 GB) [ 0.000000] c0 0 vmalloc : 0xffffff9000010000 - 0xffffffbdbfff0000 ( 182 GB) [ 0.000000] c0 0 vmemmap : 0xffffffbdc0000000 - 0xffffffbfc0000000 ( 8 GB maximum) [ 0.000000] c0 0 0xffffffbdc0000000 - 0xffffffbdc3f95400 ( 63 MB actual) [ 0.000000] c0 0 PCI I/O : 0xffffffbffa000000 - 0xffffffbffb000000 ( 16 MB) [ 0.000000] c0 0 fixed : 0xffffffbffbdfd000 - 0xffffffbffbdff000 ( 8 KB) [ 0.000000] c0 0 modules : 0xffffffbffc000000 - 0xffffffc000000000 ( 64 MB) [ 0.000000] c0 0 memory : 0xffffffc000000000 - 0xffffffc0fe550000 ( 4069 MB) [ 0.000000] c0 0 .init : 0xffffffc001d33000 - 0xffffffc001dce000 ( 620 KB) [ 0.000000] c0 0 .text : 0xffffffc000080000 - 0xffffffc001d32284 ( 29385 KB) ...
错误将如下所示:
[ 18.539668] c3 1 ================================================================== [ 18.547662] c3 1 BUG: KASAN: null-ptr-deref on address 0000000000000008 [ 18.554689] c3 1 Read of size 8 by task swapper/0/1 [ 18.559988] c3 1 CPU: 3 PID: 1 Comm: swapper/0 Tainted: G W 3.18.24-xxx #1 [ 18.569275] c3 1 Hardware name: Android Device [ 18.577433] c3 1 Call trace: [ 18.580739] c3 1 [<ffffffc00008b32c>] dump_backtrace+0x0/0x2c4 [ 18.586985] c3 1 [<ffffffc00008b600>] show_stack+0x10/0x1c [ 18.592889] c3 1 [<ffffffc001481194>] dump_stack+0x74/0xc8 [ 18.598792] c3 1 [<ffffffc000202ee0>] kasan_report+0x11c/0x4d0 [ 18.605038] c3 1 [<ffffffc00020286c>] __asan_load8+0x20/0x80 [ 18.611115] c3 1 [<ffffffc000bdefe8>] android_verity_ctr+0x8cc/0x1024 [ 18.617976] c3 1 [<ffffffc000bcaa2c>] dm_table_add_target+0x3dc/0x50c [ 18.624832] c3 1 [<ffffffc001bdbe60>] dm_run_setup+0x50c/0x678 [ 18.631082] c3 1 [<ffffffc001bda8c0>] prepare_namespace+0x44/0x1ac [ 18.637676] c3 1 [<ffffffc001bda170>] kernel_init_freeable+0x328/0x364 [ 18.644625] c3 1 [<ffffffc001478e20>] kernel_init+0x10/0xd8 [ 18.650613] c3 1 ==================================================================
此外,如果在内核中启用了模块,则可以加载 test_kasan 内核模块以供进一步测试。该模块会尝试“越界内存访问”和“释放后再使用”,有助于在目标设备上测试 KASAN。