背景介绍

现有的很多内存错误检测机制在速度、内存消耗、错误检测的类型和概率、支持的平台等方面各有所长,这些工具可以成功检测出大量的错误,但随之而来的是高开销,亦或是低开销,检测范围狭窄。
在2012年,推出了一款针对C/C++语言的检测工具,AddressSanitizer,又简称ASan,它能够找到堆栈以及全局对象的越界访问错误,同时还有内存释放后仍然被使用的错误。该工具采用了特殊的内存分配和代码插桩,能够很简单地实现在任意编译器、二进制转换系统,甚至是硬件系统中,已在 Chromium 浏览器中发现了超过300多个之前的未知错误。

原理简介

在前面也提到过,Asan由两个部分组成,分别为插桩模块和运行时库。插桩模块修改原有的程序代码来检查每一次内存访问时的影子状态(即shadow区域的赋值),在栈核全局对象的周围创建带毒的红区(即Poisoned Redzons),从而检测程序的内存访问溢出行为。运行时库则替换了原有的malloc、free等内存相关函数,在分配的堆区域创建带毒的红区,延迟已释放堆区域的重复使用操作,并进行错误报告。

项目相关资源

ASan由Google公司开发,从LLVM 3.1版本开始就已成为其中不可获取的一部分,插桩模块被嵌入在了Clang项目中,运行时库则打包在了compile-rt项目中,GCC则从4.8版本开始支持ASan进行内存错误检测。Clang+LLVM 3.1版本测试运行在Ubuntu 12.04 操作系统上,更早的系统版本社区已不再提供支持。
此外,ASan是一个持续更新的项目,随着版本的迭代,该项目做了相当多的优化,在 Clang的官方文档中给出了AddressSanitizer项目的介绍。

简单使用示例

目前ASan版本能够找出如下内存错误类型:

  • Use after free
  • Heap buffer overflow
  • Stack buffer overflow
  • Global buffer overflow
  • Use after return
  • Use after scope
  • Initialization order bugs
  • Memory leaks

以Use after free 为例

创建use_after_free.c文件内容如下:

#include <stdlib.h>
int main() {
  char *x = (char*)malloc(10 * sizeof(char));
  free(x);
  return x[5]; // 访问了已经释放的内存地址
}

上述代码很简单,在堆上创建10字节大小的内存,然后释放该内存空间。然后又通过指针来访问已经被释放的内存地址,可想而知该访问会出错。通过clang进行编译,加上-fsanitize=address开启 ASan 检测,同时还加上-g选项添加必要的调试信息,编译运行命令如下:

$ clang-16 -fsanitize=address -g use_after_free.c -o use_after_free
$ ./use_after_free

来看看运行后产生的报错信息,第一部分如下:

=================================================================
==1947==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000015 at pc 0x56319c4c9a33 bp 0x7ffde97a9aa0 sp 0x7ffde97a9a98
READ of size 1 at 0x602000000015 thread T0
    #0 0x56319c4c9a32 in main /root/asan_test/use_after_free.c:5:10
    #1 0x7f27f3029d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #2 0x7f27f3029e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #3 0x56319c3f52e4 in _start (/root/asan_test/use_after_free+0x1e2e4) (BuildId: acfd5d52426f424206fda94cae93ede3c7fd4bf4)

0x602000000015 is located 5 bytes inside of 10-byte region [0x602000000010,0x60200000001a)
freed by thread T0 here:
    #0 0x56319c48ee66 in free (/root/asan_test/use_after_free+0xb7e66) (BuildId: acfd5d52426f424206fda94cae93ede3c7fd4bf4)
    #1 0x56319c4c99f5 in main /root/asan_test/use_after_free.c:4:3
    #2 0x7f27f3029d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

previously allocated by thread T0 here:
    #0 0x56319c48f10e in __interceptor_malloc (/root/asan_test/use_after_free+0xb810e) (BuildId: acfd5d52426f424206fda94cae93ede3c7fd4bf4)
    #1 0x56319c4c99e8 in main /root/asan_test/use_after_free.c:3:20
    #2 0x7f27f3029d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

首先,给出了 ASan 工具检测出的错误,是heap-use-after-free类型,打印该错误所处的内存地址和相关寄存器内容。

==1947==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000015 at pc 0x56319c4c9a33 bp 0x7ffde97a9aa0 sp 0x7ffde97a9a98

然后,工具报告出错的位置在程序源文件的第5行,使用了释放后的堆内存。

READ of size 1 at 0x602000000015 thread T0
    #0 0x56319c4c9a32 in main /root/asan_test/use_after_free.c:5:10
    #1 0x7f27f3029d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #2 0x7f27f3029e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #3 0x56319c3f52e4 in _start (/root/asan_test/use_after_free+0x1e2e4) (BuildId: acfd5d52426f424206fda94cae93ede3c7fd4bf4)

接着,继续回溯程序栈,找出该报错是由于被访问的内存空间,在程序源文件第4行已经释放了。

0x602000000015 is located 5 bytes inside of 10-byte region [0x602000000010,0x60200000001a)
freed by thread T0 here:
    #0 0x56319c48ee66 in free (/root/asan_test/use_after_free+0xb7e66) (BuildId: acfd5d52426f424206fda94cae93ede3c7fd4bf4)
    #1 0x56319c4c99f5 in main /root/asan_test/use_after_free.c:4:3
    #2 0x7f27f3029d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

最后,确定这片堆内存是在程序源文件第3行进行了空间分配。

previously allocated by thread T0 here:
    #0 0x56319c48f10e in __interceptor_malloc (/root/asan_test/use_after_free+0xb810e) (BuildId: acfd5d52426f424206fda94cae93ede3c7fd4bf4)
    #1 0x56319c4c99e8 in main /root/asan_test/use_after_free.c:3:20
    #2 0x7f27f3029d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

综上,给出了清晰的程序流程和报错原因。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