AddressSanitizer(ASan)

ASAN功能介绍

AddressSanitizer(ASan)工具是用于用户空间程序的内存错误检测工具,无法直接用于内核空间。

ASAN早先是LLVM中的特性,后被加入gcc4.8,成为 gcc 的一部分,但不支持符号信息,无法显示出问题的函数和行数。从 4.9 开始,gcc 支持 AddressSanitizer 的所有功能。因此gcc 4.8以上版本使用ASAN时不需要安装第三方库,通过在编译时指定编译CFLAGS即可打开开关。

ASan利用编译器插桩和运行时库来检测内存错误,例如缓冲区溢出、使用未初始化的内存等。但是,内核和用户空间程序的执行环境和内存管理方式不同,因此ASan无法直接应用于内核空间。

对于内核空间的内存错误检测,可以考虑使用其他专门针对内核开发的工具,如KASAN(Kernel AddressSanitizer)。KASAN是Linux内核中的一个内存错误检测工具,可以用于检测内核空间的内存问题。后续我们介绍学习。

优点:

  1. 检测能力强大:ASan可以检测到许多内存相关的错误,如缓冲区溢出、使用未初始化的内存、使用已释放的内存等。它通过在运行时对程序进行插桩来捕获并报告这些错误,帮助开发人员及早发现和修复问题。
  2. 低侵入性:ASan工具通过编译器插桩和运行时库来实现内存错误检测,对代码的更改较少,对现有代码的侵入性较小。这使得它相对容易集成到现有项目中,而无需进行大规模的代码修改。
  3. 容易定位问题:当ASan检测到内存错误时,它会提供详细的错误报告,包括错误类型、触发位置和调用栈信息。这有助于开发人员快速定位和修复问题,减少调试时间。
  4. 平台支持广泛:ASan工具在多个平台和编译器中都有支持,包括Linux、macOS和Windows等。这使得开发人员可以在不同的环境中使用相同的工具,提高了可移植性和开发效率。

缺点:

  1. 内存开销较大:ASan会在运行时为每个分配的内存块添加额外的元数据,并在每次内存访问时进行检查。这会导致内存使用量增加,有时可能会显著影响程序的性能和内存占用。
  2. 编译时间延长:由于ASan需要对程序进行插桩,编译时间可能会延长。特别是对于大型项目或包含大量代码的程序,这可能会导致较长的编译时间。
  3. 不支持所有错误类型:尽管ASan可以检测到许多内存相关的错误,但它并不能覆盖所有可能的问题。某些类型的错误,如数据竞争和内存泄漏,ASan无法直接检测到。
  4. 可能引入假阳性:由于ASan是在运行时进行检测的,它可能会产生一些误报(假阳性),即报告了实际上并非错误的情况。这可能会增加调试和排除问题的复杂性。

综上所述,ASan作为一种内存错误检测工具,具有很多优点,但也存在一些缺点。在使用ASan之前,需要权衡其带来的利弊,根据具体情况选择适合的工具和方法来确保代码的质量和可靠性。

ASAN介绍及使用

which aarch64-none-linux-gnu-gcc
/usr/local/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc

./aarch64-none-linux-gnu/lib64/libasan.so
./aarch64-none-linux-gnu/libc/usr/lib64/libasan.so

gcc编译选项

#-fsanitize=address:开启内存越界检测

#-fsanitize-recover=address:一般后台程序为保证稳定性,不能遇到错误就简单退出,而是继续运行,采用该选项支持内存出错之后程序继续运行,需要叠加设置ASAN_OPTIONS=halt_on_error=0才会生效;若未设置此选项,则内存出错即报错退出

#-fno-stack-protector:去使能栈溢出保护

#-fno-omit-frame-pointer:去使能栈溢出保护

test.c测试程序:

//命名test.c
#include <stdio.h>
#include <stdlib.h>

char* getMemory()
{
	char *p = (char *)malloc(30);
	return p;
}

int main()
{
	char *p = getMemory();
	p = NULL;

	return 0;
}
//这段代码存在内存泄漏的问题。在函数getMemory中,它使用malloc函数动态分配了内存,并返回指向该内存的指针。
//然而,在main函数中,获取到指向动态分配内存的指针后,立即将指针赋值为NULL,导致之前分配的内存地址丢失,
//无法再被释放。

ASAN_OPTIONS设置:

ASAN_OPTIONS是Address-Sanitizier的运行选项环境变量。

#halt_on_error=0:检测内存错误后继续运行

#detect_leaks=1:使能内存泄露检测

#malloc_context_size=15:内存错误发生时,显示的调用栈层数为15

#log_path=/home/asan.log:内存检查问题日志存放文件路径

#env |grep ASAN_OPTIONS

设置环境变量,

# export ASAN_OPTIONS=halt_on_error=0:use_sigaltstack=0:detect_leaks=1:malloc_context_size=15:log_path=/home/asan.log
# env |grep ASAN_OPTIONS
# gcc test.c -fsanitize=address -fsanitize-recover=address -fno-stack-protector -fno-omit-frame-pointer

运行a.out会生成acan检出的log, /home/asan.log.60920,如下:

=================================================================
==114141==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 30 byte(s) in 1 object(s) allocated from:
    #0 0x7fb16294bb40 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb40)
    #1 0x55ca068137fb in getMemory (/home/wuqianlong/a.out+0x7fb)
    #2 0x55ca06813817 in main (/home/wuqianlong/a.out+0x817)
    #3 0x7fb16249dc86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)

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

动态库的asan日志定位

现在创建两个文件,test.c和test1.c, test.c的内容如上述内容。

//test1.c
#include <stdio.h>
#include <stdlib.h>

extern char* getMemory();

int main()
{
	char *p = getMemory();
	p = NULL;

	return 0;
}

