ASAN分析内存相关问题

1.简介

  在使用ASAN(Address Sanitizer,也就是地址消毒技术)之前,很多内存检查都使用valgrind,通过valgrind工具,检查内存问题,例如:堆栈溢出,段错误等等。 ASAN可以用来检查内存问题,例如:堆栈溢出,悬空指针的非法访问等。并且相比Valgrind工具,使用ASAN作为内存错误监测工具对程序性能损耗也非常低。 对比Valgrind只能检测到堆内存的越界访问和悬空指针的访问,ASAN不仅可以检测到堆内存的越界和悬空指针的访问,还能检测到栈和全局对象的越界访问。 所以ASAN成为目前较优秀的内存检测工具。   简单原理解释:

  1. 接管malloc的流程
  2. 物理内存有限,虚拟内存无限,分配过的内存空间free后,不在继续分配
  3. 每次分配内存,一定使用全新的虚拟地址,并且在地址两端加页保护,两端页面做读写保护(red zone) # 2.ASAN使用   asan的使用,编译时,需要增加asan选项,也就是配置开启GCC自带的asan。 ## 2.1 gcc版本要求 对gcc版本有要求,gcc版本>4.8 可以搜索一下libasan.so看看是否存在。 ## 2.2 如何使用 编译时需要增加flag选项
    CFLAGS += -fsanitize=address -g
    -fsanitize=address ------asan添加进入编译中
    -g                 ------增加堆栈信息,看到函数名称
    -fsanitize-recover=address    -----asan检查到错误后,不core继续运行,需要配合环境变量
    gcc版本>6 ASAN_OPTIONS=halt_on_error=0:report_path=xxx使用
    
    链接时,需要链接libasan.soe
    LDFLAGS = -lasan -L /usr/lib64/
    #-lasan   链接libasan.so
    #-L /usr/lib64  从这个目录搜索libasan.so
    
    运行时,可能提示缺失libasan.so,所以运行时链接上
    export LD_LIBRARY_PATH=/usr/lib64
    
    ## 2.3 使用配置 ASAN_OPTIONS是Address-Sanitizier的运行选项环境变量
常见的配置选项
halt_on_error=0       检测内存错误后继续运行
detect_leaks=1:         使能内存泄露检测
malloc_context_size=10:内存错误发生时,显示的调用栈层数为10
log_path=/asan.log:     内存检查问题日志存放文件路径
suppressions=$SUPP_FILE:屏蔽打印某些内存错误

除了上述常用选项,以下还有一些选项可根据实际需要添加:
detect_stack_use_after_return=1  检查访问指向已被释放的栈空间
handle_segv=1                    处理段错误;也可以添加handle_sigill=1处理SIGILL信号
quarantine_size=4194304:           内存cache可缓存free内存大小4M

可以参考的ASAN_OPTIONS配置项:

export ASAN_OPTIONS=halt_on_error=0:detect_leaks=1:malloc_context_size=10:log_path=./asan.log

备注:log是需要程序可以正常运行才有的,如果出现错误或者kill等方式退出,是没有log的

3.ASAN检测的内存错误种类分析

  1. 栈内存越界
  2. 堆内存越界
  3. 全局变量越界
  4. 内存泄露
  5. 悬空指针使用 测试demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//导入运行时libasan.so库路径
//export LD_LIBRARY_PATH=/usr/lib64


//export ASAN_OPTIONS=halt_on_error=0:detect_leaks=1:malloc_context_size=15:log_path=./asan.log

#if 1


//Heap OOB HeapOutOfBounds 堆内存越界
void Heap_OOB_test()
{
    char *buf = (char *)malloc(100);
    memcpy(buf+100,"dong",5);
    free(buf);

}

//Stack OOB(StackOutOfBounds 栈越界)
void Stack_OOB_test()
{
    int stack_array[100];
    stack_array[1] = 0;
    stack_array[1 + 100] = 100;  // BOOM
}

//Global OOB(GlobalOutOfBounds 全局变量越界)
int global_array[100] = {0};
void Global_OOB_test()
{
    global_array[1 + 100] = 100;
}

