最近の clang では AddressSanitizer という機能があり、コンパイルフラグに -g -fsanitize=address -fno-omit-frame-pointer
を追加することで実行時にバッファオーバーフロー/ヒープオーバーフローが検出できる。Xcode だと 7.0 以降で使えるようだ。
#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
でコピーするようなケースも検出できる。
#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