Address Sanitizer

一、 简介

Address Sanitizer(ASan)是一个快速的内存错误检测工具。它非常快,只拖慢程序两倍左右(比起Valgrind快多了)。它包括一个编译器instrumentation模块和一个提供malloc()/free()替代项的运行时库。

从gcc 4.8开始,AddressSanitizer成为gcc的一部分。当然,要获得更好的体验,最好使用4.9及以上版本,因为gcc 4.8的AddressSanitizer还不完善,最大的缺点是没有符号信息。

二、 使用步骤

  1. 用-fsanitize=address选项编译和链接你的程序。
  2. 用-fno-omit-frame-pointer编译,以得到更容易理解stack trace。
gcc -fsanitize=address -fno-omit-frame-pointer -O0 -g xxx.c -o xxx

运行产出的elf。如果发现了错误,就会打印出类似下面的信息:

==9901==ERROR: AddressSanitizer: heap-use-after-free on address 0x60700000dfb5 
  at pc 0x45917b bp 0x7fff4490c700 sp 0x7fff4490c6f8 
READ of size 1 at 0x60700000dfb5 thread T0
    #0 0x45917a in main use-after-free.c:5
    #1 0x7fce9f25e76c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
    #2 0x459074 in _start (a.out+0x459074)0x60700000dfb5 is located 5 bytes inside of 80-byte region [0x60700000dfb0,0x60700000e000)
freed by thread T0 here:
    #0 0x4441ee in __interceptor_free projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64
    #1 0x45914a in main use-after-free.c:4
    #2 0x7fce9f25e76c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
previously allocated by thread T0 here:
    #0 0x44436e in __interceptor_malloc projects/compiler-rt/lib/asan/asan_malloc_linux.cc:74
    #1 0x45913f in main use-after-free.c:3
    #2 0x7fce9f25e76c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
SUMMARY: AddressSanitizer: heap-use-after-free use-after-free.c:5 main

第一部分(ERROR)指出错误类型是heap-use-after-free。

第二部分(READ),

  • 指出线程名thread T0,操作为READ,发生的位置是use-after-free.c:5。
  • 该heapk块之前已经在use-after-free.c:4被释放了;
  • 该heap块是在use-fater-free.c:3分配。

第三部分 (SUMMARY) 前面输出的概要说明。

三、工作原理

  1. shadow memory

在这里插入图片描述
Shadow memory和normal memory的映射关系如上图所示。一个byte的shadow memory反映8个byte normal memory的状态。

  1. 检测算法
    检测用伪代码,每次内存访问都会通过shadow mem 去检查访问是否合规
ShadowAddr = (Addr >> 3) + Offset;
k = *ShadowAddr;
if (k != 0 && ((Addr & 7) + AccessSize > k))
    ReportAndCrash(Addr);
  1. 参考资料:
    http://www.elecfans.com/d/718308.html
    http://www.zyiz.net/tech/detail-123488.html

四、错误类型

  1. (heap) use after free 释放后使用

下面的代码中,分配array数组并释放,然后返回它的一个元素。

  5 int main (int argc, char** argv)
  6 {
  7     int* array = new int[100];
  8     delete []array;
  9     return array[1];
 10 }

下面的错误信息与前面的“使用步骤”一节中的类似。

==3189==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe44 
at pc 0x0000004008f1 bp 0x7ffc9b6e2630 sp 0x7ffc9b6e2620
READ of size 4 at 0x61400000fe44 thread T0
    #0 0x4008f0 in main /home/ron/dev/as/use_after_free.cpp:9
    #1 0x7f3763aa882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #2 0x4007b8 in _start (/home/ron/dev/as/build/use_after_free+0x4007b8)
0x61400000fe44 is located 4 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0)
freed by thread T0 here:
    #0 0x7f3763ef1caa in operator delete[](void*) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99caa)
    #1 0x4008b5 in main /home/ron/dev/as/use_after_free.cpp:8
    #2 0x7f3763aa882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

previously allocated by thread T0 here:
    #0 0x7f3763ef16b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)
    #1 0x40089e in main /home/ron/dev/as/use_after_free.cpp:7
    #2 0x7f3763aa882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-use-after-free /home/ron/dev/as/use_after_free.cpp:9 main
  1. heap buffer overflow 堆缓存访问溢出

如下代码中,访问的位置超出堆上数组array的边界。

  2 int main (int argc, char** argv)
  3 {
  4     int* array = new int[100];
  5     int res = array[100];
  6     delete [] array;
  7     return res;
  8 } 