//UAF(Use_After_Free 内存释放后使用)
void use_after_free_test()
{
    int * array = (int *)malloc(sizeof(int) * 100);
    free(array);
    array[1] = 1;
}

//UAR(Use_After_Return 栈内存回收后使用,
//默认未开启,开启ASAN_OPTIONS=detect_stack_use_after_return=1)
void use_after_return_test()
{

    int *ptr;
    {
        {
            int local[100];
            ptr = &local[0];
        }
    }
    ptr[1] = 0;
}

//内存泄露
char* memory_leak_test()
{
    char *buf = (char *)malloc(100);
    memcpy(buf,"xxxxx",6);
    return buf;
}

int main(int argc, char **argv) 
{
    printf("----test file: %s\r\n",__FILE__);
    printf("---function: asan test \r\n");

    //堆内存溢出
    Heap_OOB_test();

    //栈内存溢出
    Stack_OOB_test();

    //全局变量溢出
    Global_OOB_test();

    //释放后使用,悬空指针使用
    use_after_free_test();

    //栈内存释放后再使用
    use_after_return_test();

    //内存泄露
    memory_leak_test();

    return 0;
}
#endif

测试结果:

编译选项(开启asan,允许错误继续)
CFLAGS += -g -fsanitize=address  -fsanitize-recover=address

asan option
export ASAN_OPTIONS=halt_on_error=0:detect_leaks=1:malloc_context_size=15:log_path=./asan.log
=================================================================
==101527==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff68fa1634 at pc 0x000000400b07 bp 0x7fff68fa1470 sp 0x7fff68fa1468
WRITE of size 4 at 0x7fff68fa1634 thread T0
    #0 0x400b06 in Stack_OOB_test /dong/workspace/linuxcode/asan-test.c:29
    #1 0x400c4b in main /dong/workspace/linuxcode/asan-test.c:79
    #2 0x7f974dab483f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
    #3 0x400938 in _start (/dong/workspace/linuxcode/build/linuxcode+0x400938)

Address 0x7fff68fa1634 is located in stack of thread T0 at offset 436 in frame
    #0 0x400a05 in Stack_OOB_test /dong/workspace/linuxcode/asan-test.c:26

  This frame has 1 object(s):
    [32, 432) 'stack_array' <== Memory access at offset 436 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 /dong/workspace/linuxcode/asan-test.c:29 in Stack_OOB_test
Shadow bytes around the buggy address:
  0x10006d1ec270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec290: f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec2a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec2b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10006d1ec2c0: 00 00 00 00 00 00[f2]f2 f3 f3 f3 f3 00 00 00 00
  0x10006d1ec2d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec2e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec2f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006d1ec310: 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
  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
=================================================================
==101527==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000601734 at pc 0x000000400ba8 bp 0x7fff68fa1670 sp 0x7fff68fa1668
WRITE of size 4 at 0x000000601734 thread T0
    #0 0x400ba7 in Global_OOB_test /dong/workspace/linuxcode/asan-test.c:36
    #1 0x400c55 in main /dong/workspace/linuxcode/asan-test.c:82
    #2 0x7f974dab483f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
    #3 0x400938 in _start (/dong/workspace/linuxcode/build/linuxcode+0x400938)

从上述信息就可以分析出当前程序出现的各类内存问题(备注:本例程有些错误没有被检测到,后续继续深入探讨)。

4.备注

通过asan的开启可以比较优秀的发现我们在编程过程中,出现的一些错误,但是任何事物在使用时也需要足够清楚的了解 这项技术的大体原理,以避免在使用中进入新的坑。 ASAN工具主要由两个部分组成,编译插桩模块运行库运行库:libasan.so.x 这个库会接管malloc和free函数,malloc执行完毕后,已经分配的内存前后(“红区”), 会被标记为“中毒”状态,而释放的内存则会被隔离起来(暂时不会分配出去)且也会被标记成“中毒”状态。 这种情况下:内存地址可能存在一直增长,系统运行时间一长,可能导致出现内存分配不到(这个需要注意!!)