ASAN

ASAN 简介

ASAN 是 Address Sanitizer 的简称,是 GCC 自带的内存问题检查工具,比较轻量级,非常适合单元测试时检查内存问题。
使用也比较简单,只需要在编译时加上 -fsanitize=address 选项即可。若想更精确查看到源码位置,可以加上 -g 选项。

实例一,检测内存泄漏

leak.c

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char *s;

	s = (char *)malloc(100);
	strcpy(s, "hello world");
	printf("string is: %s\n", s);

	return 0;
}

编译

$ gcc leak.c -o leak.out -g -fsanitize=address

运行

$ ./leak.out 
string is: hello world

=================================================================
==5655==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 100 byte(s) in 1 object(s) allocated from:
    #0 0x7f34793ce808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x55fbf399c225 in main /home/liyongjun/project/c/C_study/memory/asan/leak.c:10
    #2 0x7f34790f3082 in __libc_start_main ../csu/libc-start.c:308

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

上述代码中,分配了 100 字节堆内存,但在 main 函数返回前始终没有释放。
ASAN 提供的报告说明了错误原因是 detected memory leaks(检测到内存泄漏), 同时指出,泄漏的内存是在 leak.c 的第 10 行分配的。
有了这么准确且易用的工具,内存泄漏问题是不是没那么头疼了。

实例二,检测悬空指针访问

huaf.c

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char *s;

	s = (char *)malloc(100);

	free(s);

	strcpy(s, "hello world");
	printf("string is: %s\n", s);

	return 0;
}

编译

$ gcc huaf.c -o huaf.out -g -fsanitize=address

运行

$ ./huaf.out 
=================================================================
==5939==ERROR: AddressSanitizer: heap-use-after-free on address 0x60b0000000f0 at pc 0x7f674231b58d bp 0x7ffeb4a7bc30 sp 0x7ffeb4a7b3d8
WRITE of size 12 at 0x60b0000000f0 thread T0
    #0 0x7f674231b58c in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790
    #1 0x556af56bc26d in main /home/liyongjun/project/c/C_study/memory/asan/huaf.c:14
    #2 0x7f67420b2082 in __libc_start_main ../csu/libc-start.c:308
    #3 0x556af56bc16d in _start (/home/liyongjun/project/c/C_study/memory/asan/huaf.out+0x116d)

0x60b0000000f0 is located 0 bytes inside of 100-byte region [0x60b0000000f0,0x60b000000154)
freed by thread T0 here:
    #0 0x7f674238d40f in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:122
    #1 0x556af56bc255 in main /home/liyongjun/project/c/C_study/memory/asan/huaf.c:12
    #2 0x7f67420b2082 in __libc_start_main ../csu/libc-start.c:308

previously allocated by thread T0 here:
    #0 0x7f674238d808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x556af56bc245 in main /home/liyongjun/project/c/C_study/memory/asan/huaf.c:10
    #2 0x7f67420b2082 in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: heap-use-after-free ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 in __interceptor_memcpy
Shadow bytes around the buggy address:
  0x0c167fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c167fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c167fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c167fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c167fff8000: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
=>0x0c167fff8010: fd fd fd fd fd fa fa fa fa fa fa fa fa fa[fd]fd
  0x0c167fff8020: fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa
  0x0c167fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c167fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c167fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c167fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
  Shadow gap:              cc
==5939==ABORTING

上述代码中我们分配了100个字节的内存空间,紧接着将其释放,但接下来我们对之前分配的内存地址执行写入操作,这是典型的悬空指针非法访问。
ASAN 指出错误原因是 heap-use-after-free(访问悬空指针),该内存地址是:0x60b0000000f0,同时还给出了发生错误时 PC、BP、SP 寄存器的值。
指出错误发生在 huaf.c 的第 14 行。
并且还指出这段内存何时被申请,何时已被释放。

实例三,检测堆溢出

overflow.c

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char *s;

	s = (char *)malloc(10);
	strcpy(s, "hello world");
	printf("string is: %s\n", s);

	return 0;
}

编译

$ gcc overflow.c -o overflow.out -g -fsanitize=address

运行

$ ./overflow.out 
=================================================================
==6026==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000001a at pc 0x7f8b7f21a58d bp 0x7ffe19791730 sp 0x7ffe19790ed8
WRITE of size 12 at 0x60200000001a thread T0
    #0 0x7f8b7f21a58c in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790
    #1 0x56188b58b241 in main /home/liyongjun/project/c/C_study/memory/asan/overflow.c:11
    #2 0x7f8b7efb1082 in __libc_start_main ../csu/libc-start.c:308
    #3 0x56188b58b14d in _start (/home/liyongjun/project/c/C_study/memory/asan/overflow.out+0x114d)

