1.简介
在使用ASAN(Address Sanitizer,也就是地址消毒技术)之前,很多内存检查都使用valgrind,通过valgrind工具,检查内存问题,例如:堆栈溢出,段错误等等。 ASAN可以用来检查内存问题,例如:堆栈溢出,悬空指针的非法访问等。并且相比Valgrind工具,使用ASAN作为内存错误监测工具对程序性能损耗也非常低。 对比Valgrind只能检测到堆内存的越界访问和悬空指针的访问,ASAN不仅可以检测到堆内存的越界和悬空指针的访问,还能检测到栈和全局对象的越界访问。 所以ASAN成为目前较优秀的内存检测工具。 简单原理解释:
- 接管malloc的流程
- 物理内存有限,虚拟内存无限,分配过的内存空间free后,不在继续分配
- 每次分配内存,一定使用全新的虚拟地址,并且在地址两端加页保护,两端页面做读写保护(red zone)
# 2.ASAN使用
asan的使用,编译时,需要增加asan选项,也就是配置开启GCC自带的asan。
## 2.1 gcc版本要求
对gcc版本有要求,gcc版本>4.8 可以搜索一下libasan.so看看是否存在。
## 2.2 如何使用
编译时需要增加flag选项链接时,需要链接libasan.soe
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.so,所以运行时链接上LDFLAGS = -lasan -L /usr/lib64/ #-lasan 链接libasan.so #-L /usr/lib64 从这个目录搜索libasan.so
## 2.3 使用配置 ASAN_OPTIONS是Address-Sanitizier的运行选项环境变量export LD_LIBRARY_PATH=/usr/lib64
常见的配置选项
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检测的内存错误种类分析
- 栈内存越界
- 堆内存越界
- 全局变量越界
- 内存泄露
- 悬空指针使用 测试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执行完毕后,已经分配的内存前后(“红区”), 会被标记为“中毒”状态,而释放的内存则会被隔离起来(暂时不会分配出去)且也会被标记成“中毒”状态。 这种情况下:内存地址可能存在一直增长,系统运行时间一长,可能导致出现内存分配不到(这个需要注意!!)
您还没有登录,请您登录后发表评论。