Sanitizers使用介绍

Sanitizersers是一个工具集合,由google开发并开源,项目地址sanitizers 。Sanitizers包括系列工具:

  • AddressSanitizer,检测内存访问问题
  • MemorySanitizer,检测未初始化内存问题
  • ThreadSanitizer,检测线程竞态和死锁问题
  • LeakSanitizer,检测内存泄露问题

sanitizers自gcc4.8加入,即编译器自带。相比于我第一个了解的检测工具valgrind,它对程序性能的影响更小,用过valgrind的就知道,使用valgrind后
程序的性能大大降低。大多数的这类工具的基本原理都差不多,就是将原程序的相关函数替换,然后里面加入分析手段。

AddressSanitizer(ASan)

ASan是一个C/C++的内存错误检测工具,它能够检测:

  • Use after free,悬空指针引用,也叫野指针引用,即对free后的内存进行存取操作。
  • Heap buffer overflow,堆溢出访问,即对堆的操作越界了。
  • Stack buffer overflow,栈溢出访问,即对栈的操作越界了。
  • Global buffer overflow,全局缓冲区溢出,例如全局的数组这些,反正都是越界访问,只不过位于程序的不同segment。
  • Use after return,即栈的野指针,例如A函数调用B函数,A函数有个指针传入到B中,B把它赋值指向了B的一个局部变量,即栈变量,B返回到A后,A操作该指针的错误。
  • Use after scope,和Use after return有点类似,C/C++的{}表示一个scope。
  • Initialization order bugs
  • Memory leaks,内存泄露,AddressSanitizer集成了LeakSanitizer的功能。

Use after free

#include <stdlib.h>

int main(int argc, char *argv[])
{
    char *p = malloc(10);
    free(p);
    p[0] = 1;
    return 0;
}

编译时,加上-fsanitize=address选项,选择sanitizers的AddressSanitizer功能,-g有助于输出调试符号信息,例如栈回溯的文件,行号等信息。
编译:

gcc -g -fsanitize=address main.c
./a.out

输出:

=================================================================
==26300==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x558bf3e0a22a bp 0x7ffcdcccedf0 sp 0x7ffcdcccede0
WRITE of size 1 at 0x602000000010 thread T0
    #0 0x558bf3e0a229 in main /home/thomas/test/ctest/main.c:7
    #1 0x7fec69da3082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x558bf3e0a10d in _start (/home/thomas/test/ctest/a.out+0x110d)

0x602000000010 is located 0 bytes inside of 10-byte region [0x602000000010,0x60200000001a)
freed by thread T0 here:
    #0 0x7fec6a07e40f in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:122
    #1 0x558bf3e0a1f5 in main /home/thomas/test/ctest/main.c:6
    #2 0x7fec69da3082 in __libc_start_main ../csu/libc-start.c:308

previously allocated by thread T0 here:
    #0 0x7fec6a07e808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x558bf3e0a1e5 in main /home/thomas/test/ctest/main.c:5
    #2 0x7fec69da3082 in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: heap-use-after-free /home/thomas/test/ctest/main.c:7 in main
Shadow bytes around the buggy address:
  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[fd]fd fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: 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
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  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
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==26300==ABORTING

从上面的输出可以看出,一条Use after free的报错,包含几个部分:

  • 1.进程号,错误类型,操作是读,还是写,操作的地址,线程号等,以及栈的回溯信息
==26300==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x558bf3e0a22a bp 0x7ffcdcccedf0 sp 0x7ffcdcccede0
WRITE of size 1 at 0x602000000010 thread T0
    #0 0x558bf3e0a229 in main /home/thomas/test/ctest/main.c:7
    #1 0x7fec69da3082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x558bf3e0a10d in _start (/home/thomas/test/ctest/a.out+0x110d)
  • 2.对此块内存的操作的具体位置,对10字节的内存区域[0x602000000010,0x60200000001a),的第0个字节操作。
0x602000000010 is located 0 bytes inside of 10-byte region [0x602000000010,0x60200000001a)
freed by thread T0 here:
    #0 0x7fec6a07e40f in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:122
    #1 0x558bf3e0a1f5 in main /home/thomas/test/ctest/main.c:6
    #2 0x7fec69da3082 in __libc_start_main ../csu/libc-start.c:308
  • 3.此块内存区域在那个线程,哪个地方分配的
previously allocated by thread T0 here:
    #0 0x7fec6a07e808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x558bf3e0a1e5 in main /home/thomas/test/ctest/main.c:5
    #2 0x7fec69da3082 in __libc_start_main ../csu/libc-start.c:308

后面那些信息是AddressSanitizer的内部收集的标记信息。

Heap buffer overflow

#include <stdlib.h>

int main(int argc, char *argv[])
{
    char *p = malloc(10);
    p[10] =  1;
    free(p);
    return 0;
}

编译运行,输出,这里需要注意一个内存区域的表达方式region [0x602000000010,0x60200000001a),类似于数学里面区间的表达方式,[, ]表示区域包括此地址,
()表示区域不包括此地址,这里区域右边的0x60200000001a是不包含的,但是操作却写了这个地址的空间,所以报错了。

=================================================================
==26705==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000001a at pc 0x558f03d1f226 bp 0x7ffe54895de0 sp 0x7ffe54895dd0
WRITE of size 1 at 0x60200000001a thread T0
    #0 0x558f03d1f225 in main /home/thomas/test/ctest/main.c:6
    #1 0x7f95c6524082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x558f03d1f10d in _start (/home/thomas/test/ctest/a.out+0x110d)

