GCC编译选项Sanitier问题定位记录
关键词:Address sanitizer、Use after free、Heap buffer overflow、Stack buffer overflow、Memory leak等等。
操作系统:Ubuntu 16.04;g++ (Ubuntu 4.8.5-4ubuntu2) 4.8.5;clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)。
1. Sanitizer简介
Sanitizers是谷歌发起的开源工具集,包括了AddressSanitizer, MemorySanitizer, ThreadSanitizer, LeakSanitizer,Sanitizers项目本是LLVM项目的一部分,但GNU也将该系列工具加入到了自家的GCC编译器中。GCC从4.8版本开始支持Address和Thread Sanitizer,4.9版本开始支持Leak Sanitizer和UB Sanitizer,这些都是查找隐藏Bug的利器。
AddressSanitizer检查地址相关问题,包括释放后使用、重复释放、堆溢出、栈溢出等等问题。
LeakSanitizer检查内存泄漏问题。
ThreadSanitizer检查线程数据竞争和死锁问题。
MemorySanitizer检查使用未初始化内存问题。
内核Sanitizer包括KASAN和KMSAN,分别提供内核中动态内存错误检查和未初始化内存使用问题检查。
2. AddressSanitizer
由于编译器版本不同,Address Sanitizer的支持不尽相同。下面记录部分功能测试结果。
2.1 Use after free
Use after free检测到使用的内存已经被释放导致的错误类型。当内存被释放后,所有数据置成0xfd。如果对应内存是0xfd表示可能使用了被释放的内存。
使用g++ use_after_free.cc -o use_after_free -ggdb -fsanitize=address编译:
// RUN: g++ use_after_free.cc -o use_after_free -ggdb -fsanitize=address int main(int argc, char **argv) { int *array = new int[100]; delete [] array; return array[argc]; // BOOM }
执行结果如下,其中地址和文件对应转换通过addr2line进行。
比如0x4007f8地址,addr2line -a 0x4007f8 -e use_after_free得到结果在use_after_free.cc的第6行。
================================================================= ==2708== ERROR: AddressSanitizer: heap-use-after-free on address 0x602e0001fc64 at pc 0x4007f9 bp 0x7ffd8b8b0830 sp 0x7ffd8b8b0828----错误类型说明是对堆的释放后使用,调用地址在0x4007f9. READ of size 4 at 0x602e0001fc64 thread T0 #0 0x4007f8 (/home/al/temp/address_sanitizer/use_after_free+0x4007f8)---------------------------错误现场在use_after_free的0x4007f8,在第6行。 #1 0x7fcbf515582f (/lib/x86_64-linux-gnu/libc-2.23.so+0x2082f) #2 0x4006b8 (/home/al/temp/address_sanitizer/use_after_free+0x4006b8) 0x602e0001fc64 is located 4 bytes inside of 400-byte region [0x602e0001fc60,0x602e0001fdf0) freed by thread T0 here: #0 0x7fcbf551083a (/usr/lib/x86_64-linux-gnu/libasan.so.0.0.0+0x1183a) #1 0x4007ac (/home/al/temp/address_sanitizer/use_after_free+0x4007ac)---------------------------释放现场,通过addr2line得到对应第5行。 #2 0x7fcbf515582f (/lib/x86_64-linux-gnu/libc-2.23.so+0x2082f) previously allocated by thread T0 here: #0 0x7fcbf551067a (/usr/lib/x86_64-linux-gnu/libasan.so.0.0.0+0x1167a) #1 0x400795 (/home/al/temp/address_sanitizer/use_after_free+0x400795)---------------------------申请现场,通过addr2line得到对应第4行。 #2 0x7fcbf515582f (/lib/x86_64-linux-gnu/libc-2.23.so+0x2082f) Shadow bytes around the buggy address: 0x0c063fffbf30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbf40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbf50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbf60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbf70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c063fffbf80: fa fa fa fa fa fa fa fa fa fa fa fa[fd]fd fd fd------------------------------------[]表示异常点,0xfd表示此段内存已经被释放。 0x0c063fffbf90: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c063fffbfa0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c063fffbfb0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa------------------------------------一个shadow字节表示8个字节,共50个0xfd,对应400个字节,也即分配的array大小。 0x0c063fffbfc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbfd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 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 righ 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 ASan internal: fe ==2708== ABORTING
可见AddressSanitizer对内存区域进行特殊标记,如果和预期不符合则表示内存被破坏。
2.2 Heap buffer overflow
Heap buffer overflow表示堆内存溢出,使用0xfa和0xfb表示堆的左右边界。如果对应内存为0xfa或者0xfb表示可能是堆溢出。
// RUN: g++ heap_buffer_overflow.cc -o heap_buffer_overflow -ggdb -fsanitize=address int main(int argc, char **argv) { int *array = new int[100]; array[0] = 0; int res = array[argc + 100]; // BOOM delete [] array; return res; }
结果如下:
================================================================= ==4852== ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602e0001fdf4 at pc 0x40087b bp 0x7fff2f0d10c0 sp 0x7fff2f0d10b8 READ of size 4 at 0x602e0001fdf4 thread T0 #0 0x40087a (/home/al/temp/address_sanitizer/heap_buffer_overflow+0x40087a)-------------------异常地址在0x40087a,对应第6行。 #1 0x7fadfc16582f (/lib/x86_64-linux-gnu/libc-2.23.so+0x2082f) #2 0x400708 (/home/al/temp/address_sanitizer/heap_buffer_overflow+0x400708) 0x602e0001fdf4 is located 4 bytes to the right of 400-byte region [0x602e0001fc60,0x602e0001fdf0) allocated by thread T0 here: #0 0x7fadfc52067a (/usr/lib/x86_64-linux-gnu/libasan.so.0.0.0+0x1167a) #1 0x4007e5 (/home/al/temp/address_sanitizer/heap_buffer_overflow+0x4007e5) #2 0x7fadfc16582f (/lib/x86_64-linux-gnu/libc-2.23.so+0x2082f) Shadow bytes around the buggy address: 0x0c063fffbf60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbf70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbf80: fa fa fa fa fa fa fa fa fa fa fa fa 00 00 00 00 0x0c063fffbf90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c063fffbfa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c063fffbfb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00[fa]fa------------------------0xfa是堆的redzone,访问到这里的内存表示堆越界访问了。 0x0c063fffbfc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbfd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbfe0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffbff0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c063fffc000: 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 righ 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 ASan internal: fe ==4852== ABORTING
2.3 Stack buffer overflow
Stack buffer overflow表示栈的访问超出边界,通过0xf1/0xf3/0xf4来判定。如果访问到0xf1/0xf3/0xf4则表示有可能超出栈边界的访问。
// RUN: g++ stack_buffer_overflow.cc -o stack_buffer_overflow -ggdb -fsanitize=address int main(int argc, char **argv) { int stack_array[100]; stack_array[1] = 0; return stack_array[argc + 100]; // BOOM }
结果如下:
================================================================= ==6196== ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe96edf094 at pc 0x400821 bp 0x7ffe96edeec0 sp 0x7ffe96edeeb8 READ of size 4 at 0x7ffe96edf094 thread T0 #0 0x400820 (/home/al/temp/address_sanitizer/stack_buffer_overflow+0x400820) #1 0x7f9f0ba9e82f (/lib/x86_64-linux-gnu/libc-2.23.so+0x2082f) #2 0x400678 (/home/al/temp/address_sanitizer/stack_buffer_overflow+0x400678) Address 0x7ffe96edf094 is located at offset 436 in frame <main> of T0's stack: This frame has 1 object(s): [32, 432) 'stack_array' HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext (longjmp and C++ exceptions *are* supported) Shadow bytes around the buggy address: 0x100052dd3dc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100052dd3dd0: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 0x100052dd3de0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100052dd3df0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100052dd3e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x100052dd3e10: 00 00[f4]f4 f3 f3 f3 f3 00 00 00 00 00 00 00 00 0x100052dd3e20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100052dd3e30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100052dd3e40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100052dd3e50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100052dd3e60: 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 righ 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 ASan internal: fe ==6196== ABORTING
2.4 Global buffer overflow
Global buffer overflow表示超出全局变量访问区域,通过0xf9进行判断。如果访问的区域是0xf9则表示可能超出了全局变量访问区域。
// RUN: g++ global_buffer_overflow.cc -o global_buffer_overflow -ggdb -fsanitize=address int global_array[100] = {-1}; int main(int argc, char **argv) { return global_array[argc + 100]; // BOOM }
结果如下:
================================================================= ==7681== ERROR: AddressSanitizer: global-buffer-overflow on address 0x0000006011f4 at pc 0x4007f3 bp 0x7ffeb4f10630 sp 0x7ffeb4f10628 READ of size 4 at 0x0000006011f4 thread T0 #0 0x4007f2 (/home/al/temp/address_sanitizer/global_buffer_overflow+0x4007f2) #1 0x7fad3bdf982f (/lib/x86_64-linux-gnu/libc-2.23.so+0x2082f) #2 0x4006d8 (/home/al/temp/address_sanitizer/global_buffer_overflow+0x4006d8) 0x0000006011f4 is located 4 bytes to the right of global variable 'global_array (global_buffer_overflow.cc)' (0x601060) of size 400 Shadow bytes around the buggy address: 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 00 00 00 00 00 00 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 00 00 00 00 00 00 00 00 00 00[f9]f9 0x0000800b8240: f9 f9 f9 f9 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 0x0000800b8270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0000800b8280: 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 righ 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 ASan internal: fe ==7681== ABORTING
2.5 Use after return
2.6 Use after scope
2.7 Initialization order bugs
2.8 Memory leaks
Memory leaks用于检查内存泄漏,结果会告知泄漏的内存在何处申请。
//RUN: clang -fsanitize=address -g memory-leak.c -o memory-leak #include <stdlib.h> void *p; int main() { p = malloc(7); p = 0; // The memory is leaked here. return 0; }
结果如下,会显示泄漏点的栈,以及一个总结:泄漏多少内存、在哪几处泄漏。
================================================================= ==9089==ERROR: LeakSanitizer: detected memory leaks Direct leak of 7 byte(s) in 1 object(s) allocated from: #0 0x40794f (/home/al/temp/address_sanitizer/memory-leak+0x40794f) #1 0x427f6a (/home/al/temp/address_sanitizer/memory-leak+0x427f6a) #2 0x7fcaaef9a82f (/lib/x86_64-linux-gnu/libc.so.6+0x2082f) SUMMARY: LeakSanitizer: 7 byte(s) leaked in 1 allocation(s).