アーカイブ

第10章 4.バッファオーバーフロー:あふれを検出するデバッグ

公開日:2007年9月26日

独立行政法人情報処理推進機構
セキュリティセンター

本ページの情報は2007年9月時点のものです。
記載の資料は資料公開当時のもので、現在は公開されていないものも含みます。

領域あふれの問題の検出については、ロジックが複雑に入り組んでいる場合、ソースコードから見いだすのは容易でない。そのような場合、デバッガ等のツールの下で対象のプログラムを動かして、問題を見つけ出すことになる。

スタックおよびヒープにおけるあふれを検出するためのコンパイラのオプションやデバッグ・ツールがいくつか存在する。

次にその主なものを紹介する。

  • 図10-4: あふれの検出

あふれの検出等に使うことのできるデバッグ・ツール

ヒープにおける領域あふれやダブルフリー等の問題を検出できるツールをふたつ紹介する。ひとつは独立したツール、もうひとつは統合開発環境の中の機能である。

(1) Valgrind [GNU/Linux]

ヒープデバッガを中心とした GNU/Linux 専用のツールスイートである。割当領域外への書き込みや、メモリリークを検出することができる。

Valgrind が備えるツールには、下記のようなものがある。

Memcheck

最も主要なツール、ヒープデバッガ。動的メモリに関わる諸問題を検出する(領域外のアクセス、未初期化領域の参照、不正free、メモリリーク、重複領域のmemcpy)

Cachegrind

キャッシュプロファイラ。CPUキャッシュをエミュレートし、使用状況を調べることができる

Callgrind

Cachegrind プラス呼び出しグラフ生成

Massif

ヒーププロファイラ

Helgrind

スレッドデバッガ ほか

[例 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.
  1. 参考:

(2) Application Verifier [Visual Studio 2005, Windows]

領域あふれの検出に使うことはできないが、Visual Studio に備わっているApplication Verifier(日本語名「アプリケーションの検証」)は、Visual C++ のデバッガとともに動作させると、ヒープのダブルフリー等の不具合を検出してくれるものである。

他にも、ハンドルの検証やロックの検証等の機能がある。

あふれを検出できるランタイム・ライブラリの機能

通常のランタイムライブラリの代わりに、領域あふれ等を検出する能力が組み込まれたライブラリをプログラムにリンクしてデバッグを行う方法がある。

(1) Mudflap [GCC]

バッファオーバーフロー、メモリリーク、ポインタの誤使用等を実行時に検出してくれるライブラリおよび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を一時的に無効にする

[例]

$ 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

(2) MALLOC_CHECK_ [glibcを制御する環境変数]

MALLOC_CHECK_ は、glibc(GNU C Library)がもつ malloc() - free() 関連のチェック機能を活性化させる環境変数である。この環境変数に値を定義すると、malloc() で割り当てたメモリブロックの簡単な保護と異常検出の機能がはたらき、free() 関数が呼び出されたときに次の問題が生じていないかのチェックが行われるようになる。

  • malloc() で割り当てたブロックの手前のメモリの4バイト分のどこかが不正な値で上書きされていないか
  • malloc() で割り当てたブロックの後続のメモリ1バイト分が不正な値で上書きされていないか
  • すでに free() されたポインタを再度 free() しようとしていないか

環境変数 MALLOC_CHECK_ に設定された値に応じ、glibc は次のように振る舞う。

  • MALLOC_CHECK_=0 上記の問題でプログラムに異常をきたすのを防ぐが報告はしない
  • MALLOC_CHECK_=1 問題を検出してメッセージを出す
  • MALLOC_CHECK_=2 問題を検出してプロセスをアボートさせる
  • MALLOC_CHECK_=3 1と2の両方を行う

(3) -D_FORTIFY_SOURCE=n [GCC, glibc]

C言語の標準ランタイム関数を使用するプログラムの実行がよりセキュアに行われるよう、マクロの仕組みを用いて呼び出すライブラリ関数をより堅固なバージョンに置き換えるglibc(GNU C Library)の機能である。

1) 呼び出すライブラリ関数を切り替えるオプション

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]
アボートしました
$

2) コンパイル時にも問題を検出

判断が可能な場合にはコンパイル時にもあふれの警告が出力される。

[例:プログラム example2.c]

1 #include <string.h>
2 main () {
3  char c8[8];
4  strcpy (c8, "1234567890");
5 }

3) 運用における使用

-D_FORTIFY_SOURCE によって導入されるチェックロジックのオーバーヘッドは比較的小さいと言われている。デバッグ時のみならず、プログラムのリリース後もこのオプションを使用することによって攻撃に備えるのもひとつの方法である。

(4) /RTCs [Visual Studio 2005, Windows]

スタック上の変数領域のあふれを実行時に検出してくれる、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 以外が書き込まれていることがあふれとして検出される