内存检测工具:sanitizer
1. 背景
內(nèi)存泄漏是一個比較常見的問題,之前使用的是valgrind來實現(xiàn)內(nèi)存檢查的情況比較多,這里介紹一種更加便利的內(nèi)存檢測工具, 那就是gcc自帶的sanitizer。
2. sanitizer 的用法
2.1 sanitizer的基本簡介
Sanitizers 是谷歌發(fā)起的開源工具集,包括AddressSanitizer,MemorySanitizer, ThreadSanitizer, LeakSanitizer, Sanitizers項目本身是llvm項目的一部分,
gcc自帶的工具, gcc從4.8版本開始支持Address和Thread Sanitizer,4.9版本開始支持Leak Sanitizer和UBSanitizer。
可以支持的內(nèi)存檢測:
- Use after free
- Heap buffer overflow
- Stack buffer overflow
- Global buffer overflow
- Use after return
- Use after scope
- Initialization order bugs
- Memory leaks
具體錯誤類型解釋:
2.2 升級高版本的gcc和安裝相關(guān)的依賴庫(centos 7)
yum -y install centos-release-scl yum -y install devtoolset-7-gcc devtoolset-7-gcc-c++ devtoolset-7-binutils yum -y install devtoolset-7-libasan-devel.x86_64 devtoolset-7-liblsan-devel.x86_64 devtoolset-7-libtsan-devel.x86_64 devtoolset-7-libubsan-devel.x86_64 scl enable devtoolset-7 bash echo "source /opt/rh/devtoolset-7/enable" >>/etc/profile這里需要特別注意的是:Address Sanitizer 會替換malloc和free, 如果采用第三方的內(nèi)存申請庫,則無法替換,會造成功能缺失。
可以檢查的內(nèi)存問題包括:
1. Out-of-bounds accesses to heap, stack and globals 2. Use-after-free 3. Use-after-return (runtime flag) 4. ASAN_OPTIONS=detect_stack_use_after_return=1) 5. Use-after-scope (clang flag -fsanitize-address-use-after-scope) 6. Double-free, invalid free 7. Memory leaks (experimental)2.3 實踐測試
2.3.1 stack overflow
其中CMakeLists.txt如下:
cmake_minimum_required (VERSION 2.8) project (sanitizer) set(CMAKE_CXX_FLAGS "-g -fsanitize=leak -fsanitize=address -fno-omit-frame-pointer") add_executable(sanitizer_stack_overflow src/sanitizer_stack_overflow.cpp)-fsanitize=address 使能Address Sanitizer工具
-fsanitize=leak 只使能Leak Sanitizer,檢測內(nèi)存泄漏問題
-fno-omit-frame-pointer 檢測到內(nèi)存錯誤時打印函數(shù)調(diào)用棧
-O1 代碼優(yōu)化選項,可以打印更清晰的函數(shù)調(diào)用棧
其中src/sanitizer_stack_overflow.cpp如下:
#include <stdio.h> #include <stdlib.h> #include <string.h>int func0(void) {char str[4] = {0};strcpy(str, "1234");return 0; }int main(int argc, char *argv[]) {func0();return 0; }執(zhí)行結(jié)果如下:
==10098==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffdcae5ca24 at pc 0x7faae0b134ca bp 0x7ffdcae5c9f0 sp 0x7ffdcae5c198 WRITE of size 5 at 0x7ffdcae5ca24 thread T0#0 0x7faae0b134c9 (/lib64/libasan.so.4+0x794c9)#1 0x400a6a in func0() /root/code/cmake_project/app/sanitizer/src/sanitizer_stack_overflow.cpp:7#2 0x400ad2 in main /root/code/cmake_project/app/sanitizer/src/sanitizer_stack_overflow.cpp:12#3 0x7faadfecf554 in __libc_start_main (/lib64/libc.so.6+0x22554)#4 0x4008f8 (/root/code/cmake_project/app/sanitizer/build/sanitizer_stack_overflow+0x4008f8)Address 0x7ffdcae5ca24 is located in stack of thread T0 at offset 36 in frame#0 0x4009b6 in func0() /root/code/cmake_project/app/sanitizer/src/sanitizer_stack_overflow.cpp:5This frame has 1 object(s):[32, 36) 'str' <== Memory access at offset 36 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 (/lib64/libasan.so.4+0x794c9) Shadow bytes around the buggy address:說明:
2.3.2 heap overflow
src/sanitizer_heap_overflow.cpp 代碼如下:
#include <stdio.h> #include <stdlib.h> #include <string.h>int func1(void) {char *p = (char*)malloc(sizeof(char)*4);char chs[] = {"12345"};memset(p, 0x0, 4);if (p != NULL) {memcpy(p, chs, 5);}return 0; }int main(int argc, char *argv[]) {func1();return 0; }執(zhí)行結(jié)果如下:
================================================================= ==10373==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x7f8f772ba4ca bp 0x7fff5a93fc10 sp 0x7fff5a93f3b8 WRITE of size 5 at 0x602000000014 thread T0#0 0x7f8f772ba4c9 (/lib64/libasan.so.4+0x794c9)#1 0x400ac2 in func1() /root/code/cmake_project/app/sanitizer/src/sanitizer_heap_overflow.cpp:10#2 0x400b2c in main /root/code/cmake_project/app/sanitizer/src/sanitizer_heap_overflow.cpp:16#3 0x7f8f76676554 in __libc_start_main (/lib64/libc.so.6+0x22554)#4 0x4008d8 (/root/code/cmake_project/app/sanitizer/build/sanitizer_heap_overflow+0x4008d8)0x602000000014 is located 0 bytes to the right of 4-byte region [0x602000000010,0x602000000014) allocated by thread T0 here:#0 0x7f8f7731f8a0 in malloc (/lib64/libasan.so.4+0xde8a0)#1 0x400a0a in func1() /root/code/cmake_project/app/sanitizer/src/sanitizer_heap_overflow.cpp:6#2 0x400b2c in main /root/code/cmake_project/app/sanitizer/src/sanitizer_heap_overflow.cpp:16#3 0x7f8f76676554 in __libc_start_main (/lib64/libc.so.6+0x22554)SUMMARY: AddressSanitizer: heap-buffer-overflow (/lib64/libasan.so.4+0x794c9) Shadow bytes around the buggy address:說明:
2.3.2 use after free
#include <stdio.h> #include <stdlib.h> #include <string.h>void func2(void) {int * a = (int*)malloc(sizeof(int)*1);if ( a != NULL ) {*a = 1;printf("a is:%d.",*a);free(a);*a = 2;printf("error a is:%d.",*a);} }int main(int argc, char *argv[]) {func2();return 0; }執(zhí)行結(jié)果如下:
================================================================= ==3838==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x000000400a43 bp 0x7ffcdbefd570 sp 0x7ffcdbefd560 WRITE of size 4 at 0x602000000010 thread T0#0 0x400a42 in func2() /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_use_after_free.cpp:11#1 0x400a7a in main /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_use_after_free.cpp:17#2 0x7ff7391d6554 in __libc_start_main (/lib64/libc.so.6+0x22554)#3 0x4008d8 (/root/Public/cmake_code/cmake_project/app/sanitizer/build/sanitizer_use_after_free+0x4008d8)0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014) freed by thread T0 here:#0 0x7ff739e7f508 in __interceptor_free (/lib64/libasan.so.4+0xde508)#1 0x400a0b in func2() /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_use_after_free.cpp:10#2 0x400a7a in main /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_use_after_free.cpp:17#3 0x7ff7391d6554 in __libc_start_main (/lib64/libc.so.6+0x22554)previously allocated by thread T0 here:#0 0x7ff739e7f8a0 in malloc (/lib64/libasan.so.4+0xde8a0)#1 0x400998 in func2() /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_use_after_free.cpp:6#2 0x400a7a in main /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_use_after_free.cpp:17#3 0x7ff7391d6554 in __libc_start_main (/lib64/libc.so.6+0x22554)SUMMARY: AddressSanitizer: heap-use-after-free /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_use_after_free.cpp:11 in func2() Shadow bytes around the buggy address:說明:
2.3.3 global_buffer_overflow
src/sanitizer_global_buffer_overflow.cpp 代碼如下:
#include <stdio.h>int g_abc[11];int func3(void) {int i = 0;for (i = 0; i <= 100; i++) {printf("value:%d\t",g_abc[i]);if (i%10 == 0 && i != 0) {printf("\n");}}return g_abc[12]; }int main() {func3();return 0; }執(zhí)行結(jié)果如下:
value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 ================================================================= ==4137==ERROR: AddressSanitizer: global-buffer-overflow on address 0x00000060216c at pc 0x0000004009e9 bp 0x7ffc3d837020 sp 0x7ffc3d837010 READ of size 4 at 0x00000060216c thread T0#0 0x4009e8 in func3() /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_global_buffer_overflow.cpp:8#1 0x400a86 in main /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_global_buffer_overflow.cpp:18#2 0x7fd1fe1a2554 in __libc_start_main (/lib64/libc.so.6+0x22554)#3 0x4008d8 (/root/Public/cmake_code/cmake_project/app/sanitizer/build/sanitizer_global_buffer_overflow+0x4008d8)0x00000060216c is located 0 bytes to the right of global variable 'g_abc' defined in '/root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_global_buffer_overflow.cpp:3:5' (0x602140) of size 44 SUMMARY: AddressSanitizer: global-buffer-overflow /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_global_buffer_overflow.cpp:8 in func3() Shadow bytes around the buggy address:說明:
2.3.4 memory leaks
src/sanitizer_memory_leaks.cpp 代碼如下:
#include <stdlib.h>char func4() {char *x = (char*)malloc(10 * sizeof(char*));return x[5]; }int main(int argc, char *argv[]) {func4();return 0; }ASAN_OPTIONS=detect_leaks=1 ./sanitizer_memory_leaks
================================================================= ==5501==ERROR: LeakSanitizer: detected memory leaksDirect leak of 80 byte(s) in 1 object(s) allocated from:#0 0x7f4d1a3848a0 in malloc (/lib64/libasan.so.4+0xde8a0)#1 0x400848 in func4() /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_memory_leaks.cpp:4#2 0x4008a2 in main /root/Public/cmake_code/cmake_project/app/sanitizer/src/sanitizer_memory_leaks.cpp:9#3 0x7f4d196db554 in __libc_start_main (/lib64/libc.so.6+0x22554)SUMMARY: AddressSanitizer: 80 byte(s) leaked in 1 allocation(s).3. sanitizer 原理介紹
AddressSanitizer主要包括兩部分:
- 插樁(Instrumentation)
- 動態(tài)運行庫(Run-time library)。
插樁主要是針對在llvm編譯器級別對訪問內(nèi)存的操作(store,load,alloca等),將它們進(jìn)行處理。
動態(tài)運行庫主要提供一些運行時的復(fù)雜的功能(比如poison/unpoison shadow memory)以及將malloc,free等系統(tǒng)調(diào)用函數(shù)hook住。
該算法的思路是:如果想防住Buffer Overflow漏洞,只需要在每塊內(nèi)存區(qū)域右端(或兩端,能防overflow和underflow)加一塊區(qū)域(RedZone),使RedZone的區(qū)域的影子內(nèi)存(Shadow Memory)設(shè)置為不可寫即可。具體的示意圖如下圖所示。
- 內(nèi)存映射
AddressSanitizer保護(hù)的主要原理是對程序中的虛擬內(nèi)存提供粗粒度的影子內(nèi)存(每8個字節(jié)的內(nèi)存對應(yīng)一個字節(jié)的影子內(nèi)存),為了減少overhead,采用了直接內(nèi)存映射策略,所采用的具體策略如下:Shadow=(Mem >> 3) + offset。每8個字節(jié)的內(nèi)存對應(yīng)一個字節(jié)的影子內(nèi)存,影子內(nèi)存中每個字節(jié)存取一個數(shù)字k,如果k=0,則表示該影子內(nèi)存對應(yīng)的8個字節(jié)的內(nèi)存都能訪問,如果0<k<7,表示前k個字節(jié)可以訪問,如果k為負(fù)數(shù),不同的數(shù)字表示不同的錯誤(e.g. Stack buffer overflow, Heap buffer overflow)。
- 插樁
為了防止buffer overflow,需要將原來分配的內(nèi)存兩邊分配額外的內(nèi)存Redzone,并將這兩邊的內(nèi)存加鎖,設(shè)為不能訪問狀態(tài),這樣可以有效的防止buffer overflow(但不能杜絕buffer overflow)。以下是在棧中插樁的一個例子。
未插樁的代碼:
插樁后的代碼:
插樁后的代碼:
在動態(tài)運行庫中將malloc/free函數(shù)進(jìn)行了替換。在malloc函數(shù)中額外的分配了Redzone區(qū)域的內(nèi)存,將與Redzone區(qū)域?qū)?yīng)的影子內(nèi)存加鎖,主要的內(nèi)存區(qū)域?qū)?yīng)的影子內(nèi)存不加鎖。
free函數(shù)將所有分配的內(nèi)存區(qū)域加鎖,并放到了隔離區(qū)域的隊列中(保證在一定的時間內(nèi)不會再被malloc函數(shù)分配),可檢測Use after free類的問題。
詳細(xì)了解ASan算法原理可以訪問以下地址:
https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm
4. 對比 sanitizer 和 valgrind
4.1 sanitizer
-
包括address, memory, leak等多種sanitizer檢測工具
-
在使用gcc或者clang編譯時,加入額外編譯選項"-fsanitize=leak"
-
memsanitizer和leaksanitizer只能夠在clang中使用
-
能夠準(zhǔn)確檢測出任何memory leak或者error
-
如果需要定位到源文件,需要指定以下環(huán)境: (否則只會定位到內(nèi)存地址)
export ASAN_OPTIONS=‘a(chǎn)bort_on_error=1’ ==> 過將環(huán)境變量 ASAN_OPTIONS 修改成如下形式來迫使軟件崩潰
-
優(yōu)點: 定位準(zhǔn)確, 檢查全面, 性能預(yù)計降低2倍左右
-
缺點: 需要重新編譯可執(zhí)行文件
4.2 valgrind
linux平臺下的內(nèi)存檢測工具包含多種tool包
- massif
- memcheck
輸出:內(nèi)存泄漏、越界的代碼位置
作用:檢測內(nèi)存泄漏或者內(nèi)存越界。
如果是debug版本的程序,可以直接定位到行。
Note:still reacheable部分可以忽略。
常見問題:
5.1 malloc, calloc 與free不配對提前return或者goto使用時,造成possible leak
5.2 free 多次同一內(nèi)存free未初始化的內(nèi)存
5.3 如果使用了tcmalloc4 代替原始的malloc, 會使得valgrind失效
-
優(yōu)點: 可以對任何可執(zhí)行文件使用, 可視化圖像顯示內(nèi)存使用
-
缺點: 常常會有誤報, 受編譯環(huán)境影響較大, 性能預(yù)計降低10倍左右
4. 參考資料
- https://www.jianshu.com/p/9e85345e500b
- https://www.bynav.com/cn/resource/bywork/healthy-work/70.html
- https://juejin.im/post/6844904067538370573
總結(jié)
以上是生活随笔為你收集整理的内存检测工具:sanitizer的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 内存错误检测工具——kfence工作原理
- 下一篇: 在线租车app开发