Clang で Address Sanitizer を使って C/C++ の不正なメモリアクセスを探す2021年06月08日 11時58分47秒

Clang の C/C++ コンパイラに address sanitizer がある。これは、メモリの割当てと解放の監視をし、free の後のメモリの利用とか、二回 free 等を検知する。同様の機能を行うライブラリやプログラムは他にもいくつか存在する。

Clang の物は実行速度が二倍ぐらいになるとの事。実例を用いて実験。

% cat address_sanitizer.cpp
// clang++ -g -fsanitize=address -fno-omit-frame-pointer
int main()
{
    int *i = new int( 1 );
    delete i;

    *i = 0;
}
これを普通に実行しても問題は露呈しない。
% clang++ address_sanitizer.cpp
% ./a.out
これに、-fsanitize=address -fno-omit-frame-pointer のオプションを追加する。実行結果はこの通り。なお、色付けをサポートしている末端だと、とても色とりどりな報告を見せてくれる。
% clang++ -g -fsanitize=address -fno-omit-frame-pointer address_sanitizer.cpp
% ./a.out 
=================================================================
==99116==ERROR: AddressSanitizer: heap-use-after-free on address 0x222007b0 at pc 0x004adb28 bp 0xffbfe814 sp 0xffbfe80c
WRITE of size 4 at 0x222007b0 thread T0
    #0 0x4adb27 in main /tmp/address_sanitizer.cpp:7:8

0x222007b0 is located 0 bytes inside of 4-byte region [0x222007b0,0x222007b4)
freed by thread T0 here:
    #0 0x4ab847 in operator delete(void*) /usr/src/contrib/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:160:3
    #1 0x4adae9 in main /tmp/address_sanitizer.cpp:5:5
    #2 0x430635 in _start1 /usr/src/lib/csu/i386/crt1_c.c:72:7
    #3 0x43078f in _start /usr/src/lib/csu/i386/crt1_s.S:46

previously allocated by thread T0 here:
    #0 0x4ab029 in operator new(unsigned int) /usr/src/contrib/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:99:3
    #1 0x4ada88 in main /tmp/address_sanitizer.cpp:4:14
    #2 0x430635 in _start1 /usr/src/lib/csu/i386/crt1_c.c:72:7
    #3 0x43078f in _start /usr/src/lib/csu/i386/crt1_s.S:46

SUMMARY: AddressSanitizer: heap-use-after-free /tmp/address_sanitizer.cpp:7:8 in main
Shadow byte legend (one shadow byte represents 8 application bytes):
  0x444400a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x444400b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x444400c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x444400d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x444400e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x444400f0: fa fa fa fa fa fa[fd]fa fa fa fa fa fa fa fa fa
  0x44440100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x44440110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x44440120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x44440130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x44440140: 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
==99116==ABORTING
なお、分割コンパイルする場合には、コンパイラとリンカの両方にこれらのオプションを渡す必要がある。なお、デバッグを有効にする -g オプションを渡さないと、問題のある行数を出力してくれない。