1. AddressSanitizer
1.1 简介
AddressSanitizer (ASan) 是一种基于编译器的快速检测工具,用于检测native代码中的内存错误。
注意:从android11开始,在arm64上弃用了asan,改为使用hwasan.
ASan 可以检测以下问题:
- 堆栈和堆缓冲区上溢/下溢
- 释放之后的堆使用情况
- 超出范围的堆栈使用情况
- 重复释放/错误释放
注:Android上的AddressSanitizer暂不能检测内存泄漏
参考:Android Native内存问题检测
1.2 使用
1.2.1 检查可执行文件中使用
- 在Android.mk中添加:
LOCAL_SANITIZE:=address
- 在Android.bp中添加:
sanitize: { address: true }
检测到错误时,ASan 会向标准输出和 logcat 发送一份详细报告,然后让进程崩溃。
1.2.2 在共享库中使用
- 在Android.mk中添加:
LOCAL_SANITIZE:=address
- 在Android.bp中添加:
sanitize: { address: true }
根据 ASan 的运行原理,只有通过 ASan 构建的可执行文件可以使用通过 ASan 构建的库。
注意:在运行时,如果将 ASan 库加载到错误的进程中,系统将显示以 _asan
或 _sanitizer
开头的消息,提示您有无法解析的符号。
- 特殊情况:
如果多个可执行文件使用了同一个共享库,但其中只有部分可执行文件使用了asan,将需要该库的两个副本,为此,可以在Android.mk中添加以下命令:
LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan
这样一来,系统会将库放到 /system/lib/asan
中而非 /system/lib
中。然后,在使用asan的可执行文件的构建规则中添加下面的命令:LD_LIBRARY_PATH=/system/lib/asan
对于系统守护程序,将以下命令添加到 /init.rc 或 /init.$device$.rc 的相应部分。setenv LD_LIBRARY_PATH /system/lib/asan
警告:
LOCAL_MODULE_RELATIVE_PATH 设置会将您的库移至 /system/lib/asan,这意味着,如果从头开始重写和重新构建,将会导致 /system/lib 中缺少该库,并且生成的映像可能无法启动。很遗憾,这是当前构建系统的一个限制。请不要执行 clobber 命令;而是使用 make -j $N 和 adb sync。
通过读取 /proc/$PID/maps
,验证进程使用的库是否来自 /system/lib/asan(如果此库存在)。如果不是,您可能需要停用 SELinux:
adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID
# if it is a system service, or may be adb shell stop; adb shell start.
1.2.3 在应用中使用asan
ASan 无法检查 Java 代码,但可以检测 JNI 库中的错误。为此,您需要使用 ASan 构建可执行文件(在此情况下是 /system/bin/app_process(32|64))。这将在设备上的所有应用中同时启用 ASan,因而负载非常大,但 2 GB RAM 的设备应该能够从容应对。
将LOCAL_SANITIZE:=address
添加到 frameworks/base/cmds/app_process
中的 app_process
构建规则中。暂时忽略同一个文件中的 app_process__asan
目标(如果在您阅读本文时这个目标仍在其中)。
修改相应 system/core/rootdir/init.zygote(32|64).rc
文件的 service zygote 部分,将以下代码行添加到包含 class main 的缩进行代码块中,且缩进量相同:
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
setenv ASAN_OPTIONS allow_user_segv_handler=true
1.2.4 一次性构建整个android平台
Android 7.0 及更高版本支持使用 ASan 一次性构建整个 Android 平台。(如果您要构建的版本高于 Android 9,那么 HWASan 是更好的选择。)
请在同一构建树中运行以下命令。
make -j42
SANITIZE_TARGET=address make -j42
在此模式下,userdata.img 中包含其他库,必须也刷写到设备上。
这样将构建两组共享库:/system/lib
中的常规库(第一次 make 调用)和 /data/asan/lib
中进行 ASan 插桩的库(第二次 make 调用)。
第二次构建的可执行文件会覆盖第一次构建的可执行文件。
通过使用 PT_INTERP
中的 /system/bin/linker_asan
,进行 ASan 插桩的可执行文件会获得一个不同的库搜索路径,该路径中的 /system/lib
前面添加了 /data/asan/lib
。
$SANITIZE_TARGET
的值变更时,构建系统会重写中间对象目录。(从out/target/../obj
变为out/target/../obj_asan
)这样一来,系统便会强制重新构建所有目标,同时保留 /system/lib
下已安装的二进制文件。
- 不参与构建
有些目标无法使用 ASan 构建:
- 静态关联的可执行文件
- LOCAL_CLANG:=false 目标
- 不会针对 SANITIZE_TARGET=address 进行 ASan 操作的 LOCAL_SANITIZE:=false 目标
在 SANITIZE_TARGET build 中,系统会跳过此类可执行文件,且会将第一次 make 调用中构建的版本留在 /system/bin 中。
1.3 示例
例1:对surfaceflinger使用asan
只修改surfaceflinger Android.bp,在Android.bp中添加如下字段:
sanitize: { address: true }
通过cat /proc/$PID/maps
文件节点查看使用了哪些asan库
例2: 重复释放&堆、栈溢出&内存泄漏
目录文件如下所示
.
├── Android.bp
├── heap_buffer_overflow.c
├── memory_leak.c
├── stack_buffer_overflow.c
└── use_after_free.c
示例代码如下
// use_after_free.c
#include<stdio.h>
#include<stdlib.h>
int main (int argc, char **argv)
{
int count = 10;
int *array = malloc(sizeof(int)*count);
free(array);
for (int i = 0; i < count+10; i++)
{
printf("array[%d]: %d\n",i, array[i]);
}
return 0;
}
// heap_buffer_overflow.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int length = 100;
int *array = (int*)malloc(length*sizeof(int));
int res = array[100];
free(array);
printf("the value of res is %d", res);
return res;
}
// stack_buffer_overflow.c
#include <stdio.h>
int main(void)
{
int array[100];
return array[100];
}
// memory_leak.c
#include <stdio.h>
void *p;
int main()
{
p = malloc(7);
p = 0;
return 0;
}
Android.bp文件:
cc_binary {
name: "heap_buffer_overflow",
vendor: true,
srcs: ["heap_buffer_overflow.c"],
cflags: [
"-Wall",
"-Werror",
"-Wextra",
"-Wno-unused-parameter",
"-O0",
],
sanitize: {
address: true
},
}
cc_binary {
name: "memory_leak",
vendor: true,
srcs: ["memory_leak.c"],
cflags: [
"-Wall",
"-Werror",
"-Wextra",
"-Wno-unused-parameter",
"-O0",
"-fsanitize=address",
"-fno-omit-frame-pointer",
],
sanitize: {
address: true
},
}
cc_binary {
name: "stack_buffer_overflow",
vendor: true,
srcs: ["stack_buffer_overflow.c"],
cflags: [
"-Wall",
"-Werror",
"-Wextra",
"-Wno-unused-parameter",
"-Wno-array-bounds",
"-O0",
],
sanitize: {
address: true
},
}
cc_binary {
name: "use_after_free",
vendor: true,
srcs: ["use_after_free.c"],
cflags: [
"-Wall",
"-Werror",
"-Wextra",
"-Wno-unused-parameter",
"-O0",
],
sanitize: {
address: true
},
}
注:在未打开-O0
(即关闭优化),使用默认优化编译时,上述use_after_free可以检测到,heap_buffer_overflow和stack_buffer_overflow均无法检测到。
只有关闭默认优化,才可以检测到堆、栈溢出错误。
开发板测试结果
- 释放后再使用
- 堆溢出
- 栈溢出
- 内存泄漏
如前所述,该内存泄漏的例子在Android11上并没有检测到
2. UndefinedBehaviorSanitizer
2.1 简介
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。
2.2 使用
设备制造商可通过将 LOCAL_SANITIZE:=default-ub 包含到生成文件或将 default-ub: true 包含到 blueprint 文件的 sanitize 块中,将 UBSan 加入其测试构建中。
2.2.1 全局使用
如需全局启用 UBSan,请在 Android.mk 中设置 SANITIZE_TARGET。
2.2.2 局部使用
- Android.mk中添加:
请在 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)
LOCAL_SANITIZE 接受以英文逗号分隔的排错程序列表,其中 integer_overflow 是一组预封装的选项,用于各个有符号和无符号整数溢出排错程序,且带有一个默认黑名单
LOCAL_SANITIZE_DIAG 用于为排错程序启用诊断模式。请仅在测试期间使用诊断模式,因为它不会在溢出发生时中止,而这会完全抹杀缓解功能的安全优势。
- 在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')
2.3 示例
.
├── Android.bp
└── ubsan_test.c
// Ubsan_test.c
int main(int argc, char**argv)
{
int k = 0x7fffffff;
k += argc;
return 0;
}
Android.bp文件内容如下:
cc_binary {
cflags: [
"-std=c11",
"-Wall",
"-O0",
],
srcs: ["ubsan_test.c"],
name: "ubsan_test",
sanitize: {
misc_undefined: [
"alignment",
"bounds",
"null",
"unreachable",
"integer",
],
diag: {
undefined : true
},
},
}
测试结果如下:
3. BoundSan
3.1 简介
BoundsSanitizer (BoundSan) adds instrumentation to binaries to insert bounds checks around array accesses. These checks are added if the compiler cannot prove at compile time that the access will be safe and if the size of the array will be known at runtime, so that it can be checked against. Android 10 deploys BoundSan in Bluetooth and codecs. BoundSan is provided by the compiler and is enabled by default in various components throughout the platform.
3.2 使用
BoundSan uses UBSan's bounds sanitizer. This mitigation is enabled on a per-module level. It helps keep critical components of Android secure and shouldn't be disabled.
- 在Android.bp中添加
将 "bounds" 添加到二进制文件和库模块的 misc_undefined sanitize 属性:
sanitize: {
misc_undefined: ["bounds"],
diag: {
misc_undefined: ["bounds"],
},
blacklist: "modulename_blacklist.txt",
diag属性的解释:
The diag property enables diagnostics mode for the sanitizers. Use diagnostics mode only during testing. Diagnostics mode doesn't abort on overflows, which negates the security advantage of the mitigation and carries a higher performance overhead, so it's not recommended for production builds.
Diag属性使能sanitizers的诊断模式。只能在测试时使用诊断模式。诊断模式不会在溢出发生时中止,导致该功能的安全优势不能体现且增加性能开销,因此不建议在正式版本中使用。
blacklist:
The blacklist property allows the specification of a blacklist file that developers can use to prevent functions and source files from being sanitized. Use this property only if performance is a concern and the targeted files/functions contribute substantially. Manually audit these files/functions to ensure that array accesses are safe. See Troubleshooting for additional details.
Blacklist属性用于指定黑名单文件,开发者可使用该文件来防止函数和源文件被sanitized。只要当这些文件或函数对性能影响很大时,才能使用此属性。手动审核这些文件/函数以确保数组访问安全无虞。
- 在Android.mk中添加:
方法是将 "bounds" 添加到二进制文件和库模块的 LOCAL_SANITIZE 变量:
LOCAL_SANITIZE := bounds
# Optional features
LOCAL_SANITIZE_DIAG := bounds
LOCAL_SANITIZE_BLACKLIST := modulename_blacklist.txt
LOCAL_SANITIZE 接受以英文逗号分隔的排错程序列表。
LOCAL_SANITIZE_DIAG 用于开启诊断模式。只能在测试期间使用诊断模式。诊断模式不会在溢出发生时中止,而这会抹杀该缓解功能的安全优势并导致更高的性能开销,因此不建议在正式版中使用。
LOCAL_SANITIZE_BLACKLIST 属性用于指定黑名单文件,以便开发者防止函数和源文件成为排错对象。仅当您担心性能且目标文件/函数有很大的影响时,才能使用此属性。手动审核这些文件/函数以确保数组访问安全无虞。如需了解详情,请参阅问题排查。
- 停用BoundSan
您可以使用黑名单或函数属性在函数和源文件中停用 BoundSan。建议将 BoundSan 保留为启用状态,因此只有在函数或文件产生大量性能开销并且已手动审核源文件时才停用 BoundSan。
如需详细了解如何使用函数属性和黑名单文件格式设置停用 BoundSan,请参阅 Clang LLVM 文档。应将黑名单的作用范围限定为特定的排错程序,方法是使用区块名称指定目标排错程序,以免影响其他排错程序。
3.3 其它注意事项
- 验证
没有专门针对 BoundSan 的 CTS 测试。因此请确保 CTS 测试在启用或未启用 BoundSan 的情况下均能通过,以证明它不会给设备带来影响。
- 问题排查
在启用 BoundSan 之后全面测试组件,以确保任何先前未检测到的出界访问得到解决。
BoundSan 错误可以轻松识别,因为此类错误包含以下 tombstone 中止消息:
pid: ###, tid: ###, name: Binder:### >>> /system/bin/foobar <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'ubsan: out-of-bounds'
在诊断模式下运行时,会将源文件、行号和索引值输出到 logcat。默认情况下,此模式不会抛出中止消息。请查看 logcat 以检查是否存在任何错误。
external/foo/bar.c:293:13: runtime error: index -1 out of bounds for type 'int [24]'
评论