0x60200000001a is located 0 bytes to the right of 10-byte region [0x602000000010,0x60200000001a)
allocated by thread T0 here:
    #0 0x7f8b7f28c808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x56188b58b225 in main /home/liyongjun/project/c/C_study/memory/asan/overflow.c:10
    #2 0x7f8b7efb1082 in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 in __interceptor_memcpy
Shadow bytes around the buggy address:
  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa 00[02]fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
  Shadow gap:              cc
==6026==ABORTING

上面这段代码我们只分配了 10 个字节,但在随后操作中写入了 12 字节的数据,此时,数据的写入显然是溢出分配的内存块了。
ASAN 告诉我们错误的原因是:heap-buffer-overflow,堆区内存溢出了,问题代码为 overflow.c 的第 11 行。

实例四,检测栈溢出

sbo.c

#include <stdio.h>

int main(int argc, char *argv[])
{
	int array[100];

	array[100] = 1;

	return 0;
}

编译

$ gcc sbo.c -o sbo.out -g -fsanitize=address

运行

$ ./sbo.out 
=================================================================
==6154==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffef64bd350 at pc 0x560368c3c2ae bp 0x7ffef64bd170 sp 0x7ffef64bd160
WRITE of size 4 at 0x7ffef64bd350 thread T0
    #0 0x560368c3c2ad in main /home/liyongjun/project/c/C_study/memory/asan/sbo.c:7
    #1 0x7f10ec5fc082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x560368c3c10d in _start (/home/liyongjun/project/c/C_study/memory/asan/sbo.out+0x110d)

Address 0x7ffef64bd350 is located in stack of thread T0 at offset 448 in frame
    #0 0x560368c3c1d8 in main /home/liyongjun/project/c/C_study/memory/asan/sbo.c:4

  This frame has 1 object(s):
    [48, 448) 'array' (line 5) <== Memory access at offset 448 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/liyongjun/project/c/C_study/memory/asan/sbo.c:7 in main
Shadow bytes around the buggy address:
  0x10005ec8fa10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005ec8fa20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005ec8fa30: 00 00 f1 f1 f1 f1 f1 f1 00 00 00 00 00 00 00 00
  0x10005ec8fa40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005ec8fa50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10005ec8fa60: 00 00 00 00 00 00 00 00 00 00[f3]f3 f3 f3 f3 f3
  0x10005ec8fa70: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005ec8fa80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005ec8fa90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005ec8faa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005ec8fab0: 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
  Shadow gap:              cc
==6154==ABORTING

上面的代码,我们在栈上创建了一个容量为100的数组,但在随后的写入操作中在超过数据容量的地址上写入数据,导致了栈溢出。
ASAN 指出错误原因是 stack-buffer-overflow(栈溢出),出错代码位置是 sbo.c 第 7 行。

ASAN 原理

在编译时,ASAN 通过 libasan.so 替换 malloc 和 free 接口。ASAN 会接管内存申请接口,即用户的内存全都由 ASAN 来管理。
在程序申请内存时,ASAN 会额外分配一部分内存来标识该内存的状态。
在程序使用内存时,ASAN 会额外进行判断,确认该内存是否可以被访问,并在访问异常时输出错误信息。

编译前:

*address = ...;  // or: ... = *address;

编译后:

if (IsPoisoned(address)) {
	ReportError(address, kAccessSize, kIsWrite);
}
*address = ...;  // or: ... = *address;

该方式的关键点就在于读写内存前会判断地址是否处于“中毒”状态。

使用技巧

如果你觉得 ASAN 插桩代码和检测的对你某些的代码来说太慢了,那么可以使用编译器标志来禁用特定函数的,使 ASAN 跳过对代码中某个函数的插桩和检测, 跳过分析函数的编译器指令是:

__attribute__((no_sanitize_address))

KASAN

KASAN 是 Kernel Address Sanitizer 的缩写,ASAN 用于用户空间,KASAN 则用于内核空间。
KASAN 集成在 Linux 内核中,随 Linux 内核代码一起发布。

要启用 KASAN,请使用以下命令配置内核:

CONFIG_KASAN = y

典型的越界访问报告如下所示:

==================================================================
BUG: AddressSanitizer: out of bounds access in kmalloc_oob_right+0x65/0x75 [test_kasan] at addr ffff8800693bc5d3
Write of size 1 by task modprobe/1689
=============================================================================
BUG kmalloc-128 (Not tainted): kasan error
-----------------------------------------------------------------------------