0x60200000001a is located 0 bytes to the right of 10-byte region [0x602000000010,0x60200000001a)
allocated by thread T0 here:
    #0 0x7f95c67ff808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x558f03d1f1e5 in main /home/thomas/test/ctest/main.c:5
    #2 0x7f95c6524082 in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/thomas/test/ctest/main.c:6 in main
.........
.........
.........
.........

Stack buffer overflow

#include <stdlib.h>
int main(int argc, char *argv[])
{
    char p[10];
    p[10] = 1;
    return 0;
}

编译,运行输出,这里栈的区域表达和堆的不同它使用的是当前栈指针的偏移,例如这里的:

[32, 42) 'p' (line 4) <== Memory access at offset 42 overflows this variable
=================================================================
==26927==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffe0389d7a at pc 0x5577fcfd7289 bp 0x7fffe0389d30 sp 0x7fffe0389d20
WRITE of size 1 at 0x7fffe0389d7a thread T0
    #0 0x5577fcfd7288 in main /home/thomas/test/ctest/main.c:5
    #1 0x7f97e9d0d082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x5577fcfd710d in _start (/home/thomas/test/ctest/a.out+0x110d)

Address 0x7fffe0389d7a is located in stack of thread T0 at offset 42 in frame
    #0 0x5577fcfd71d8 in main /home/thomas/test/ctest/main.c:3

  This frame has 1 object(s):
    [32, 42) 'p' (line 4) <== Memory access at offset 42 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/thomas/test/ctest/main.c:5 in main
........
........
........
........

Global buffer overflow

#include <stdlib.h>
char p[10]={0};
int main(int argc, char *argv[])
{
    p[10] = 1;
    return 0;
}

编译,运行输出:

=================================================================
==27271==ERROR: AddressSanitizer: global-buffer-overflow on address 0x5632b22930aa at pc 0x5632b2290213 bp 0x7ffeba0b5140 sp 0x7ffeba0b5130
WRITE of size 1 at 0x5632b22930aa thread T0
    #0 0x5632b2290212 in main /home/thomas/test/ctest/main.c:5
    #1 0x7f90fd627082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x5632b229010d in _start (/home/thomas/test/ctest/a.out+0x110d)

0x5632b22930aa is located 0 bytes to the right of global variable 'p' defined in 'main.c:2:6' (0x5632b22930a0) of size 10
SUMMARY: AddressSanitizer: global-buffer-overflow /home/thomas/test/ctest/main.c:5 in main
.......
.......
.......

有个现象是,如果全局变量p位于bss段,ASan好像检测不出来,即全局变量p不赋初始值时,原因未知。

Use after return

char *p;
void foo()
{
    char a[10];
    p = &a[0];
}

int main(int argc, char *argv[])
{
    foo();
    return *p;
}

运行前添加环境变量ASAN_OPTIONS=detect_stack_use_after_return=1,好像没错误输出,查了相关资料,好像是说这个类型的检测不稳定,有的环境可以,有的不行。

Use after scope

int main()
{
    int* p;
    {
        int a = 1;
        p = &a;
    }
    *p = 2;
    return 0;
}

编译额外带上-fsanitize-address-use-after-scope,运行带上环境变量ASAN_OPTIONS=detect_stack_use_after_scope=0,我的环境这些都可以
不用带,gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0。编译,运行输出:

=================================================================
==28003==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffe8d7d5280 at pc 0x56336b7972d3 bp 0x7ffe8d7d5240 sp 0x7ffe8d7d5230
WRITE of size 4 at 0x7ffe8d7d5280 thread T0
    #0 0x56336b7972d2 in main /home/thomas/test/ctest/main.c:9
    #1 0x7fe84a92e082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x56336b79710d in _start (/home/thomas/test/ctest/a.out+0x110d)

Address 0x7ffe8d7d5280 is located in stack of thread T0 at offset 32 in frame
    #0 0x56336b7971d8 in main /home/thomas/test/ctest/main.c:2

  This frame has 1 object(s):
    [32, 36) 'a' (line 5) <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope /home/thomas/test/ctest/main.c:9 in main

Initialization order bugs

和C++相关的一个错误,暂不分析

Memory leaks

非常常见的c/c++问题,

#include <stdlib.h>
int main()
{
    malloc(10);
    return 0;
}

编译,运行输出:

=================================================================
==28220==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 10 byte(s) in 1 object(s) allocated from:
    #0 0x7f99d3980808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x55f5ddef019a in main /home/thomas/test/ctest/main.c:4
    #2 0x7f99d36a5082 in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: 10 byte(s) leaked in 1 allocation(s).

官方的Wiki里面说支持X86_64的linux系统和OS X系统,也就是说ARM系列的不支持?支持情况是:

OS x86 x86_64 ARM ARM64 MIPS MIPS64 PowerPC PowerPC64
Linux yes yes yes yes yes yes
OS X yes yes
iOS Simulator yes yes
FreeBSD yes yes
Android yes yes yes yes

其他OS/arch的组合支持情况是也可以工作,但是未积极的开发测试。

posted @ 2023-02-13 18:05  thammer  阅读(1154)  评论(0编辑  收藏  举报