下面的错误信息指出:

  • 错误类型是heap-buffer-overflow
  • 不合法操作READ发生在线程T0, heap_buf_overflow.cpp:5
  • heap块分配发生在heap_buf_overflow.cpp
==3322==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61400000ffd0 
at pc 0x0000004008e0 bp 0x7ffeddce53a0 sp 0x7ffeddce5390READ of size 4 at 0x61400000ffd0 thread T0
    #0 0x4008df in main /home/ron/dev/as/heap_buf_overflow.cpp:5
    #1 0x7f3b83d0882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #2 0x4007b8 in _start (/home/ron/dev/as/build/heap_buf_overflow+0x4007b8)
0x61400000ffd0 is located 0 bytes to the right of 400-byte region [0x61400000fe40,0x61400000ffd0)
allocated by thread T0 here:
    #0 0x7f3b841516b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)
    #1 0x40089e in main /home/ron/dev/as/heap_buf_overflow.cpp:4
    #2 0x7f3b83d0882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ron/dev/as/heap_buf_overflow.cpp:5 main
  1. stack buffer overflow 栈缓存访问溢出
    如下代码中,访问的位置超出栈上数组array的边界。
  2 int main (int argc, char** argv)
  3 {
  4     int array[100];
  5     return array[100];
  6 }

下面的错误信息指出:

  • 错误类型是stack-buffer-overflow
  • 不合法操作READ发生在线程T0, stack_buf_overflow.cpp:5
  • 栈块在线程T0的栈上432偏移位置上。
==3389==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd061fa4a0 
at pc 0x0000004009ff bp 0x7ffd061fa2d0 sp 0x7ffd061fa2c0
READ of size 4 at 0x7ffd061fa4a0 thread T0
    #0 0x4009fe in main /home/ron/dev/as/stack_buf_overflow.cpp:5
    #1 0x7fbade4e882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #2 0x400858 in _start (/home/ron/dev/as/build/stack_buf_overflow+0x400858)

Address 0x7ffd061fa4a0 is located in stack of thread T0 at offset 432 in frame
    #0 0x400935 in main /home/ron/dev/as/stack_buf_overflow.cpp:3

  This frame has 1 object(s):
    [32, 432) '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)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/ron/dev/as/stack_buf_overflow.cpp:5 main
  1. global buffer overflow 全局缓冲访问溢出
    如下代码中,访问的位置超出全局数组array的边界。
  2 int array[100];
  3 
  4 int main (int argc, char** argv)
  5 {
  6     return array[100];
  7 }

下面的错误信息指出:

- 错误类型是global-buffer-overflow
- 不合法操作READ发生在线程T0, global_buf_overflow.cpp:6
- 缓存块在global_buf_overflow.cpp:2 定义。
==3499==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000601270 
at pc 0x000000400915 bp 0x7ffd8e80c020 sp 0x7ffd8e80c010READ of size 4 at 0x000000601270 thread T0
    #0 0x400914 in main /home/ron/dev/as/global_buf_overflow.cpp:6
    #1 0x7f613c1c882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #2 0x400808 in _start (/home/ron/dev/as/build/global_buf_overflow+0x400808)
0x000000601270 is located 0 bytes to the right of global variable 'array' defined in '/home/ron/dev/as/global_buf_overflow.cpp:2:5' (0x6010e0) of size 400
SUMMARY: AddressSanitizer: global-buffer-overflow /home/ron/dev/as/global_buf_overflow.cpp:6 main
  1. use after return
    如下代码中,访问的位置是已经释放的栈空间。
int *ptr;
__attribute__((noinline))
void FunctionThatEscapesLocalObject() {
  int local[100];
  ptr = &local[0];
}

int main(int argc, char **argv) {
  FunctionThatEscapesLocalObject();
  return ptr[argc];
}

下面的错误信息指出:

  • 错误类型是stack-use-after-return
  • 不合法操作READ发生在线程T0, example_UseAfterReturn.cc:17
  • 被误用的stack located at offset 36 in frame <_Z30FunctionThatEscapesLocalObjectv>
=================================================================
==6268== ERROR: AddressSanitizer: stack-use-after-return on address 0x7fa19a8fc024 at pc 0x4180d5 bp 0x7fff73c3fc50 sp 0x7fff73c3fc48
READ of size 4 at 0x7fa19a8fc024 thread T0
    #0 0x4180d4 in main example_UseAfterReturn.cc:17
    #1 0x7fa19b11d76c (/lib/x86_64-linux-gnu/libc.so.6+0x2176c)
    #2 0x417f34 (a.out+0x417f34)
