公開日:2007年9月26日
独立行政法人情報処理推進機構
セキュリティセンター
本ページの情報は2007年9月時点のものです。
記載の資料は資料公開当時のもので、現在は公開されていないものも含みます。
領域あふれの問題の検出については、ロジックが複雑に入り組んでいる場合、ソースコードから見いだすのは容易でない。そのような場合、デバッガ等のツールの下で対象のプログラムを動かして、問題を見つけ出すことになる。
スタックおよびヒープにおけるあふれを検出するためのコンパイラのオプションやデバッグ・ツールがいくつか存在する。
次にその主なものを紹介する。
ヒープにおける領域あふれやダブルフリー等の問題を検出できるツールをふたつ紹介する。ひとつは独立したツール、もうひとつは統合開発環境の中の機能である。
ヒープデバッガを中心とした GNU/Linux 専用のツールスイートである。割当領域外への書き込みや、メモリリークを検出することができる。
Valgrind が備えるツールには、下記のようなものがある。
最も主要なツール、ヒープデバッガ。動的メモリに関わる諸問題を検出する(領域外のアクセス、未初期化領域の参照、不正free、メモリリーク、重複領域のmemcpy)
キャッシュプロファイラ。CPUキャッシュをエミュレートし、使用状況を調べることができる
Cachegrind プラス呼び出しグラフ生成
ヒーププロファイラ
スレッドデバッガ ほか
[例 Valgrind の Memcheck:対象プログラムをデバッグモード(gcc -g 等)でコンパイルしてあると、Valgrindは問題とともにソースコード位置も知らせてくれる。]
1: $ valgrind --tool=memcheck --leak-check=yes test_program
...
↓≪≪実行途中における、 malloc()した領域からのあふれの検出≫≫
8: ==1929== Invalid write of size 1
9: ==1929== at 0x8048970: header_getContentType (test_program.c:110)
10: ==1929== by 0x8049A58: block_processHeader (test_program.c:586)
11: ==1929== by 0x8049EA6: block_processField (test_program.c:673)
12: ==1929== by 0x8049F80: main_processBlock (test_program.c:712)
13: ==1929== Address 0x1BA4B4B2 is 0 bytes after a block of size 434 alloc'd
14: ==1929== at 0x1B904DF0: malloc (vg_replace_malloc.c:131)
15: ==1929== by 0x8048930: header_getContentType (test_program.c:106)
16: ==1929== by 0x8049A58: block_processHeader (test_program.c:586)
17: ==1929== by 0x8049EA6: block_processField (test_program.c:673)
18: ==1929==
...
↓ ≪≪プログラム終了時、メモリリークの検出≫≫
1048: ==1929== 60907 bytes in 134 blocks are definitely lost in loss record 12 of 12
1049: ==1929== at 0x1B904DF0: malloc (vg_replace_malloc.c:131)
1050: ==1929== by 0x8048930: header_getContentType (test_program.c:106)
1051: ==1929== by 0x8049A58: block_processHeader (test_program.c:586)
1052: ==1929== by 0x8049EA6: block_processField (test_program.c:673)
1053: ==1929==
↓ ≪≪メモリリークのサマリ≫≫
1054: ==1929== LEAK SUMMARY:
1055: ==1929== definitely lost: 60907 bytes in 134 blocks.
1056: ==1929== possibly lost: 334 bytes in 1 blocks.
1057: ==1929== still reachable: 1512 bytes in 14 blocks.
1058: ==1929== suppressed: 0 bytes in 0 blocks.
領域あふれの検出に使うことはできないが、Visual Studio に備わっているApplication Verifier(日本語名「アプリケーションの検証」)は、Visual C++ のデバッガとともに動作させると、ヒープのダブルフリー等の不具合を検出してくれるものである。
他にも、ハンドルの検証やロックの検証等の機能がある。
通常のランタイムライブラリの代わりに、領域あふれ等を検出する能力が組み込まれたライブラリをプログラムにリンクしてデバッグを行う方法がある。
バッファオーバーフロー、メモリリーク、ポインタの誤使用等を実行時に検出してくれるライブラリおよびGCCのコンパイルオプション。
Valgrind に比べて、解析速度が速く、stack, data, bss 変数のチェックが行えるが、コンパイルオプションとライブラリの追加オプションが必要である。
GCCオプション: -fmudflap -lmudflap
マルチスレッドの場合のGCCオプション: -fmudflapth -lmudflapth
[例 プログラム example3.c]
1 #include <string.h>
2
3 sub () {
4 char c8[8];
5 strcpy(c8, "1234567890");
6 }
7
8 main () {
9 sub();
10 }
[コンパイルするコマンドの例]
cc -fmudflap -lmudflap example3.c -o example3
[実行例]
$ ./example3
*******
mudflap violation 1 (check/write): time=1167124492.737106 ptr=0xbff80720 size=11
pc=0x5d5f9d location=`(strcpy dest)'
/usr/lib/libmudflap.so.0(__mf_check+0x3d) [0x5d5f9d]
/usr/lib/libmudflap.so.0(__mfwrap_strcpy+0x158) [0x5e2368]
./example3(sub+0x3c) [0x8048630]
Nearby object 1: checked region begins 0B into and ends 3B after
mudflap object 0x8fb2588: name=`example3.c:4 (sub) c8'
bounds=[0xbff80720,0xbff80727] size=8 area=stack check=0r/3w liveness=3
alloc time=1167124492.700013 pc=0x5d59dd
number of nearby objects: 1
環境変数 MUDFLAP_OPTIONS を設定すると次のようなこともできる。
[例]
$ MUDFLAP_OPTIONS="-mode-nop" ./example3
[例]
$ MUDFLAP_OPTIONS="-no-timestamps -backtrace=0" ./example3
[タイムスタンプとバックトレースを止めて実行した例]
$ MUDFLAP_OPTIONS="-no-timestamps -backtrace=0" ./example3
*******
mudflap violation 1 (check/write): time=1167124513.569876 ptr=0xbff0d680 size=11
pc=0xd31f9d location=`(strcpy dest)'
Nearby object 1: checked region begins 0B into and ends 3B after
mudflap object 0x9f7d5f0: name=`example3.c:4 (sub) c8'
bounds=[0xbff0d680,0xbff0d687] size=8 area=stack check=0r/3w liveness=3
alloc time=0.000000 pc=0xd319dd
number of nearby objects: 1
MALLOC_CHECK_ は、glibc(GNU C Library)がもつ malloc() - free() 関連のチェック機能を活性化させる環境変数である。この環境変数に値を定義すると、malloc() で割り当てたメモリブロックの簡単な保護と異常検出の機能がはたらき、free() 関数が呼び出されたときに次の問題が生じていないかのチェックが行われるようになる。
環境変数 MALLOC_CHECK_ に設定された値に応じ、glibc は次のように振る舞う。
C言語の標準ランタイム関数を使用するプログラムの実行がよりセキュアに行われるよう、マクロの仕組みを用いて呼び出すライブラリ関数をより堅固なバージョンに置き換えるglibc(GNU C Library)の機能である。
GCC にコンパイルオプション -D_FORTIFY_SOURCE=1 あるいは =2 を指定すると、あふれを起こしやすい strcpy() のような関数がマクロ展開によって同様な処理を行うが領域あふれを検査するよう対策された別の関数 __strcpy_chk() に置き換わる。
プログラム実行時にあふれ等の問題が検出されたときは、プロセスの実行が abort()によって中止される。
この方法を使うと、ソースコードを変更する必要がなく、コンパイルのし直しのみであふれ検出機能をはたらかせることができる。
GCCに指定するオプション: -O1 -D_FORTIFY_SOURCE=1 または =2
この機能を使用するときは、コンパイラの最適化オプションを -O1 またはそれ以上のレベルで指定する必要がある。
[例 プログラム example1.c]
1 #include <stdio.h>
2 main () {
3 char buf[4];
4 gets (buf);
5 puts (buf);
6 }
[コンパイルと実行の例]
$ gcc -O1 -D_FORTIFY_SOURCE=2 example1.c -o example1
$ ./example1
aaaa
*** buffer overflow detected ***: ./example1 terminated
======= Backtrace: =========
/lib/libc.so.6(__chk_fail+0x41)[0x464d7161]
/lib/libc.so.6(__gets_chk+0x174)[0x464d70c4]
./example2[0x804840d]
/lib/libc.so.6(__libc_start_main+0xdc)[0x4640bf2c]
./example2[0x8048351]
======= Memory map: ========
00651000-00652000 r-xp 00651000 00:00 0 [vdso]
08048000-08049000 r-xp 00000000 fd:00 914420 /home/test/example2
08049000-0804a000 rwxp 00000000 fd:00 914420 /home/test/example2
0947a000-0949b000 rwxp 0947a000 00:00 0
45a27000-45a40000 r-xp 00000000 fd:00 948678 /lib/ld-2.5.so
...省略...
bfa0c000-bfa22000 rw-p bfa0c000 00:00 0 [stack]
アボートしました
$
判断が可能な場合にはコンパイル時にもあふれの警告が出力される。
[例:プログラム example2.c]
1 #include <string.h>
2 main () {
3 char c8[8];
4 strcpy (c8, "1234567890");
5 }
-D_FORTIFY_SOURCE によって導入されるチェックロジックのオーバーヘッドは比較的小さいと言われている。デバッグ時のみならず、プログラムのリリース後もこのオプションを使用することによって攻撃に備えるのもひとつの方法である。
スタック上の変数領域のあふれを実行時に検出してくれる、Visual C++ のコンパイルオプション。
文字型配列(char[ ])のみならず、スタック上のすべての変数を検査してくれるのが特徴である。変数の大きさが1バイトしかなくても、型がintやポインタであってもあふれが検査される。
あふれを検出するのに使われる方法は、変数ひとつひとつの前後に 0xCCCCCCCC の値をもつ4バイトのワードが置かれ、これが壊れていないか、関数からのリターン時に調べるというものである。この /RTCs の検査は、/GS のスタック破壊検査よりも先に行われる。
[例 プログラム]
func () {
int i;
char c16[16];
char *p;
int j;
i = 0x11111111;
memset (c16, 0x22, sizeof(c16));
p = 0x33333333;
j = 0x44444444;
c16[16] = 0xFF;
return 0;
}
[func() 関数からリターンする直前のスタックの内容]
0x0012FE20 cccccccc cccccccc cccccccc cccccccc フフフフフフフフフフフフフフフフ
0x0012FE30 cccccccc cccccccc 44444444 cccccccc フフフフフフフフDDDDフフフフ
↑int j
0x0012FE40 cccccccc 33333333 cccccccc cccccccc フフフフ3333フフフフフフフフ
↑char *p
0x0012FE50 22222222 22222222 22222222 22222222 """"""""""""""""
↑char c16[16]
0x0012FE60 ccccccff cccccccc 11111111 cccccccc .フフフフフフフ....フフフフ
** ↑int i
0x0012FE70 9bbfcf2a 0012ff48 004114a3 00000000 *マソ・H...」.A.....
0x0012FE80 00000000 7ffdf000 cccccccc cccccccc .....・..フフフフフフフフ
0x0012FE90 cccccccc cccccccc cccccccc cccccccc フフフフフフフフフフフフフフフフ
注 c16[ ] の前後に 0xcc 以外が書き込まれていることがあふれとして検出される