コンパイラの Sanitizer 機能で変数の使用をチェック#
コンパイラに Sanitizer という機能があり、変数の使用に関係する様々なチェックを行うことができる。
Note
Sanitize とは
To sanitize an activity or a situation that is unpleasant or unacceptable means to describe it in a way that makes it seem more pleasant or more acceptable.
—from Collins COBUILD, Advanced Learner’s Dictionary, 9th Edition.
「改善する」くらいの意味合い?
GCC のオプション#
Instrumentation Options (Using the GNU Compiler Collection (GCC))
によると、-fsanitize=<type>
のような形式で Sanitizer の指定ができる。
<type>
には以下のような種類の Sanitizer を指定できる。
Sanitizer |
内容 |
併用できない Sanitizer |
---|---|---|
address |
メモリのチェック。領域外の参照や解放後のメモリの参照など。 |
thread, hwaddress |
kernel-address |
Linux Kernel 用のメモリチェック。 |
|
hwaddress |
ハードウェアによるメモリのチェック。オーバーヘッドが少ないが、AArch64 でしか利用できない。 |
thread, address |
kernel-hwaddress |
ハードウェアによる Linux Kernel のメモリのチェック。オーバーヘッドが少ないが、AArch64 でしか利用できない。 |
|
pointer-compare |
ポインタの比較に処理を追加する。kernel-address, address と併用する必要がある。チェックを実際に動作させるには、プログラム実行時に |
|
pointer-subtract |
ポインタの減算に処理を追加する。kernel-address, address と併用する必要がある。チェックを実際に動作させるには、プログラム実行時に |
|
thread |
データ競合の検知。メモリへのアクセスの処理に細工をする。 |
address, leak |
leak |
メモリリークの検知。リンクする |
thread |
undefined |
未定義動作の検知。 |
併用できない Sanitizer が存在するため、全て有効化するようなオプションは存在しない。
Clang のオプション#
Clang Compiler User’s Manual — Clang 13 documentation
によると、-fsanitize=<type1>,<type2>,...
のような形式で Sanitizer の指定ができる。
<type>
には以下のような種類の Sanitizer を指定できる。
Sanitizer |
内容 |
併用できない Sanitizer |
---|---|---|
address |
AddressSanitizer. 範囲外アクセス、解放後のメモリアクセス、return 後のアクセス、スコープ外でのアクセス、二重解放、不正な解放、メモリリークのチェック。 |
thread, memory |
thread |
ThreadSanitizer. データ競合のチェック。 |
address, memory |
memory |
MemorySanitizer. 未初期化のメモリへのアクセスのチェック。 |
address, memory |
undefined |
UndefinedBehaviorSanitizer. 未定義動作のチェック。 |
|
dataflow |
DataFlowSanitizer. データフローの解析。ソースコード中でタグをつけたデータの流れを補足する。 |
|
cfi |
Control Flow Integrity.
制御フローを奪えるような未定義動作を検出する。
|
|
safe-stack |
SafeStack. スタックバッファーオーバーフローによる攻撃から守る。 |
Visual Studio のオプション#
/fsanitize (Enable sanitizers) | Microsoft Docs
によると、/fsanitize=address
のような形式で
Address Sanitizer
を有効化できる。他の Sanitizer はない。
AddressSanitizer は
メモリの取得・解放の矛盾
極端に大きなメモリ取得
calloc/alloca のオーバーフロー
二重解放
解放後のメモリ使用
グローバル変数のオーバーフロー
ヒープバッファーオーバーフロー
align された変数の不正な alignment
memcpy/strncat のオーバーラップ
スタックバッファーオーバーフロー/アンダーフロー
return 後のスタック使用
スコープ外のメモリアクセス
不正なメモリアクセス
などをチェックする。
例#
Clang による範囲外アクセスの検出#
ソースコード:test-cpp-sanitizer
次のような C++ のソースコードを準備した。
#include <vector>
int main() {
constexpr std::size_t size = 128;
auto vec = std::vector<double>(size);
vec[size] = 0; // ギリギリ範囲外
return 0;
}
次のようにオプション付きでコンパイルした。
add_executable(test_ouf_of_bound out_of_bound.cpp)
target_compile_options(test_ouf_of_bound PRIVATE -fsanitize=address)
target_link_options(test_ouf_of_bound PRIVATE -fsanitize=address)
実行すると次のようになった。
$ ./build/Debug/bin/test_ouf_of_bound
=================================================================
==589927==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x619000000480 at pc 0x0000004cb0d5 bp 0x7ffde99a1070 sp 0x7ffde99a1068
WRITE of size 8 at 0x619000000480 thread T0
#0 0x4cb0d4 in main /home/<user>/projects/test/test-cpp-sanitizer/build/Debug/../../out_of_bound.cpp:30:15
#1 0x7fbef5a440b2 in __libc_start_main /build/glibc-eX1tMB/glibc-2.31/csu/../csu/libc-start.c:308:16
#2 0x41c30d in _start (/home/<user>/projects/test/test-cpp-sanitizer/build/Debug/bin/test_ouf_of_bound+0x41c30d)
0x619000000480 is located 0 bytes to the right of 1024-byte region [0x619000000080,0x619000000480)
allocated by thread T0 here:
#0 0x4c867d in operator new(unsigned long) /home/brian/src/llvm_releases/llvm-project/llvm/utils/release/final/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:99:3
#1 0x4cbe0b in __gnu_cxx::new_allocator<double>::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/ext/new_allocator.h:114:27
#2 0x4cbdb0 in std::allocator_traits<std::allocator<double> >::allocate(std::allocator<double>&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/alloc_traits.h:444:20
#3 0x4cbd6f in std::_Vector_base<double, std::allocator<double> >::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_vector.h:343:20
#4 0x4cbb70 in std::_Vector_base<double, std::allocator<double> >::_M_create_storage(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_vector.h:358:33
#5 0x4cb614 in std::_Vector_base<double, std::allocator<double> >::_Vector_base(unsigned long, std::allocator<double> const&) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_vector.h:302:9
#6 0x4cb2b8 in std::vector<double, std::allocator<double> >::vector(unsigned long, std::allocator<double> const&) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_vector.h:508:9
#7 0x4cb092 in main /home/<user>/projects/test/test-cpp-sanitizer/build/Debug/../../out_of_bound.cpp:29:16
#8 0x7fbef5a440b2 in __libc_start_main /build/glibc-eX1tMB/glibc-2.31/csu/../csu/libc-start.c:308:16
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/<user>/projects/test/test-cpp-sanitizer/build/Debug/../../out_of_bound.cpp:30:15 in main
Shadow bytes around the buggy address:
0x0c327fff8040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c327fff8050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c327fff8060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c327fff8070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c327fff8080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c327fff8090:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff80a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff80b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff80c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff80d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff80e0: 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
==589927==ABORTING
Note
なお、オプションなしで実行してみたところ、何も表示されずに終了ステータス 0 で終了した。ぎりぎり範囲外くらいでは Debug ビルドでも検出してくれない場合があるということになる。急がない処理のときはなるべく at
関数を使用し、範囲外アクセスは例外を投げさせるようにしよう。