clang の AddressSanitizer を使って、バッファオーバーフロー/ヒープオーバーフローを検出する


最近の clang では [AddressSanitizer](http://clang.llvm.org/docs/AddressSanitizer.html) という機能があり、コンパイルフラグに `-g -fsanitize=address -fno-omit-frame-pointer` を追加することで実行時にバッファオーバーフロー/ヒープオーバーフローが検出できる。Xcode だと 7.0 以降で使えるようだ。

```c
#include <stdio.h>
int main()
{
	char buffer[5];
	for (int i = 0; i <= 5; i++) {
		buffer[i] = i;
	}
	printf("OK\n");
}
```
上記コードは普通にコンパイルすると何事も無いかのように実行できる。

```
$ clang foo.c
$ ./a.out
OK
```

次に `-g -fsanitize=address -fno-omit-frame-pointer` を追加してコンパイルしてみる。

```
$ clang foo.c -g -fsanitize=address -fno-omit-frame-pointer
$ ./a.out
=================================================================
==47384==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff5ad0c745 at pc 0x000104ef3d98 bp 0x7fff5ad0c710 sp 0x7fff5ad0c708
WRITE of size 1 at 0x7fff5ad0c745 thread T0
    #0 0x104ef3d97 in main foo.c:6
    #1 0x7fff9a7745ac in start (/usr/lib/system/libdyld.dylib+0x35ac)
    #2 0x0  (<unknown module>)

Address 0x7fff5ad0c745 is located in stack of thread T0 at offset 37 in frame
    #0 0x104ef3c5f in main foo.c:3

  This frame has 1 object(s):
    [32, 37) 'buffer' <== Memory access at offset 37 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 foo.c:6 main
Shadow bytes around the buggy address:
  0x1fffeb5a1890: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a18a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a18b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a18c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a18d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1fffeb5a18e0: 00 00 00 00 f1 f1 f1 f1[05]f3 f3 f3 00 00 00 00
  0x1fffeb5a18f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a1900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a1910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a1920: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb5a1930: 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==47384==ABORTING
[1]    47384 abort      ./a.out
```
とバッファオーバーフローが発生していることがわかる。

以下のように `malloc` で確保したヒープよりも大きな文字列を `strcpy` でコピーするようなケースも検出できる。

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
	char *buffer = malloc(5);
	strcpy(buffer, "abcdef");
	free(buffer);
	printf("OK\n");
}
```

```
$ clang bar.c -g -fsanitize=address -fno-omit-frame-pointer
$ ./a.out
=================================================================
==47833==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000eeb5 at pc 0x00010e0e930d bp 0x7fff51b5a7c0 sp 0x7fff51b59f70
WRITE of size 7 at 0x60200000eeb5 thread T0
    #0 0x10e0e930c in wrap_strcpy (/Applications/Xcode7.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/7.0.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib+0x3c30c)
    #1 0x10e0a5df7 in main bar.c:7
    #2 0x7fff9a7745ac in start (/usr/lib/system/libdyld.dylib+0x35ac)
    #3 0x0  (<unknown module>)

0x60200000eeb5 is located 0 bytes to the right of 5-byte region [0x60200000eeb0,0x60200000eeb5)
allocated by thread T0 here:
    #0 0x10e0ef980 in wrap_malloc (/Applications/Xcode7.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/7.0.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib+0x42980)
    #1 0x10e0a5de3 in main bar.c:6
    #2 0x7fff9a7745ac in start (/usr/lib/system/libdyld.dylib+0x35ac)
    #3 0x0  (<unknown module>)

SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 wrap_strcpy
Shadow bytes around the buggy address:
  0x1c0400001d80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400001d90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400001da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400001db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400001dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x1c0400001dd0: fa fa fa fa fa fa[05]fa fa fa 00 06 fa fa 00 00
  0x1c0400001de0: fa fa 00 04 fa fa 00 06 fa fa 00 06 fa fa 00 fa
  0x1c0400001df0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fa
  0x1c0400001e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400001e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400001e20: 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==47833==ABORTING
[1]    47833 abort      ./a.out
```