一、背景
为了提前检测出Android User Sapce的app或native进程的内存错误问题,帮助研发定位与分析这些问题,基于Android 14版本上对HWASAN做了调研分析。
二、ASAN介绍
HWASAN是在ASAN的基础上做了拓展,因此在介绍HWASAN之前先了解下ASAN.
ASAN(AddressSanitizer)和HWASAN(Hardware-assisted AddressSanitizer)都是内存错误检测工具,用于帮助开发者发现和修复内存相关的bug,如heap-buffer-overflow、Heap-use-after-free、stack-buffer-overflow、global-buffer-overflow、double-free、Use-after-return、Alloc-dealloc-mismatch、use-after-poision等内存错误问题。它们的主要区别在于实现方式和性能开销。
2.1 shadow memory
shadow memory区域来记录实际内存的状态信息,malloc申请的内存或其它方式申请的内存一般8字节对齐,8字节的正常内存对应1个字节的shadow memory,8个字节中可划分为可寻址(访问)与不可寻址区域。如,前4个字节可寻址,后4个字节不可寻址,shadow memory记录的数据为5,即0000 0100.
normal mem与shadow mem对应关系:
shadow memory address = (normal memory address >> 3) + 0x1000000000
8字节组成的normal memory region共有3种状态:
1)1~7个字节可寻址,即shadow memory的值为1~7。
2)8个字节都可寻址,即shadow memory的值为0。
3)0个字节可寻址,shadow memory的值为负数。
0个字节可寻址其实可以分为多种情况,如:
这块区域是heap redzones、stack redzones、global redzones、freed memory,不同错误类型对应的shadow memory值也不一样。
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa (实际上Heap right 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
Shadow gap: cc
每次访问normal memory地址时,都会去结合对应的shadow mem的值检测是否合法。
2.2 ASAN实现原理
ASAN实现原理:通过在内存分配时插入额外的代码来检查内存访问是否合法。使用一个影子内存(shadow memory)区域来记录实际内存的使用情况,当检测到非法内存访问时,ASAN会报告错误。
检测算法:
ShadowAddr = (Addr >> 3) + Offset;
k = *ShadowAddr;
if (k != 0 && ((Addr & 7) + AccessSize > k))
ReportAndCrash(Addr);
k!=0,说明Normal memory region中的8个字节并不是都可以被寻址的。
Addr & 7,将得知此次内存访问是从memory region的第几个byte开始的。
AccessSize是此次内存访问需要访问的字节长度。
(Addr&7)+AccessSize > k,则说明此次内存访问将会访问到不可寻址的字节。
当此次内存访问可能会访问到不可寻址的字节时,ASAN会报错并结合shadow memory中具体的值明确错误类型。
下面以use-after-free、heap-buffer-overflow来分析具体的检测原理。
2.2.1 use-after-free检测
检测原理:
1)已经free的normal memory对应的shadow memory值为0xfd.
2)已经free的normal memory区域需要放入隔离区一段时间(过段时间才允许被重新分配),防止发生错误时该区域已经通过malloc重新分配给其他人使用。一旦分配给其他人使用,则可能漏掉UseAfterFree的错误。
3)如果再次访问该normal memory区域时,发现对应的shadow memory值为0xfd,则触发ASAN异常报错。
2.2.2 heap-buffer-overflow检测
检测原理:
1)分配内存时normal memory的前后需要插入一定长度的安全区(大小不定),且此安全区对应的shadow memory被标记为0xfa.
2)当访问越界时,刚好访问了normal memory的前后安全区,此安全区对应的shadow memory值为0xfa,则触发ASAN异常报错。
2.3 ASAN缺陷
2.3.1 存在漏检的风险
1)use-after-free检测依赖隔离区,一段时间后这块内存被其他模块重新分配后,该模块再去访问,则无法检测出use-after-ree的错误。
2)heap-buffer-overflow检测依赖安全区,安全区有大小限制。可能是8bytes,64bytes或者其他什么值,但不管怎么样终归是有限的。如果某次踩踏跨过了安全区,踩踏到另一片可寻址的内存区域,则无法检测出heap-buffer-overflow的错误。
2.3.2 性能开销大
有两个原因导致ASAN性能开销:
1)增加额外的内存消耗:8字节的normal memory对应1个字节的shadow memory,相当于额外多出1/8的内存用于记录正常内存的状态信息;内存分配时需要在前后插入安全区,用于检测越界访问。这两种情况都会导致内存增加。
2)效率降低:需要在每次内存访问时进行检查,降低了效率;被释放的内存,需被隔离一段时间,无法立即被重新分配,系统内存紧张时,可能存在较大的性能影响
因此,针对ASAN的存在漏检的风险和性能开销大的缺陷,google提出了HWASAN来改善这两大缺陷。HWASAN解决措施:
1)措施1:64位的机器,实际上ARMv8寻址只用到了低48位,因此malloc时,HWASAN利用高8位标记tag,对应的shadow memory的值也记做tag
由于HWASAN 16字节的normal memory对应1个字节的shadow memory(ASAN为8:1),降低额外内存的消耗;由于HWASAN use-after-free检测不依赖隔离区,解决use-after-free漏检的风险(详细参考3.1.1)。
2)措施2:去除ASAN安全区的内存机制
由于去除ASAN安全区的内存机制,被释放的内存可以被其他模块立即分配,从而提高了内存的利用效率;
同时可以解决heap-buffer-overflow漏检的风险(详细参考3.1.2)。
三、HWASAN介绍
HWASAN是ASAN的升级版,优化了ASAN性能开销和漏检风险的缺陷。
3.1 实现原理
实现原理:64位的机器,实际上ARMv8寻址只用到了低48位。HWASAN用这8bit来存储一块内存区域的标签(tag)。
堆内存通过malloc分配出来,HWASAN在它返回地址时会更改该有效地址的高8位,随机生成一个tag数值,并将该tag同步到内存对应的shadow memory,当内存释放时,更新shadow memory的tag,HWASAN中normal memory和shadow memory的映射关系是16:1,而ASAN中二者的映射关系是8:1。通过对比内存高8位的tag与shadow memory的tag是否一致,如果不一致,会触发HWASAN相关的内存错误。
下面以use-after-free、heap-buffer-overflow来分析具体的检测原理。
3.1.1 use-after-free检测
检测原理:
1)分配一块内存时,内存的高8位打上tag,对应的shadow memory的值也为该tag
2)内存释放时,更新对应的shadow memory的tag,使这块内存的高8位的tag与shadow memory的tag不一致
3)当再次访问这块内存时,发现内存的高8位的tag与shadow memory的tag不一致,触发HWASAN内存错误
如,char* p =new char[10],分配10个字节的内存,由于8个字节对齐,共占用16个字节,高8位随机生成tag1。
当内存释放时,对应的shadow memory的值由原来的tag1更新位tag2.再次访问p[0]时,检查高8位tag1与shadow memory中的值tag2不一致,触发HWASAN use-after-free.
char* p =new char[10];
delete [] p;
p[0] = "abc";
3.1.2 heap-buffer-overflow检测
检测原理:
1)相邻内存的shadow memory的tag不一致
2)当访问这块内存地址时,会去检测该地址高8位的tag与对应shadow memory的tag是否一直不一致。如果不一致,说明越界访问
如,char* p =new char[10],分配10个字节的内存,由于8个字节对齐,共占用16个字节,高8位随机生成tag2。当访问p[16]时,由于p+16所处地址对应的shadow memory的值为tag3与p地址对应的高8位tag2不一致,会触发HWASAN heap-buffer-overflow.
char* p =new char[10];
p[16] = "abc";
由于每次malloc,高8位的tag随机生成,因此存在相邻内存高8位的tag一致的概率(如tag2等于tag3),概率为1/256,在这种情况下即使越界访问,也无法检测出heap-buffer-overflow的问题。
3.2 HWASAN优缺点
优点:
解决了ASAN的缺陷
缺点:
1)由于采用了高8位的tag检测机制,因此只能适用于64位的机器
2)HWASAN中normal memory和shadow memory的映射关系是16:1,即会多消耗1/16的额外内存用于记录normal memory的状态(tag)信息
3)每次内存分配、释放、访问,都需要额外的检测工作,降低性能
4.1 系统全功能打开
1)编译带HWASAN的镜像和带hwasan的libc.so & libclang_rt.hwasan-aarch64-android.so
source build/envsetup.sh
lunch missi_auto_native_only64-userdebug
export SANITIZE_TARGET=hwaddress
make -j8 或make systemimage
libc.so路径:
out/target/product/missi/system/lib64/bootstrap/hwasan/libc.so
libclang_rt.hwasan-aarch64-android.so路径:out/target/product/missi/system/lib64/bootstrap/libclang_rt.hwasan-aarch64-android.so
2)刷机或push libc.so和libclang_rt.hwasan-aarch64-android.so到/system/lib64目录后重启设备
4.2 单个应用或进程打开
AndroidManifest.xml中 <application> 元素中配置了 android:debuggable="true"
<application android:debuggable="true">
</application>
native进程开启HWASAN,需要在Android.bp中做配置:
sanitize: {
hwaddress: true,
},
五、Debug案例
在设备验证HWASAN功能:
1)设置HWASAN环境变量,编译出带HWASAN的镜像或libc.so + libclang_rt.hwasan-aarch64-android.so
source build/envsetup.sh
lunch missi_auto_native_only64-userdebug
export SANITIZE_TARGET=hwaddress
make -j8 或make systemimage
带hwasan的libc.so路径:
out/target/product/missi/system/lib64/bootstrap/hwasan/libc.so
libclang_rt.hwasan-aarch64-android.so路径:out/target/product/missi/system/lib64/bootstrap/libclang_rt.hwasan-aarch64-android.so
(刷机方式效率较低,可以直接push so库到设备)
2)push libc.so和libclang_rt.hwasan-aarch64-android.so到/system/lib64目录后重启设备
3)自己编写一个Demo,push hw-asan-test-bin Demo到/system/bin目录并执行
检测double-free、use-after-free、invalid-free、heap-over-flow等场景,
分别执行./system/bin/hw-asan-test-bin double-free、./system/bin/hw-asan-test-bin use-after-free、./system/bin/hw-asan-test-bin heap-over-flow命令。
Demo的部分代码:
void doubleFree() {
// 分配4个字节
int *ptr = (int*)malloc(4);
std::cout << "ptr = " << ptr << std::endl;
free(ptr);
free(ptr);
}
void invalidFree() {
int *ptr = (int*)malloc(4);
std::cout << "ptr = " << ptr << std::endl;
free(ptr+1);
std::cout << "invalid-free ~" << std::endl;
}
void useAfterFree() {
char* ptr = new char[20];
std::cout << "ptr = " << ptr << std::endl;
delete[] ptr;
ptr[0] = 'A';
std::cout << "useAfterFree ~" << std::endl;
}
void heapOverFlow() {
char* ptr = new char[20];
std::cout << "ptr = " << ptr << std::endl;
ptr[32] = 'A';
std::cout << "heapOverFlow ~" << std::endl;
}
void memLeak() {
for (int i = 0; i < 1000; i++) {
char *buffer = (char *)malloc(100);
}
std::cout << "memLeak ~" << std::endl;
}
4)查询生成的tombstone文件
以下是hw-asan-test-bin Demo crash生成的HWASAN tombstone文件,如下:
Cmdline: ./system/bin/hw-asan-test-bin double-free
pid: 12968, tid: 12968, name: hw-asan-test-bi >>> ./system/bin/hw-asan-test-bin <<<
uid: 0
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
// 内存被释放后,0x003c95840020地址对应的内存被标记为6c
Abort message: '==12968==ERROR: HWAddressSanitizer: invalid-free on address 0x003c95840020 at pc 0x007d0dd81ba4 on thread T0
tags: 77/6c (ptr/mem)
#0 0x7d0dd81ba4 (/system/lib64/libclang_rt.hwasan-aarch64-android.so+0x23ba4) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#1 0x5b9584c230 (/system/bin/hw-asan-test-bin+0x1230) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#2 0x5b9584c6a4 (/system/bin/hw-asan-test-bin+0x16a4) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#3 0x7d0aab6b60 (/apex/com.android.runtime/lib64/bionic/hwasan/libc.so+0xb0b60) (BuildId: 86a860a589207e712675d7d611b13147)
#4 0x5b9584c054 (/system/bin/hw-asan-test-bin+0x1054) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
[0x003c95840020,0x003c95840040) is a small unallocated heap chunk; size: 32 offset: 0
Cause: use-after-free
// 分配的内存地址0x003c95840020至0x003c95840024,刚好是4个字节与Demo代码一致
0x003c95840020 is located 0 bytes inside a 4-byte region [0x003c95840020,0x003c95840024)
// HWASAN内存释放的堆栈信息
freed by thread T0 here:
#0 0x7d0dd81ba4 (/system/lib64/libclang_rt.hwasan-aarch64-android.so+0x23ba4) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#1 0x5b9584c188 (/system/bin/hw-asan-test-bin+0x1188) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#2 0x5b9584c6a4 (/system/bin/hw-asan-test-bin+0x16a4) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#3 0x7d0aab6b60 (/apex/com.android.runtime/lib64/bionic/hwasan/libc.so+0xb0b60) (BuildId: 86a860a589207e712675d7d611b13147)
#4 0x5b9584c054 (/system/bin/hw-asan-test-bin+0x1054) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
// HWASAN内存分配的堆栈信息
previously allocated here:
#0 0x7d0dd82244 (/system/lib64/libclang_rt.hwasan-aarch64-android.so+0x24244) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#1 0x7d0aa671dc (/apex/com.android.runtime/lib64/bionic/hwasan/libc.so+0x611dc) (BuildId: 86a860a589207e712675d7d611b13147)
#2 0x5b9584c0d4 (/system/bin/hw-asan-test-bin+0x10d4) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#3 0x5b9584c6a4 (/system/bin/hw-asan-test-bin+0x16a4) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#4 0x7d0aab6b60 (/apex/com.android.runtime/lib64/bionic/hwasan/libc.so+0xb0b60) (BuildId: 86a860a589207e712675d7d611b13147)
#5 0x5b9584c054 (/system/bin/hw-asan-test-bin+0x1054) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
hwasan_dev_note_heap_rb_distance: 1 1023
hwasan_dev_note_num_matching_addrs: 0
hwasan_dev_note_num_matching_addrs_4b: 0
Thread: T0 0x007400002000 stack: [0x007fc95fb000,0x007fc9dfb000) sz: 8388608 tls: [0x007d0ea6efc0,0x007d0ea72000)
Memory tags around the buggy address (one tag corresponds to 16 bytes):
0x003c9583f800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c9583f900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c9583fa00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c9583fb00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c9583fc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c9583fd00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c9583fe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c9583ff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x003c95840000: 08 00 [6c] 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x003c95840800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Tags for short granules around the buggy address (one tag corresponds to 16 bytes):
0x003c9583ff00: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
=>0x003c95840000: 6e .. [..] .. .. .. .. .. .. .. .. .. .. .. .. ..
0x003c95840100: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
......
backtrace:
#00 pc 00000000000bab8c /apex/com.android.runtime/lib64/bionic/hwasan/libc.so (abort+308) (BuildId: 86a860a589207e712675d7d611b13147)
#01 pc 00000000000354b4 /system/lib64/libclang_rt.hwasan-aarch64-android.so (__sanitizer::Abort()+60) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#02 pc 0000000000033d3c /system/lib64/libclang_rt.hwasan-aarch64-android.so (__sanitizer::Die()+204) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#03 pc 00000000000286c4 /system/lib64/libclang_rt.hwasan-aarch64-android.so (__hwasan::ScopedReport::~ScopedReport()+544) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#04 pc 0000000000027458 /system/lib64/libclang_rt.hwasan-aarch64-android.so (__hwasan::ReportInvalidFree(__sanitizer::StackTrace*, unsigned long)+560) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#05 pc 0000000000023c00 /system/lib64/libclang_rt.hwasan-aarch64-android.so (__sanitizer_free+264) (BuildId: 558b5c131872716737ddc0a62f3382dd3df70b9a)
#06 pc 0000000000001230 /system/bin/hw-asan-test-bin (doubleFree()+472) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#07 pc 00000000000016a4 /system/bin/hw-asan-test-bin (main+648) (BuildId: b3d99e0748a4c4a2607c8c2b9b91815e)
#08 pc 00000000000b0b60 /apex/com.android.runtime/lib64/bionic/hwasan/libc.so (__libc_init+148) (BuildId: 86a860a589207e712675d7d611b13147)
......