Address 0x7fa19a8fc024 is located at offset 36 in frame <_Z30FunctionThatEscapesLocalObjectv> of T0's stack:
  This frame has 1 object(s):
    [32, 432) 'local'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
  1. use after scope

如下代码中,访问的位置是已经处在有效作用域外的栈空间

volatile int *p = 0;

int main() {
  {
    int x = 0;
    p = &x;
  }
  *p = 5;
  return 0;
}

下面的错误信息指出:

- 错误类型是stack-use-after-scope
- 不合法操作WRITE发生在线程T0,  /tmp/use-after-scope+0x5097ec

=================================================================
==58237==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffc4d830880 at pc 0x0000005097ed bp 0x7ffc4d830850 sp 0x7ffc4d830848
WRITE of size 4 at 0x7ffc4d830880 thread T0
    #0 0x5097ec  (/tmp/use-after-scope+0x5097ec)
    #1 0x7ff85fa6bf44  (/lib/x86_64-linux-gnu/libc.so.6+0x21f44)
    #2 0x41a005  (/tmp/use-after-scope+0x41a005)

Address 0x7ffc4d830880 is located in stack of thread T0 at offset 32 in frame
    #0 0x5096ef  (/tmp/use-after-scope+0x5096ef)

  This frame has 1 object(s):
    [32, 36) 'x' <== 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 or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope (/tmp/use-after-scope+0x5097ec) 
  1. initializations order bugs
    如下代码中,仅发生在C++中,初始化顺序不同引发的结果不可预测
$ cat tmp/init-order/example/a.cc
#include <stdio.h>
extern int extern_global;
int __attribute__((noinline)) read_extern_global() {
  return extern_global;
}
int x = read_extern_global() + 1;
int main() {
  printf("%d\n", x);
  return 0;
}

$ cat tmp/init-order/example/b.cc
int foo() { return 42; }
int extern_global = foo();
==26772==ERROR: AddressSanitizer: initialization-order-fiasco on address 0x000001068820 at pc 0x427e74 bp 0x7ffff8295010 sp 0x7ffff8295008
READ of size 4 at 0x000001068820 thread T0
    #0 0x427e73 in read_extern_global() tmp/init-order/example/a.cc:4
    #1 0x42806c in __cxx_global_var_init tmp/init-order/example/a.cc:7
    #2 0x4280d5 in global constructors keyed to a tmp/init-order/example/a.cc:10
    #3 0x42823c in __libc_csu_init (a.out+0x42823c)
    #4 0x7f9afdbdb6ff (/lib/x86_64-linux-gnu/libc.so.6+0x216ff)
    #5 0x427d64 (a.out+0x427d64)
0x000001068820 is located 0 bytes inside of global variable 'extern_global' from 'tmp/init-order/example/b.cc' (0x1068820) of size 4
SUMMARY: AddressSanitizer: initialization-order-fiasco tmp/init-order/example/a.cc:4 read_extern_global()
=================================================================
==27853==ERROR: AddressSanitizer: initialization-order-fiasco on address 0x0000010687e0 at pc 0x427f74 bp 0x7fff3d076ba0 sp 0x7fff3d076b98
READ of size 4 at 0x0000010687e0 thread T0
    #0 0x427f73 in read_extern_global() tmp/init-order/example/a.cc:4
  1. memory leaks 内存泄露

检测内存的LeakSanitizer是集成在AddressSanitizer中的一个相对独立的工具,它工作在检查过程的最后阶段。
下面代码中,p指向的内存没有释放。

  4 void* p;
  5 
  6 int main ()
  7 {
  8     p = malloc (7);
  9     p = 0;
 10     return 0;
 11 }

下面的错误信息指出 detected memory leaks

  • 内存在mem_leak.cpp:8分配
  • 缓存块在global_buf_overflow.cpp:2 定义。
    注意:Asan的mem leak判定的功能只对丢失跟踪的已分配mem块作出判定,其判定规则会导致对没有释放但是也没有失去跟踪的mem的leak视而不见。(rtos的task退出与pc中的进程退出对资源的回收有所不同,rtos的task退出时,不会回收其使用过的mem)
==4088==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 7 byte(s) in 1 object(s) allocated from:
    #0 0x7ff9ae510602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
    #1 0x4008d3 in main /home/ron/dev/as/mem_leak.cpp:8
    #2 0x7ff9ae0c882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

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

