作为一个程序员在实际项目开发中很有可能会遇到下面的使用场景:
ASan(Address Sanitizer)工具便可以应用于 C/C++的内存错误检测,它可以实现:
ASan 原理概览
ASan 由两大模块构成:
插桩模块(Instrumentation Module),该模块主要做两件事情:
静态插桩,即对对所有访问的内存检查该内存所对应的 Shadow Memory 的状态,需要重新编译。
为所有栈上对象和全局对象创建前后的保护区(Poisoned Redzone),为检测溢出做准备。
运行时库(Runtime Library),该库同样要做两件事情:
替换默认路径的 malloc/free 等函数。为所有堆对象创建前后的保护区,将 free 掉的堆区域隔离(quarantine)一段时间,避免它立即被分配给其他人使用。
对错误情况进行输出,包括堆栈信息。
认识 Shadow Memory
Shadow Memory 也是内存中的一块儿区域,记录的是某块内存的状态信息,通常 malloc 函数返回的地址是 8 字节,该 8 字节的内存可能存在以下 9 中状态:
最前面的 k(0≤k≤8)字节是可寻址的,而剩下的 8-k 字节是不可寻址的。
Shadow Memory 对这 9 中状态进行编码,一个字节有 256(2^8)个状态,将 8 个字节的 Normal Memory 映射到 1 个字节,其中映射算法:
对于 64 位机器:
Shadow = (Mem >> 3) + 0x7fff8000;
复制代码
对于 32 位机器
Shadow = (Mem >> 3) + 0x20000000;
复制代码
Shadow Memory 的状态
Shadow Memory 一共有 9 中状态:
8 个字节都可寻址,shadow memory 的值为 0
1~7 个字节可寻址, shadow memory 的值为 1~7
0 个字节可寻址,Shadow Memory 的值为负数
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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
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
复制代码
对于不可寻址的区域,又可以划分为不同的区域,其中包括:
Heap Redzone:访问堆变量。
Stack Redzone:访问栈变量。
Global Redzone:访问全局变量。
Freed Heap:访问已经释放的对变量。
使用示例
在查阅相关资料的过程中看到了一些还不错的网站的相关资料,没有进行很仔细的阅读,在此做一下记录已被后续有需要的时候进行阅读:
Global-buffer-overflow
#include <iostream>
// g++ -fsanitize=address -fno-omit-frame-pointer -O1 -g -o asan_test asan_test.cpp
int global_array[4] = {-1};
int main(int argc, char*argv[]) {
return global_array[4]; // BOOM
}
复制代码
堆栈信息:
==26452==ERROR: AddressSanitizer: global-buffer-overflow on address 0x0000006010b0 at pc 0x0000004009bc bp 0x7ffc5fc1b810 sp 0x7ffc5fc1b800
READ of size 4 at 0x0000006010b0 thread T0
#0 0x4009bb in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6
#1 0x7f9a5d83883f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x4008c8 in _start (/home/ha/project/code/cpp_test_demo/base/asan_test+0x4008c8)
0x0000006010b0 is located 0 bytes to the right of global variable 'global_array' defined in 'asan_test.cpp:4:5' (0x6010a0) of size 16
SUMMARY: AddressSanitizer: global-buffer-overflow /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6 main
Shadow bytes around the buggy address:
0x0000800b81c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b81d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b81e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b81f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0000800b8210: 00 00 00 00 00 00[f9]f9 f9 f9 f9 f9 00 00 00 00
0x0000800b8220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8230: 00 00 00 00 01 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
0x0000800b8240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
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
复制代码
根据堆栈信息可以看到
global-buffer-overflow on address 0x0000006010b0 at pc 0x0000004009bc bp 0x7ffc5fc1b810 sp 0x7ffc5fc1b800
READ of size 4 at 0x0000006010b0 thread T0
复制代码
尝试在0x0000006010b0
地址处读取 4 个字节,出现了global-buffer-overflow
问题。同时根据堆栈信息可以看到f9
,即Global redzone
。栈顶指出了出问题的代码的位置asan_test.cpp:6
,检查相应的代码便可以发现访问 global_array 超出了数组的下标范围。
在此也可以验证,Memory 到 Shadow Memory 的地址映射算法,我的机器是 64 位的,在堆栈信息可以看到0x0000006010b0
不可用,根据前面的计算公式可以得到:
0x6010b0 >> 3 + 0x7fff8000 = 0x800B8216
复制代码
根据堆栈中,=>
位置后向后数 7 位,显示[f9]
,也就是Global redzone
,出错的地址。
heap-use-after-free
#include <iostream>
// g++ -fsanitize=address -fno-omit-frame-pointer -O1 -g -o asan_test asan_test.cpp
int main(int argc, char*argv[]) {
int *array = new int[5];
delete []array;
return array[0];
}
复制代码
堆栈信息:
=================================================================
==19937==ERROR: AddressSanitizer: heap-use-after-free on address 0x60300000efe0 at pc 0x000000400a70 bp 0x7ffd072c54d0 sp 0x7ffd072c54c0
READ of size 4 at 0x60300000efe0 thread T0
#0 0x400a6f in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:7
#1 0x7faedec9183f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400958 in _start (/home/ha/project/code/cpp_test_demo/base/asan_test+0x400958)
0x60300000efe0 is located 0 bytes inside of 20-byte region [0x60300000efe0,0x60300000eff4)
freed by thread T0 here:
#0 0x7faedf456caa in operator delete[](void*) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99caa)
#1 0x400a48 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6
#2 0x7faedec9183f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
previously allocated by thread T0 here:
#0 0x7faedf4566b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)
#1 0x400a38 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:5
#2 0x7faedec9183f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
SUMMARY: AddressSanitizer: heap-use-after-free /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:7 main
Shadow bytes around the buggy address:
0x0c067fff9da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c067fff9df0: fa fa fa fa fa fa fa fa fa fa fa fa[fd]fd fd fa
0x0c067fff9e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
复制代码
Heap-buffer-overflow
#include <iostream>
// g++ -fsanitize=address -fno-omit-frame-pointer -O1 -g -o asan_test asan_test.cpp
int main(int argc, char*argv[]) {
int *array = new int[4];
int res = array[5];
delete [] array;
return res;
}
复制代码
堆栈信息:
==5165==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000f004 at pc 0x000000400a61 bp 0x7fffde0396b0 sp 0x7fffde0396a0
READ of size 4 at 0x60200000f004 thread T0
#0 0x400a60 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6
#1 0x7f397592583f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400958 in _start (/home/ha/project/code/cpp_test_demo/base/asan_test+0x400958)
0x60200000f004 is located 4 bytes to the right of 16-byte region [0x60200000eff0,0x60200000f000)
allocated by thread T0 here:
#0 0x7f39760ea6b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)
#1 0x400a38 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:5
#2 0x7f397592583f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6 main
Shadow bytes around the buggy address:
0x0c047fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9df0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa 00 00
=>0x0c047fff9e00:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
复制代码
将上述int *array = new int[4];
更改为int *array = new int[5];
堆栈信息:
==5488==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300000eff4 at pc 0x000000400a61 bp 0x7ffe97d98820 sp 0x7ffe97d98810
READ of size 4 at 0x60300000eff4 thread T0
#0 0x400a60 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6
#1 0x7fb54501b83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400958 in _start (/home/ha/project/code/cpp_test_demo/base/asan_test+0x400958)
0x60300000eff4 is located 0 bytes to the right of 20-byte region [0x60300000efe0,0x60300000eff4)
allocated by thread T0 here:
#0 0x7fb5457e06b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)
#1 0x400a38 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:5
#2 0x7fb54501b83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6 main
Shadow bytes around the buggy address:
0x0c067fff9da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c067fff9df0: fa fa fa fa fa fa fa fa fa fa fa fa 00 00[04]fa
0x0c067fff9e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
复制代码
stack-buffer-overflow
#include <iostream>
// g++ -fsanitize=address -fno-omit-frame-pointer -O1 -g -o asan_test asan_test.cpp
int main(int argc, char*argv[]) {
int array[5];
int res = array[5];
return res;
}
复制代码
堆栈信息:
==22364==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc19b76594 at pc 0x000000400b63 bp 0x7ffc19b76550 sp 0x7ffc19b76540
READ of size 4 at 0x7ffc19b76594 thread T0
#0 0x400b62 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6
#1 0x7f20dc7b683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x4009f8 in _start (/home/ha/project/code/cpp_test_demo/base/asan_test+0x4009f8)
Address 0x7ffc19b76594 is located in stack of thread T0 at offset 52 in frame
#0 0x400ad5 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:4
This frame has 1 object(s):
[32, 52) 'array' <== Memory access at offset 52 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6 main
Shadow bytes around the buggy address:
0x100003366c60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366c70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366c90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366ca0: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
=>0x100003366cb0: 00 00[04]f4 f3 f3 f3 f3 00 00 00 00 00 00 00 00
0x100003366cc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366cd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366ce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366cf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100003366d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
复制代码
类似地将int array[5];
修改为int array[4];
==22984==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffead172424 at pc 0x000000400b63 bp 0x7ffead1723e0 sp 0x7ffead1723d0
READ of size 4 at 0x7ffead172424 thread T0
#0 0x400b62 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6
#1 0x7f9cafe3c83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x4009f8 in _start (/home/ha/project/code/cpp_test_demo/base/asan_test+0x4009f8)
Address 0x7ffead172424 is located in stack of thread T0 at offset 52 in frame
#0 0x400ad5 in main /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:4
This frame has 1 object(s):
[32, 48) 'array' <== Memory access at offset 52 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6 main
Shadow bytes around the buggy address:
0x100055a26430: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a26440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a26450: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a26460: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a26470: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
=>0x100055a26480: f1 f1 00 00[f4]f4 f3 f3 f3 f3 00 00 00 00 00 00
0x100055a26490: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a264a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a264b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a264c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055a264d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
复制代码
避坑集
GCC7.2 不支持 swapcontext
参见https://github.com/google/sanitizers/issues/189,关于不支持的原因:
AddressSanitizer does not fully support swapcontext.
Sometimes, swapcontext causes the entire shadow region (16T)
to be written by asan-internal routines (e.g. __asan_handle_no_return)
because the location of the stack changes w/o asan noticing it.
This may cause the machine to die or hang for a long time.
I am not at all sure if asan can fully support swapcontext,
but we at least should collect more test cases.
复制代码
在我们的项目中,采用了bthread
,在代码抛出异常,catch
住执行的过程中,很大概率会崩溃,崩溃栈中会提示https://github.com/google/sanitizers/issues/189的Bug
,很详细地分析了,相关的回复,猜测是bthread
会调用swapcontext
,修改配置,采用pthread
,代码便可以正常执行。
参考资料
ASAN和HWASAN原理解析
ASan Wiki
AddressSanitizerAlgorithm
Asan Debug
评论