写点什么

C++ 内存访问错误检测

用户头像
正向成长
关注
发布于: 2021 年 06 月 11 日
C++内存访问错误检测

作为一个程序员在实际项目开发中很有可能会遇到下面的使用场景:

  • 上线的程序运行一段时间之后变得很慢,重启之后就好了,这很有可能是程序存在内存泄漏,导致机器中可用的内存越来越少。

  • 程序崩溃了,有可能是堆栈栈溢出,如何获取更多的信息进行问题的定位?


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.cppint 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 0x7ffc5fc1b800READ 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 16SUMMARY: AddressSanitizer: global-buffer-overflow /home/ha/project/code/cpp_test_demo/base/asan_test.cpp:6 mainShadow 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 00Shadow 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 0x7ffc5fc1b800READ 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.cppint 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 0x7ffd072c54c0READ 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 mainShadow 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.cppint 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 0x7fffde0396a0READ 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 mainShadow 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 0x7ffe97d98810READ 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 mainShadow 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.cppint 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 0x7ffc19b76540READ 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 variableHINT: 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 mainShadow 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 0x7ffead1723d0READ 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 variableHINT: 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 mainShadow 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/189Bug,很详细地分析了,相关的回复,猜测是bthread会调用swapcontext,修改配置,采用pthread,代码便可以正常执行。

参考资料


  1. ASAN和HWASAN原理解析

  2. ASan Wiki

  3. AddressSanitizerAlgorithm

  4. Asan Debug

发布于: 2021 年 06 月 11 日阅读数: 9
用户头像

正向成长

关注

正向成长 2018.08.06 加入

想要坚定地做大规模数据处理(流数据方向),希望结合结合批处理的传统处理方式,以及之后流批混合处理方向进行学习和记录。

评论

发布
暂无评论
C++内存访问错误检测