ASan(Address Sanitizer)和HWASan(Hardware-assisted Address Sanitizer)都是用于检测内存bug的工具,但它们在实现和使用上有一些区别。

  • ASan:ASan工具包含两大块:插桩模块和一个运行时库。

    • 插桩模块主要会对所有的memory access都去检查该内存所对应的shadow memory的状态。这是静态插桩,因此需要重新编译。为所有栈上对象和全局对象创建前后的保护区,为检测溢出做准备。
    • 运行时库也同样会做两件事:替换默认路径的malloc/free等函数。为所有堆对象创建前后的保护区,将free掉的堆区域隔离一段时间,避免它立即被分配给其他人使用。同时,对错误情况进行输出,包括堆栈信息。
  • HWASan:HWASan则是ASan的升级版,它利用了64位机器上忽略高位地址的特性,将这些被忽略的高位地址重新利用起来,从而大大降低了工具对于CPU和内存带来的额外负载。

在Android开发中,对于arm64,建议使用HWASan;对于32位arm和非Arm平台,建议使用ASan。两者提供的功能相同,并且都应当用于检测用户空间代码中的内存安全bug。


Kernel Address SANitizer (KASAN) 是一种动态内存安全错误检测工具,主要功能是检查内存越界访问和使用已释放内存的问题。KASAN通过建立影子内存来管理内存访问的合法性。每次读/写内存区域前, 都会读取一下影子内存, 获得这块内存访问合法性 (是否被分配, 是否已被释放)。

在代码运行时,每一次memory access都会检测对应的shadow memory的值是否valid。这就需要编译器为我们做些工作。编译的时候,在每一次memory access前编译器会帮我们插入__asan_load##size ()或者__asan_store##size ()函数调用(size是访问内存字节的数量)。这也是要求更新版本gcc的原因,只有更新的版本才支持自动插入。

对于读取操作,编译器会在每次读取操作前插入__asan_load##size ()函数,用于检查该操作是否合法。同样,对于写入操作,编译器会在每次写入操作前插入__asan_store##size ()函数,用于检查该操作是否合法。这种方式可以在出现越界访问时及时进行检测,并报告错误。这就是KASAN处理读写检查的基本逻辑。


Kernel Address Sanitizer (KASAN) 和 Address Sanitizer (ASan) 都是内存 bug 检测工具,它们在开发期间用于检测内存错误。然而,它们的主要区别在于它们的应用领域。

  • ASan 是一个用于用户空间的内存错误检测工具。它可以检测出各种类型的内存错误,包括越界读/写,使用后释放,使用前初始化等。

  • KASAN 则是 ASan 的内核版本。它主要用于检测内核空间的内存错误¹。KASAN 和 ASan 互为补充,因为其中一个工具适用于内核,另一个工具适用于用户空间。

总的来说,这两个工具在功能上是相似的,但应用的领域不同。ASan 主要用于用户空间的内存错误检测,而 KASAN 则主要用于内核空间的内存错误检测。


ASAN工具主要由两部分组成:

  • 编译器插桩模块

加了ASAN相关的编译选项后,代码中的每一次内存访问操作都会被编译器修改为如下方式:

# 启用ASan
CONFIG_SIM_ASAN=y

在这里插入图片描述

  • 运行时库

运行时库(libasan.so.x)会接管malloc和free函数。malloc执行完后,已分配内存的前后(称为“Red Zone”)会被标记为“poison”状态,而释放的内存则会被隔离起来(暂时不会分配出去)且也会被标记为“poison”状态。
在这里插入图片描述
需要注意的是, 由于libasan提供了full feature的实现,所以整体size非常大,在资源受限的设备中做移植不太现实,目前只在sim平台中使用Address Sanitizer的相关功能。


插桩确实是Address Sanitizer工作的一个重要部分,但仅仅插桩是不够的。在编译和链接过程中,还需要链接到Address Sanitizer的运行时库(runtime library),这些库提供了必要的支持函数和数据结构,以便在程序运行时检测和报告内存错误。

简而言之,Address Sanitizer的检测过程包括以下步骤:

  • 编译时插桩:编译器在编译时将检测代码插入到程序的内存访问点上。
  • 链接时链接库:在链接阶段,将ASan的运行时库链接到你的程序中。
  • 运行时检测:程序运行时,ASan的运行时库会监控内存访问,并在检测到内存错误时报告。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值