Disabling lock debugging due to kernel taint
INFO: Allocated in kmalloc_oob_right+0x3d/0x75 [test_kasan] age=0 cpu=0 pid=1689
 __slab_alloc+0x4b4/0x4f0
 kmem_cache_alloc_trace+0x10b/0x190
 kmalloc_oob_right+0x3d/0x75 [test_kasan]
 init_module+0x9/0x47 [test_kasan]
 do_one_initcall+0x99/0x200
 load_module+0x2cb3/0x3b20
 SyS_finit_module+0x76/0x80
 system_call_fastpath+0x12/0x17
INFO: Slab 0xffffea0001a4ef00 objects=17 used=7 fp=0xffff8800693bd728 flags=0x100000000004080
INFO: Object 0xffff8800693bc558 @offset=1368 fp=0xffff8800693bc720

Bytes b4 ffff8800693bc548: 00 00 00 00 00 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a  ........ZZZZZZZZ
Object ffff8800693bc558: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ffff8800693bc568: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ffff8800693bc578: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ffff8800693bc588: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ffff8800693bc598: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ffff8800693bc5a8: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ffff8800693bc5b8: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ffff8800693bc5c8: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5  kkkkkkkkkkkkkkk.
Redzone ffff8800693bc5d8: cc cc cc cc cc cc cc cc                          ........
Padding ffff8800693bc718: 5a 5a 5a 5a 5a 5a 5a 5a                          ZZZZZZZZ
CPU: 0 PID: 1689 Comm: modprobe Tainted: G    B          3.18.0-rc1-mm1+ #98
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.7.5-0-ge51488c-20140602_164612-nilsson.home.kraxel.org 04/01/2014
 ffff8800693bc000 0000000000000000 ffff8800693bc558 ffff88006923bb78
 ffffffff81cc68ae 00000000000000f3 ffff88006d407600 ffff88006923bba8
 ffffffff811fd848 ffff88006d407600 ffffea0001a4ef00 ffff8800693bc558
Call Trace:
 [<ffffffff81cc68ae>] dump_stack+0x46/0x58
 [<ffffffff811fd848>] print_trailer+0xf8/0x160
 [<ffffffffa00026a7>] ? kmem_cache_oob+0xc3/0xc3 [test_kasan]
 [<ffffffff811ff0f5>] object_err+0x35/0x40
 [<ffffffffa0002065>] ? kmalloc_oob_right+0x65/0x75 [test_kasan]
 [<ffffffff8120b9fa>] kasan_report_error+0x38a/0x3f0
 [<ffffffff8120a79f>] ? kasan_poison_shadow+0x2f/0x40
 [<ffffffff8120b344>] ? kasan_unpoison_shadow+0x14/0x40
 [<ffffffff8120a79f>] ? kasan_poison_shadow+0x2f/0x40
 [<ffffffffa00026a7>] ? kmem_cache_oob+0xc3/0xc3 [test_kasan]
 [<ffffffff8120a995>] __asan_store1+0x75/0xb0
 [<ffffffffa0002601>] ? kmem_cache_oob+0x1d/0xc3 [test_kasan]
 [<ffffffffa0002065>] ? kmalloc_oob_right+0x65/0x75 [test_kasan]
 [<ffffffffa0002065>] kmalloc_oob_right+0x65/0x75 [test_kasan]
 [<ffffffffa00026b0>] init_module+0x9/0x47 [test_kasan]
 [<ffffffff810002d9>] do_one_initcall+0x99/0x200
 [<ffffffff811e4e5c>] ? __vunmap+0xec/0x160
 [<ffffffff81114f63>] load_module+0x2cb3/0x3b20
 [<ffffffff8110fd70>] ? m_show+0x240/0x240
 [<ffffffff81115f06>] SyS_finit_module+0x76/0x80
 [<ffffffff81cd3129>] system_call_fastpath+0x12/0x17
Memory state around the buggy address:
 ffff8800693bc300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff8800693bc380: fc fc 00 00 00 00 00 00 00 00 00 00 00 00 00 fc
 ffff8800693bc400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff8800693bc480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff8800693bc500: fc fc fc fc fc fc fc fc fc fc fc 00 00 00 00 00
>ffff8800693bc580: 00 00 00 00 00 00 00 00 00 00 03 fc fc fc fc fc
                                             ^
 ffff8800693bc600: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff8800693bc680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff8800693bc700: fc fc fc fc fb fb fb fb fb fb fb fb fb fb fb fb
 ffff8800693bc780: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 ffff8800693bc800: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================

报告的标题描述了发生了什么类型的错误以及导致它的访问类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Li-Yongjun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值