Bootstrap

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 

// 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

尝试在地址处读取4个字节,出现了问题。同时根据堆栈信息可以看到,即。栈顶指出了出问题的代码的位置,检查相应的代码便可以发现访问global_array超出了数组的下标范围。

在此也可以验证,Memory到Shadow Memory的地址映射算法,我的机器是64位的,在堆栈信息可以看到不可用,根据前面的计算公式可以得到:

0x6010b0 >> 3 + 0x7fff8000 = 0x800B8216

根据堆栈中,位置后向后数7位,显示,也就是,出错的地址。

heap-use-after-free

#include 

// 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 

// 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

将上述更改为

堆栈信息:

==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 

// 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

类似地将修改为

==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. 

在我们的项目中,采用了,在代码抛出异常,住执行的过程中,很大概率会崩溃,崩溃栈中会提示https://github.com/google/sanitizers/issues/189的,很详细地分析了,相关的回复,猜测是会调用,修改配置,采用,代码便可以正常执行。

参考资料