将test.c编译成共享库libtest.so

gcc -shared -o libtest.so test.c  -fsanitize=address -fsanitize-recover=address -fno-stack-protector -fPIC

链接共享库,编译test1.c成a.out文件

gcc -o a.out test1.c /home/wuqianlong/libtest.so -fsanitize=address -fsanitize-recover=address -fno-stack-protector

运行a.out文件生成asan log如下:

=================================================================
==89713==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 30 byte(s) in 1 object(s) allocated from:
    #0 0x7f0efbcefb40 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb40)
    #1 0x7f0efba0f6eb in getMemory (/home/wuqianlong/libtest.so+0x6eb)
    #2 0x5570669f283b in main (/home/wuqianlong/a.out+0x83b)
    #3 0x7f0efb63fc86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)

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

案例

1.堆访问越界(heap-buffer-overflow)

#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    int *array = malloc(sizeof (int) * 100);
    array[0] = 0;
    int res = array[1 + 100]; //array访问越界
    free(array);

    pause();//程序等待,不退出
    return 0;
}

编译命令:

gcc heapOOB.c -o heapOOB -g -fsanitize=address -fsanitize=leak

执行情况:

==3653==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61400000ffd4 at pc 0x000000400871 bp 0x7ffe50cde9c0 sp 0x7ffe50cde9b0
READ of size 4 at 0x61400000ffd4 thread T0
#0 0x400870 in main /home/jetpack/work/4G/test/asan/heapOOB.c:7
#1 0x7f30b337a83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400708 in _start (/home/jetpack/work/4G/test/asan/heapOOB+0x400708)
0x61400000ffd4 is located 4 bytes to the right of 400-byte region [0x61400000fe40,0x61400000ffd0)
allocated by thread T0 here:
#0 0x7f30b37bc602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x4007ee in main /home/jetpack/work/4G/test/asan/heapOOB.c:5
#2 0x7f30b337a83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

2.栈访问越界(stack-buffer-overflow)

#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) 
{
    int stack_array[100];
    stack_array[100] = 0;//栈访问越界

    pause();
    return 0; 
}

编译命令:

gcc stackOOB.c -o stackOOB -g -fsanitize=address -fsanitize=leak

执行结果:

==3952==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffdb72e830 at pc 0x0000004008ef bp 0x7fffdb72e660 sp 0x7fffdb72e650
WRITE of size 4 at 0x7fffdb72e830 thread T0
#0 0x4008ee in main /home/jetpack/work/4G/test/asan/stackOOB.c:6
#1 0x7f0c47a8e83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400748 in _start (/home/jetpack/work/4G/test/asan/stackOOB+0x400748)
Address 0x7fffdb72e830 is located in stack of thread T0 at offset 432 in frame
#0 0x400825 in main /home/jetpack/work/4G/test/asan/stackOOB.c:4
This frame has 1 object(s):
[32, 432) 'stack_array' <== Memory access at offset 432 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)

3.使用已经释放的内存(UseAfterFree)

#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) 
{
    int *array = malloc(sizeof (int) * 100);
    array[0] = 0;
    free(array);
    int res = array[0]; //使用已经释放的内存

    pause();
    return 0;
}

编译命令:

gcc heapUAF.c -o heapUAF -g -fsanitize=address -fsanitize=leak

执行结果:

==4385==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe40 at pc 0x000000400877 bp 0x7ffdc9019c20 sp 0x7ffdc9019c10
READ of size 4 at 0x61400000fe40 thread T0
#0 0x400876 in main /home/jetpack/work/4G/test/asan/heapUAF.c:8
#1 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400708 in _start (/home/jetpack/work/4G/test/asan/heapUAF+0x400708)
0x61400000fe40 is located 0 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0)
freed by thread T0 here:
#0 0x7f93f91982ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x40083f in main /home/jetpack/work/4G/test/asan/heapUAF.c:7
#2 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
previously allocated by thread T0 here:
#0 0x7f93f9198602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x4007ee in main /home/jetpack/work/4G/test/asan/heapUAF.c:5
#2 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

4.内存泄漏 (Memory Leak)

#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) 
{
    int *array = malloc(sizeof (int) * 100);
    memset(array, 0, 100 * 4);

    return 0;
}
//内存泄漏:在 main 函数中通过调用 malloc 分配了内存空间给 array,但在函数结束时并未调用 free 来释放这块内存。这将导致在每次程序运行时都会分配一块新的内存空间,而不会释放之前分配的内存,最终可能导致内存泄漏。

编译命令:

gcc heapLeak.c -o heapLeak -g -fsanitize=address -fsanitize=leak

执行结果:

==3120==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 400 byte(s) in 1 object(s) allocated from:
#0 0x7f412d5b7602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x40073e in main /home/jetpack/work/4G/test/asan/heapLeak.c:7
#2 0x7f412d17583f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

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

5.函数返回的局部变量访问

int *ptr;
void usr_func(void)
{    
    //申请栈内存
    int local[100] = {0};
    ptr = local;
}    
     
int main(void)
{    
    //使用函数栈返回的内存
    *ptr = 0;
    return 0;                                                                                                                                                                                                                                
}

6.全局变量访问越界

int global_array[100] = {0};                                                                                                                                                                                                                 
 
int main(int argc, char **argv) 
{
    //全局变量global_array访问越界 
    global_array[101];
    return 0;
}

感谢一起学习,有疑问评论区讨论。

相关推荐
Komorebi.py19 分钟前
【Linux】-学习笔记05
linux·笔记·学习
Mr_Xuhhh25 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
内核程序员kevin3 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
zy张起灵5 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
朝九晚五ฺ8 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream8 小时前
Linux的桌面
linux
xiaozhiwise8 小时前
Makefile 之 自动化变量
linux
意疏11 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu11 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器