バッファ溢れ攻撃とその防御

( 1 )
1
チュートリアル
バッファ溢れ攻撃とその防御
脇田 建
1989 年 11 月,のちにインターネットワームと呼
ウェアが異常動作をすることは古くから知られてい
ばれる悪性ソフトウェアによって,産声をあげたばか
た.C 言語を覚えたてのころ,文字列を入力する簡単
りのインターネットは壊滅状態に陥いった.そして,
なプログラムのデバッグ中にオペレーティングシステ
2001 年 7 月 Code Red と呼ばれる悪性ソフトウェア
ムのメモリ保護違反に悩まされたことは誰しも経験
によって,再びインターネットは大きな被害を受け
することであろう.このようなバッファ溢れのバグは
ることとなった.10 年以上の時の経過にも関わらず,
長いあいだ,多くの人々を悩ませていたものの,些細
二つの事件に共通するのは,攻撃されたソフトウェア
なバグという以上の関心を引くことはなかった.
がいずれもバッファ溢れ と呼ばれる,『些細なバグ』
を含んでいた点である.
バッファ溢れバグが注目を集めるのは,1988 年の
インターネットワーム [9] の仕組みが明らかにされて
この,バッファ溢れバグをつく攻撃とはどのような
からである.インターネットワームは,fingerd デー
仕組みなのだろうか?また,この攻撃からコンピュータ
モンのバッファ溢れバグを悪用して UNIX のシェル
システムを守るための有効な手段はあるのだろうか?
を起動するものであった.コンピュータへ侵入するの
に,セキュリティとは無縁と思えるデーモンが利用
1 はじめに
されたことは当時の人々にとって大きな衝撃だったよ
バッファ溢れ攻撃は,過去 10 年間に最も盛んに利
うだ.
用されたコンピュータの攻撃手法である [3].ある調
インターネットワーム以後も,しばらくはバッファ
査によれば,1998 年に実際にあったコンピュータに
溢れ攻撃の報告例は少ない.しかし,1996 年に Aleph
対する攻撃のうち,首位の人的な機密漏曳に続いて,
One を名乗る人物によるバッファ溢れ攻撃の詳細な
バッファ溢れ攻撃が第二位となっている.また,こ
解説 [1] が出回ると,技術的な内容が世間によく理解
こ数年の CERT のセキュリティ勧告では半分をバッ
され,バッファ溢れ攻撃の事例が次第に増えてきた.
ファ溢れ攻撃が占めている.このようにバッファ溢れ
これまでにバッファ溢れ攻撃を受けたソフトウェア
攻撃は,コンピュータシステムに対する最も深刻な脅
には,sendmail(メイルサーバ),X クライアント,
威といえる.
BIND (ネームサーバ),wu-ftpd(FTP サーバ)な
ソフトウェアに対して,それが想定する以上の大き
どが含まれる.最近では,2001 年 7 月の Code Red
さのデータが与えられたときに,しばしば,ソフト
worm が Microsoft 社のウェブサーバの拡張機能の中
に存在するバグを悪用して,インターネットに甚大
Ken Wakita, 東 京 工 業 大 学, Tokyo Institute of
Technology
コンピュータソフトウェア,Vol.?, No.?(2002), pp.??-??.
2001 年?月?日受付.
なる被害をもたらした.今日,バッファ溢れ攻撃は,
最も一般的な,ソフトウェアの攻撃手法となってお
り,近年は,CERT のセキュリティ情報の半数以上
コンピュータソフトウェア
2
がバッファ溢れ攻撃に関わるものとなっている [3].
( 2 )
が存在ことは明白である.しかし,一般的にはこの種
本稿では,ソフトウェアに内在するバッファ溢れの
のバグが誰にも知られずにソフトウェアに残ることは
バグを悪用するコンピュータシステムの攻撃手法とそ
多い.このようなバグは,プログラマが暗黙のうちに
の攻撃に対するさまざまな防御手法について概説す
仮定した大きさを越えたデータが入力されたときに
る.本稿の構成は以下の通りである.2 節では,バッ
顕在化する.
ファ溢れ攻撃の手法を解説し,3 節では人手に頼って
バッファ溢れ攻撃は,バッファを溢れさせて,バッ
バッファ溢れのバグを見つけ修正する作業の困難さ
ファに隣接するデータ構造や制御構造を破壊し,悪意
を説明する.4 節から 7 節では潜在的にバッファ溢れ
のあるコード(悪性コード)に制御を移す方法であ
の脆弱性を持つソフトウェアを攻撃から保護するた
る.この種の攻撃方法のうち,単純かつ一般的なもの
めのさまざまな防御方法を解説し,9 節で本稿をまと
は,プログラミング言語処理系の関数呼び出しのた
める.
めのスタック構造を破壊するスタック破壊攻撃(stack
smashing attack) [1] である.スタック破壊攻撃につ
2 バッファ溢れバグに対する攻撃
いて述べる前に,スタックを用いた関数呼出しの実現
この節では,バッファ溢れという些細なバグがス
の仕組みを説明する.
タック破壊攻撃に代表されるバッファ溢れ攻撃を可能
にする仕組みをプログラミング言語処理系の実装方
2.2 関数呼び出しとスタック
式から説明する.
プログラミング言語処理系の実行時システムにお
けるスタックはプログラミング言語の再帰的な関数呼
2.1 バッファ溢れ
出しを実現するために用いられるデータ構造である.
バッファ溢れ(buffer overflow, buffer overrun) と
再帰的な関数呼び出しを正しく実現するためには,引
は,データ構造に割当てられた記憶領域を越えてアク
数,局所変数,戻り番地などといった,それぞれの
セスを意味する.典型的なバッファ溢れは,配列のア
関数に固有の局所環境を個別に保存する必要がある.
クセスにおいて配列の範囲の検査が十分でないこと
このために,関数呼び出しごとに,スタックにその関
が原因である.やや,人工的な例ではあるが以下は,
最も簡単なバッファ溢れの例である.†1
数の局所環境を保存するのに十分な大きさの領域が
int main() {
char buffer[4];
char *data = "xxxxxxxx";
strcpy(buffer, data);
return 0;
割当てられる.この領域をスタックフレームと呼ぶ.
例えば,関数 f1 が関数 f2 を呼出し,さらに f2 が f3
を呼出したときのスタックの状態を図示すると図 1 の
ようになる.ここで,Ai と Li はそれぞれ関数 fi の
実引数と局所変数を保存する領域である.また,ri は
関数 fi の戻り番地と呼ばれ,関数 fi の処理が終了し
た時点で,実行すべき命令のアドレスが保存される.
}
通常,このアドレスは関数を呼び出した命令の次の命
バッファ溢れのバグはバッファへのアクセス範囲を検
令を指す.bi はフレームポインタ,あるいはベース
査するコードを挿入することによって簡単に修正で
ポインタと呼ばれ,呼出し元の関数のスタックフレー
きる.
ムを指す.
if (strlen(data) < 4)
strcpy(buffer, data);
この例では,ソフトウェアが異常終了するためにバグ
†1 このプログラムは,Pentium 上の Linux では,セグ
メンテーション違反で異常終了する.
図 1 に示されるように,最近の一般的な CPU アー
キテクチャの多くでは,スタックはアドレス空間の低
位番地に向かって成長する.
( 3 )
Vol. 1 No. 1 Jan. 1984
Lower memory address
L3
b3 r3
Higher memory address
A3
L2 b2 r2
A2
L1
b1 r1
3
Lower memory address
Higher memory address
B
A1
z
y
x b1 r1
A1
Malicious string
f2(A2) {
...;
f3(A3);
...
}
図1
f1(A1) {
...;
f2(A2);
...
}
foo(...) {
...;
f1(A1);
...
}
スタックの構造
2.3 スタック破壊攻撃
NOP
Malicious Code
図2
RET
foo(...) {
...;
f1(A1);
...
}
バッファが溢れる直前の状態
Lower memory address
Higher memory address
スタック破壊攻撃は,バッファ溢れ攻撃の一種であ
る.後述の一般的なバッファ溢れ攻撃に比べて単純な
NOP
Malicious Code
RET
ために,これまでに報告されたバッファ溢れ攻撃のほ
とんどがこの範疇に含まれる.スタック破壊攻撃は,
図3
バッファが溢れた直後の状態
スタック上に割当てられたバッファを溢れさせるこ
とで攻撃を成功させる.たとえば,図 2 に示された
タックポインタの値などの情報を元にして有効な悪性
バッファB に悪性コード (Malicious code) を含む悪
文字列を作成する必要がある.攻撃先のソフトウェア
性文字列 (Malicious string) が読み込まれ,バッファ
のソースプログラムがない場合には,これらの正確な
が溢れたとしよう(図 3).悪性文字列は,三つの部
情報を得ることは難しい.しかし, [1] はこれらを簡
分から構成される.先頭の NOP 部は,適切な長さの
易な方法によって予測できることを示している.
NOP 命令の列である.悪性コードに続く RET 部は,
バッファ中の適切なアドレスの列である.悪性文字列
2.4 一般的なバッファ溢れ攻撃
がバッファにコピーされたときに,RET 部は関数の
スタック破壊攻撃の変種としては,return-into-libc
戻り番地 r1 を書換え,コピーされた NOP 部のどこ
攻撃と呼ばれるものがある.これは,RET 部に標準
かを指すように調整しておく.スタックフレームの構
ライブラリの中の既知の関数のアドレスを保存する
造が正確に把握できていれば RET 部は 1 ワードでよ
攻撃法である.最近のソフトウェアの多くは動的リン
いが,予測が難しい場合には図 2 のように RET 部に
クライブラリを用いているため,動的リンクライブラ
幅をもたせて予測の誤差を吸収する.NOP 部の目的
リの関数が実際に割当てられるアドレスはシステム
も同様にバッファのアドレスが正確に予測できない
によって変わり得る.しかし,システム内で基本的な
ことへの対処である.この悪性文字列によって,バッ
ライブラリは最初に動的リンクされるために,機械
ファが溢れた時点で,スタックフレームの局所変数や
が異っても同じアドレスに割当てられることが多く,
それに続く制御情報が破壊される.特に重要な点は,
スタック破壊攻撃で悪用される可能性が高い.
スタックフレームの戻り番地が変更され,破壊された
スタック破壊攻撃以外のバッファ溢れ攻撃の方法と
バッファ中の NOP 部を指していることである.この
しては,スタック上に保存された関数ポインタやオブ
結果,この関数が処理を終え,呼び出し元の関数に制
ジェクト指向言語の仮想関数表を書換える方法,フ
御を戻そうとした時点で,NOP 部に続く悪性コード
レームポインタを変更する方法,例外処理や大域脱出
が実行されてしまう.
を実現するための制御データを変更する方法などが
スタック破壊攻撃を成功させるためには,あらか
じめバッファの大きさ,バッファの先頭から戻り番地
が保存されている個所までのオフセット,実行時のス
指摘されており,これらのなかには実際に悪用された
方法もある [6] [10].
コンピュータソフトウェア
4
3 ソースの書き換え
( 4 )
い文字列処理関数を提案している [23].
バッファ溢れ攻撃に対する根本的な解決は,ソフ
バッファ溢れのバグには二種類の原因が考えられ
トウェアからバッファ溢れのバグを完全に取り除く
る.ひとつはプログラマが作成したバッファ操作の処
ことである.OpenBSD [28] と Linux Security Audit
理に起因するもの,もうひとつは標準ライブラリに
Project [22] はこの困難な作業に取り組んでいるが,
含まれるバッファ操作関数の使用において必要なバッ
人手による膨大なコードの検査には今後も多大な労力
ファ溢れの検査を怠った場合である.既知のバッファ
を要するものと考えられる.人手によってバッファ溢
溢れ攻撃のうち 9 割までが表 1 の標準ライブラリ中
れを発見することの困難は,検査済みとされた lprd
のバッファ操作関数を悪用していることが知られて
に未知のバグが残っており,実際に攻撃を受けた事
いる.
例や高度な安全性が保証されるべき sshd デーモンに
これらの関数がバッファ溢れの検査を行わないの
バッファ溢れが見つかったことに象徴されている.
は,標準ライブラリの設計において性能がコードの安
人手による作業を補助する方法として,入力に無作
全性より優先されたからである.最近は,これらに代
為に長いデータ列を指定して,おかしな挙動を探す方
えてより安全なライブラリ関数を利用することが推奨
法 [14] が利用されている.しかし,必ずしも全てのバ
されている.例えば,前述のいくつかの関数に対して
グが見つかるわけではないので,その有効性には限界
はバッファ溢れの検査を行う変種が用意されている.
がある.別な方法としては,7 節に解説する静的解析
strncpy(char *dest, const char *src, size t n)
を利用する方法がある.
strncat(char *dest, const char *src, size t n)
getcwd(char *buf, size t size)
4 オペレーティングシステムのサポート
fgets(char *s, int size, FILE *stream)
4.1 実行不可能なスタック
残念なことに,これらの関数の性質についての誤解
スタック破壊攻撃では,スタックに配置された悪性
がもととなったバッファ溢れバグが多数報告されてい
コードが実行される.そこで,オペレーティングシス
る.たとえば,strncpy 関数は通常はコピー先に空文
テムの仮想記憶のメモリ保護機構を用いてスタック
字を書き込むがバッファが溢れそうな場合には空文字
上のコードが実行できないように設定することによっ
を書きこまない.そこで,多くのプログラマの直観に
てスタック破壊攻撃から完全に防御できると考えら
反して,バッファ長として,実際より 1 小さい値を与
れる.すでに述べたように,スタック破壊攻撃がバッ
え,さらに,空文字が書きこまれない場合に備えて,
ファ溢れ攻撃の大部分を占めるため,この手法は有効
空文字をバッファの右端に書き込むのが正しい利用法
である.さらに,メモリ保護機構はハードウェアで実
とされている.
現されているため,実行時の性能低下もなく効率的で
char dest[BUFSIZE];
ある.この機能は,Linux に対するパッチ [29] として
...
提供されたが,最近では商用オペレーティングシステ
strncpy(dest, src, BUFSIZE-1);
ムのカーネルの設定項目に含まれるようになった [8].
dest[BUFSIZE-1] = ’\0’;
この方法は,既存のソフトウェアに対する互換性
別の問題として,strncpy はバッファが十分な大
について問題を残す.実は,プログラミング言語の実
きさがある場合には,書き込まれなかった部分を空
行系によっては,スタック上に小さな実行コード(ト
文字で埋める.このため,長大なバッファに短い文
ランポリン)を作成するものがある.例えば,C++
字列をコピーする効率が極めて悪い.このことが
などのオブジェクト指向言語の多重継承 (multiple
strncpy と strncat が敬遠されるひとつの理由とされ
inheritance) や GNU C コンパイラ (gcc) の拡張機能
る.OpenBSD プロジェクトでは,これらの反省か
である入れ子状の関数,関数型言語の関数閉包などの
ら,strlcpy と strlcat という効率的でより誤解の少な
実装にこの手法が利用される.また,Linux ではシグ
( 5 )
Vol. 1 No. 1 Jan. 1984
表1
5
バッファ溢れの検査を行わない標準ライブラリ関数の例
関数のプロトタイプ宣言
関数の働き
バッファが溢れる条件や例
strcpy(dest, src)
文字列のコピー
sizeof (dest) < |src|
strcat(char dest, src)
文字列の連結
sizeof (dest) < |dest | + |src|
gets(s)
行の入力
sizeof (s) < | 文字列 |
getwd(path)
パスの取得
sizeof (buf ) < |dir |
realpath(path, path’)
標準パス表現への変換
sizeof (path ) < |path|
int fscanf(stream, format, . . . )
入力形式の変換
fscanf(stdin, ”%s”)
sprintf(r, format, . . . )
形式指定出力
sprintf(””, ”foo”)
ナル処理に実行可能なスタックの機能が利用される.
で,return-into-libc 攻撃が失敗する.†2
このため, [29] では,シグナルの処理中は一時的にス
タックを実行可能に設定する.さらに,トランポリン
4.3 多様性の向上
を利用するソフトウェアについては,メモリ保護の機
[13] はそもそもコンピュータシステムが互いに類似
能を無効にする.
することが外部からの攻撃を容易にしている原因で
このように,基本的なアイデアは単純であるにも
あり,オペレーティングシステムの実装のさまざまな
関わらず,互換性を保つための実装は複雑なものとな
要素をシステムごとに相違させることによって,セ
る.さらに,return-into-libc 攻撃をはじめとする一
キュリティを向上できると主張している.特に,バッ
般的なバッファ溢れ攻撃に対する防御力には限定的で
ファ溢れ攻撃への防御方法として,メモリ構成を変化
ある.このため,他の手法に対する補完的な手法と位
させる三手法が提案されている.
置づけられる.
1. スタックフレームの局所変数が割当てられる領
域と戻り番地が保存される場所の間に無作為に
4.2 動的リンクライブラリの配置の工夫
Return-into-libc 攻撃 [7] は,バッファ溢れによって
関数の戻り番地を標準ライブラリ関数の既知のアド
ギャップを挿入する.
2. 大域変数の割当てアドレスや局所変数の配置を
ランダムにする.
レスに書きかえる.これまでの事例では,UNIX の
3. スタックフレームをヒープ上に割当てることに
system 関数を利用してシステムコマンドを実行した
よって,スタックフレームの配置の規則性をな
ものが多い.すでに述べたように,たとえ動的リンク
くす.
ライブラリであっても,標準的なライブラリは常に同
じアドレスに配置されることが多い.
システムごとの相異を実現する手法のプロトタイプ
は,コンパイラを修正して,コンパイルするたびにス
前述の [29] では,return-into-libc 攻撃への対処と
タックフレームの構造を無作為に変更することによっ
して,標準ライブラリの仮想アドレス空間へのマッピ
て実現されている.このシステムを利用して,既知の
ングの工夫を提案している.この方法では,ライブラ
バッファ溢れ攻撃を防止した事例が報告されている.
リに含まれるそれぞれの関数のアドレスが空文字を
興味深い試みであるが,既存のソフトウェアとの互換
含むようにライブラリを配置する.空文字は,C 言語
性やこのシステムを対象とした攻撃の可能性につい
のプログラムでは文字列の終端を表わすのに使われ
る.このため,return-into-libc 攻撃のために標準ラ
イブラリ関数のアドレスを用いても,文字列操作関数
は,関数のアドレスの不完全なコピーを作成するの
†2 ライブラリ関数のアドレスの代わりに,動的リンクラ
イブラリの PLT(procedure linking table) のエント
リを指定することによって return-into-libc が可能だ
とする指摘がある.
コンピュータソフトウェア
6
てはさらなる検討を要すると思われる.
( 6 )
Lower memory address
5 スタック破壊法に対する動的検査
A3
一般にバッファ溢れを正確に検査することは困難で
Higher memory address
L2 b2 r2
A2
b1 r1
A1
buffer
upperbound
ある.近似的な手法として,関数の実行中にスタック
フレームの範囲を越えたデータの書き込みがないこ
buffer
char *strcpy(char *dest, const char *src) {
とを保証することは比較的容易である.この手法は,
size_t len = strlen(src);
スタック破壊攻撃に対する有効な防御法であるが,一
char *ub = upperbound(dest);
般のバッファ溢れ攻撃を検知するためにはより正確な
if (dest+len < ub) memcpy(dest, src, len)
検査手法が必要である.これまでに提案された,さま
else error;
ざまな検査手法は以下の観点に関して分類できる.
検査方法としては,予想される書き込み領域をス
return
}
タックフレームの構造と比較する方法 [33] [3],他所
図4
に保存された戻り番地との比較を行う直接的な方法
libsafe の仕組み
[19] [35],隣接したデータの変化を捉える間接的な手
libparanoia については不明瞭なメモがあるのみで詳
法がある [4] [10].これらの違いは,検査の正確性と性
細は不明である.このアイデアは,長いこと忘れられ
能について利害得失をもたらす.
ていたが,後に libsafe [3] で独立再発見された.ここ
検査対象の関数として既知の標準ライブラリ関数
では,この文献にしたがって説明する.
に限るもの [33] [3] [19] とプログラム中の任意の関数を
基本的な考え方では,引数に与えられたデータから
扱うもの [5] [35] がある.前者は,文字列処理に代表
この関数が書き込むメモリの領域を求め,それがス
される既知のバッファ溢れの脆弱性を保護する.後者
タックフレームの境界を跨がないことを調べる.関数
は,それに加え,プログラマが仕込んでしまう未知の
が書き込む領域はそれぞれの関数の性質に応じて計
バッファ溢れの脆弱性にも対応できる.
算できる.次に,書き込み先のバッファがどのスタッ
検査のタイミングとしては,関数の処理の事前か
クフレームに属するかを調べる必要がある.図 4 か
事後かということがある.事前に検査する場合には,
ら分かるように,フレームポインタのリストを辿るこ
バッファ溢れそのものを未然に防止できる場合がある
とによってスタックフレームの構造を把握できる.こ
[3].楽観的な事後の検査では,検査に要する費用が低
の中で,バッファの先頭アドレスよりも大きいフレー
い [4].
ムポインタで最小のもの,すなわち,スタックトップ
実装方法として,コンパイラを修正する方法 [5] [10],
のフレームポインタから辿り始めて,バッファの先頭
標準ライブラリを置き換える方法 [33],動的リンクの
アドレスより大きくなる最初のフレームポインタが,
機能を使って一部の標準ライブラリ関数を上書きする
バッファを含むフレームを表すことが分る(図 4 の陰
方法 [3] [19] などがある.
の部分).このとき,書き込む領域がこのスタックフ
レームの範囲を越えない条件は,書き込む領域が当該
5.1 安全なライブラリ
スタックフレームのフレームポインタより小さいこと
バッファ溢れ攻撃に対応するライブラリとして最
である.
初のものは,Panaschenko のアイデアに基づいて,
libsafe は標準ライブラリ関数に対するラッパを集
Snarskii が FreeBSD に実装した libparanoia [33] の
めた動的リンクライブラリとして実装されており,ラ
ようである.libparanoia は FreeBSD の標準ライブ
イブラリの動的リンク機構のロード済みライブラリ
ラリの中で定義されているいくつかの危険な関数に
としてロードされる.このため,既存のシステムにこ
対して検査のコードを追加するためのパッチである.
れ以外の一切の変更を与えずに,システムのセキュリ
( 7 )
Vol. 1 No. 1 Jan. 1984
7
ティを向上できる.このことは,商用オペレーティン
変化によってバッファ溢れの検知を試みる.通常のス
グシステムにも応用できる点,ソースコードのないソ
タックフレームは,関数の戻り番地と呼出し元の関数
フトウェアを守ることができる点などで優れている.
のフレームポインタで始まる.StackGuard のスタッ
クフレームでは,フレームポインタと局所関数領域の
5.2 戻り番地の一貫性検査
戻り番地の一貫性検査(integrity check ) は,関数の
間に 1 ワードの領域を確保し,関数処理の開始時にカ
ナリア†3 と呼ばれる一貫性検査のための値を保存す
戻り番地に着目して,関数実行が戻り番地に制御を移
る.この領域には,関数呼び出しの直後に適切なカナ
す直前に,この戻り番地が関数が呼出された時点から
リア値が保存され,関数の終了終了時にカナリアの値
変化していないことを検査する手法である.安全なラ
が変化していないことを確認してから,戻り番地に制
イブラリの方法では,バッファ溢れが起きる直前に検
御を移す.カナリアは戻り番地よりもバッファの近く
査できるのに比べて,一貫性検査ではバッファ溢れの
に配置されているため,カナリアが無地なことから,
発見が関数呼出しの終了の時点まで遅れる.しかし,
戻り番地が書換えられていないことが期待できる.
検査費用を大幅に削減できるため効率はよい.また,
この方法を破る攻撃法としては,カナリアの値を正
コンパイラを利用したアプローチでは,未知のスタッ
しく予測し,それを悪性文字列の当該個所に含め,カ
ク破壊攻撃に対処できるという重要な利点がある.
ナリアが変化していないように偽装すること,あるい
5.2.1 直接的な検査法 (StackShield, BOWall)
は,カナリアの領域を書き換えずに戻り番地を書換え
StackShield [35] と BOWall [19] はそれぞれ Linux
ることが考えられる.このためカナリアの値の定め方
と Windows NT をスタック破壊攻撃から守るシステ
はこの検査法にとって重要である.後者については,
ムである.いずれのシステムも,関数処理の開始時
バッファが構造体の配列で,たまたまアラインメント
に戻り番地をスタックとは別の安全な場所に保存し,
の調整のためにカナリアの位置が隣接される構造体
関数処理を終える直前にスタック上の戻り番地と保
データの隙間にあたり,カナリアに書き込まれないと
存された値を比較して書換えられていないことを検
いう極めて限定された状況が想定できる.
査する.StackShield は gcc のバックエンドとして実
カナリアの与え方には二通りが提案されている.無
装されているため,利用するためにはソフトウェアの
作為カナリア(random canary) 法では,ソフトウェ
再コンパイルを要する.BOWall は,libsafe と同様,
アが起動するとき無作為に選ばれたワード値をカナ
標準ライブラリに含まれる危険な関数に対するラッ
リアとして利用する.この手法はカナリアの予測の困
パとして実現されている.アプローチの利害得失は
難さによってスタック破壊攻撃から防御する.無作為
libsafe と同様である.
カナリア法の問題点は,共有ライブラリの保護ができ
BOWall はさらに,return-into-libc 攻撃に対処す
ない点である.無作為カナリア法では,起動時に定ま
るために,すべてのライブラリ関数に,関数の呼出し
るカナリア値を保存しておく必要があるが,ソフト
元(すなわち,関数の戻り番地)についての検査コー
ウェアごとに異なるカナリア値を共有ライブラリに与
ドを追加する.この検査コードは関数の呼出し元が
えることができないからである.このような欠点を補
スタック領域やデータ領域ではないことを検査する.
うために,次の終端カナリア法が考え出された.
この機能は,Windows の動的リンクライブラリ(dll
終端カナリア(terminator canary) 法では,固定し
ファイル)として実装されており,動的リンク表のリ
たカナリア値を用いる.固定したカナリア値では攻
ンク先を変更して,すべてのライブラリ関数の処理に
撃側に予測されやすいため,カナリア値にさまざま
際して検査を実行する.
な終端符号を含めることによって,仮にカナリアが
5.2.2 カナリア法 (StackGuard)
StackGuard [4] は,関数の戻り番地の変化を捉える
代りにスタックフレーム内の隣接したデータの値の
予測されても悪性文字列の中のカナリア値までしか
†3 欧州の鉱夫が酸素欠乏の危険を知るために,炭坑にカ
ナリアを持ち込んだことに由来する.
コンピュータソフトウェア
8
バッファにコピーされない工夫をしている.具体的
( 8 )
Lower memory address
Higher memory address
には,カナリア値として文字列の終端を表す空文字
コード (0x00),リターンコード (0x0D),行末コード
buf
f2
(0x0A),ファイルの終端コード (0xFF) を寄せ集めた
“0x000D0AFF” のような値を用いる.終端カナリア
prev. canFP ary
ret
f1
StackGuard [4] のスタックフレームの構造
の中の符号は,多くのバッファ処理関数によってデー
タ列の終端として解釈されるために,終端カナリアを
f2
f1’
buf
正しく予測した場合でも,終端カナリアを先に配置さ
れている戻り番地を上書きすることは難しい.
can- prev.
ret
ary FP
f1
copy f1 to f1’
propolice [10] のスタックフレームの構造
終端カナリア法は,バッファに書き込む処理が終端
カナリアに影響を受けない場合には無力である.この
void bar(void (*func1)()) {
ため,無作為カナリア法に比べて防御力が劣る.終端
void (*func2)();
カナリア法の利点は,共有ライブラリの保護に用いる
char buf[128];
ことができる点に加え,一貫性の検査がカナリアと定
...
数との比較で実現できるため効率がよいことである.
gets(buf);
(*func1)(); (*func2)();
StackGuard は,gcc のコード生成部を変更するこ
とで実現されている.システム全体を StackGuard で
守るためには,システム内のすべてのソフトウェアと
}
図5
StackGuard と propolice の違い
ライブラリを StackGuard のパッチがあたった gcc で
再コンパイルする必要がある.Linux の配布パッケー
これによって,フレームポインタを間接的に利用した
ジ全体を StackGuard を利用してコンパイルしたも
複雑な攻撃も阻んでいる.
のがある.
propolis は,スタックフレームの構成を変更するた
5.2.3 変数配置の工夫 (propolis)
めにコンパイラの中間コードの層を変更している.こ
江藤による propolice [10] [11] は StackGuard のス
れは,StackGuard や StackShield がコンパイラバッ
タック一貫性検査機構に加えてスタックフレームの配
クエンドに対する小さな修正として実現されている
置を工夫することで,関数の戻り番地だけでなく,フ
ことと比べて特徴的である.propolis では,コンパイ
レームポインタや局所変数や関数の引数に与えられ
ラ内部の情報にアクセスできることを積極的に利用
るポインタも保護する.
して,動的な検査に対する最適化をレジスタ割当ての
たとえば,図 5 の bar 関数に対するスタックフレー
ムの表現が両システムで異なることが分かるであろ
工夫や無駄な検査コードの出力の抑制などによって実
現している.
う.注目すべき点は,propolice では,関数の中で利
5.2.4 コード書き換え (libveryfy)
用されるポインタ値がいずれも配列より低位アドレ
Libverify は libsafe シ ス テ ム の 一 部 に 含 ま れ ,
スに割当てられていることである.ANSI C の言語仕
StackGuard と同様の戻り番地の一貫性を検査する.
様書は,関数の局所変数の配置に任意性を認めてい
StackGuard とは異なり,動的リンクライブラリとし
るために,このような表現が可能となる.一方,関数
て実装されている.このため,ソフトウェアを再コン
の引数は,局所変数にコピーし,関数の中ではコピー
パイルせずにセキュリティを強化できる.
を利用するため,バッファ溢れによって実際の引数が
Libverify は一貫性検査のための汎用的なラッパ関
破壊されても,関数の実行に影響を及ぼさない.さら
数 wrapper entry と wrapper exit を含むほか,ロー
に,カナリアの位置をずらすことによって,戻り番地
ドされた他のライブラリを命令編集するためのコー
だけでなく,フレームポインタの破壊を避けている.
ドから構成される(図 6).命令編集の機能は,他の
( 9 )
Vol. 1 No. 1 Jan. 1984
まとめ
wrapper_entry() {
// store return address
// jump to new_entry
}
1
表 2 に本節で紹介したシステムについてまとめた.
2
wrapper_exit() {
// verify return address
return;
}
動的リンクライブラリ機能を用いた防御機構は,シ
ステムの透明性を維持する点で優れている.有効性
4
void main() {
=> jump to
char buf[96];
wrapper_entry;
...
strcpy(buf, large_str);
return;
}
図6
3
9
void new_main() {
char buf[96];
...
strcpy(buf, large_str);
// jump to wrapper_exit;
}
Libverify のコード編集方式
については,特定の標準関数のみを対象とするシス
テムは不十分と判断した.propolis は変数の配置の工
夫,フレームポインタと関数引数の保護などにより,
他に抜きんでた防御力を持つと判断した.性能につ
いては MemGuard 以外については,実用的な性能で
動的リンクライブラリがロードされると,そのライブ
あると判断できる.libparanoia と libsafe で実装され
ラリをヒープにコピーし,もともとのライブラリのそ
ているスタックフレームの解析はカナリア法に比べ
れぞれの先頭アドレスを wrapper entry を介したコ
て,検査費用が高い.しかし,検査対象が限られるた
ピー先の呼出しに置き換え,コピー先のライブラリ
めソフトウェアの実行速度に及ぼす影響は無視でき
のそれぞれの関数の RET 命令を wrapper exit で置
る.スタック破壊以外の攻撃については,BOWall は
き換える.これにより,もともとのライブラリはラッ
return-into-libc 攻撃への一定の対応をしている.
パの呼出しに利用されるだけで,実際の処理はヒープ
上のコピーが実行する.
6 バッファ溢れの動的検査
ここまでに述べてきた手法の多くはスタック破壊攻
5.3 メモリ保護機構の利用 (MemGuard)
撃に対する防御法であり,一般的なバッファ溢れ攻撃
StackGuard システムにはカナリアを用いた実装と
に対しては部分的な解決でしかない.バッファ溢れ攻
は別に,仮想記憶のメモリ保護機能を利用して実装さ
撃に対するより本質的な解決はバッファ溢れを発見す
れた MemGuard がある [5].MemGuard はワード単
る方法である.本節ではバッファ溢れを実行時に検知
位のメモリ保護機能を利用して戻り番地の変更を検
するための方法を解説する.
知するシステムである.
MemGuard の実装方法は,基本的にはオペレー
6.1 Purify
ティングシステムのページ単位のメモリ保護機能を用
Purify [15] は,メモリリークなどのメモリ操作上の
いることによって戻り番地への変更をメモリ違反とし
さまざまなバグを動的に発見するための商用ツールで
て検知する.この方法の明らかな問題点は,戻り番地
ある.Purify は,ソフトウェアの実行可能ファイル
と同じページに配置されている局所変数の変更がす
の中のメモリ操作命令に検査コードをつけ加えるこ
べてメモリ違反となることである.この場合,一時的
とによって,メモリ操作をモニタする.また,静的,
にページを書きこみ可能に設定することによって,局
あるいは動的な記憶域の確保と解放を捉えて,確保さ
所変数の変更を可能にする.MemGuard では,CPU
れている記憶域を管理する.実行時にメモリが操作さ
のデバッグ用のレジスタを利用してスタックトップか
れると,その操作の検査コードは記憶域の管理表を
ら数個のフレームに対してはメモリ保護をはずす最
使ってアクセスの対象となる記憶域が既に確保された
適化を施している.それでも,数倍から百倍程度の速
領域であることを確認し,未確保の領域が操作された
度低下を招く.
場合には警告する.Purify の検査は,すべてのポイ
StackGuard の適応的な防御方式では,高性能なカ
ナリア方式がスタック破壊を検知した時点で,性能は
低いがより安全な MemGuard 方式に切り替える実行
方式を提案している.
ンタを検査対象に含むので戻り番地の一貫性検査よ
りも正確である.
Purify の検査は粗いために全ての不適切なポイン
タの使用を見つけられるわけではない.たとえば,
コンピュータソフトウェア
10
表2
名前
( 10 )
スタック破壊攻撃に対する動的検査法の比較
防御対象
検査方式
実装方式
有効性
性能
他の攻撃への対応
libparanoia [33]
libc
範囲検査
ライブラリ変更
△
○
—
libsafe [3]
libc
範囲検査
動的リンク
△
○
—
StackShield [35]
一般
戻り値の検査
コンパイラ
○
○
—
BOWall [19]
libc
戻り値の検査
動的リンク
△
○
△
StackGuard [5]
一般
カナリア
コンパイラ
○
○
—
propolis [10]
一般
カナリア,配置
コンパイラ
◎
○
◎
libverify [3]
一般
カナリア
動的リンク
○
○
—
MemGuard [5]
一般
メモリ保護
コンパイラ
○
××
—
A[i] がたまたま別の配列 B の中を参照した場合には
Austin らの C 言語に対するソース変換システム [2]
エラーと見なされない.このようなエラーを発見する
は,ポインタの表現に,参照する記憶域の先頭アド
ためには,以後で述べるさらに正確な検査を要する.
レスと大きさに加えて,記憶域の種別(heap, local,
global)と参照可能性(capability)を保持している.
6.2 非標準のポインタ表現を用いた範囲検査
capability を利用して参照先のデータの存否を確認で
Kendall の Bcc [18] と Steffen の RTCC [34] は,ポ
きる.
インタの表現にポインタの自然な参照領域に関する
情報を加え,ポインタが辿られるときに実際に参照さ
6.3 標準的なポインタ表現を用いた範囲検査
れるアドレスがその参照領域に含まれることを確認
Jones と Kelly の提案 [17] は,標準的なポインタ表
する.この拡張されたポインタの表現として最も単
現を維持したまま範囲検査を実現する手法である.前
純ものは,本来のポインタ (pointer ) に自然な参照領
述の手法とは異なり,ポインタの自然な参照領域に関
域の開始アドレス (base) と終端アドレス (limit) を
する情報は,ポインタとは別の splay tree を用いた
加えた三つ組である.たとえば,新たに ptr に確保
データ構造に保存し,与えられたポインタから効率的
された大きさ size の記憶域の先頭を指すポインタは
に検索できるうように工夫している.標準的なポイン
(ptr , ptr , ptr + size) と表わされる.このポインタに
タ表現を用いることによって,既存のソースにほとん
オフセット d を加えて得られるポインタは,参照領域
ど手を加えずにコンパイルできるようになった.互換
に関する情報はそのままにして pointer の情報のみを
性を確かめる実験では,12 万行の Tcl/Tk のソース
変更した (ptr + d, ptr , ptr + size) として表わされる.
コードのうち,変更を要したのは 11 個所で,いずれ
ポインタの参照先が使用されるときには,pointer が
も,もとのソースプログラムに不適切なポインタの使
[base, limit) に含まれることを検査する.これらのシ
用が見つかった個所であった.このシステムでは,性
ステムの問題点は,ポインタ表現を変更することに
能を改善する最適化手法として,ポインタ参照されな
よって失われる互換性,性能の劣化などである.前者
い変数の登録の省略とループ内の検査コードのルー
に関連して,範囲検査を施したモジュールをそうでは
プ外へ移動を実装している.これらの最適化により
ない標準的なモジュールとリンクできない問題点が
多くのシステムの実行性能は 3-5 倍の劣化にとどまっ
ある.
ている.
これらのシステムに共通する問題点として free 関
数によってヒープに回収された記憶領域へのアクセス
(迷子ポインタの使用)の検査ができないことがある.
( 11 )
Vol. 1 No. 1 Jan. 1984
6.4 型変換への対応
大岩らによる C 言語のソースレベル変換器 [27] は,
11
バッファ溢れが起こらないことが示せる検査コードを
除去できる点である.文献 [2] [17] はこのような最適化
Bcc, RTCC と同様,非標準の表現を利用することに
が動的検査の費用を低減することに大きく貢献するこ
よって記憶域の情報を管理している.他の仕事に比
とを報告している.彼らは C 言語のイディオムを利
べた特徴は自動的な記憶域管理(ゴミ集め)を利用
用した簡単な解析であった.本節で紹介する手法は,
することと型変換(キャスト)にも対応しているこ
プログラムの意味を捉える本格的な解析手法である.
とである.ポインタは参照する記憶域の先頭アドレ
静的解析の困難は,プログラムの意味をなるべく正
ス (base) とそこからのオフセット値 (offset) を用い
確に捉えることである.静的解析に求められる性質と
て表現される.base の未使用ビット (casted ビット)
して,完全性と健全性がある.バッファ溢れバグの検
はそのポインタが型変換の結果得られたものか否か
出に関していえば,完全性はすべてのバグを発見でき
を表す.C 言語では整数値とポインタ値間で相互の型
ること,健全性は発見したものは確かにバグである
変換を許しているため,整数値についても型変換に
ことを意味する.完全でないシステムが与える誤った
ついても casted ビットの情報を保持する必要がある.
結果,すなわち本当はバグであるにも関わらず,シス
このため,整数値もポインタ値と同様に 2 ワードの
テムが発見できない場合を false negative という.一
表現をとる.一方,静的,あるいは動的に割当てられ
方,健全でないシステムが与える誤った結果,すなわ
る記憶域には,型情報,領域の大きさなどが記録され
ち本当はバグではないのだが,システムが誤ってバグ
る.これらの情報は,範囲検査とともにゴミ集め処理
の可能性があると判断する場合を false positive とい
にも利用される.
う.残念ながら,本稿で紹介する研究はいずれも完全
でも健全でもない.
まとめ
以上のシステムの比較をまとめると表 3 のようにな
る.空間検査の列は,範囲検査の有無を表す.Purify
7.1 註釈の利用 [20]
LCLint [12] は,軽量なソフトウェアの検証システ
は,配列の範囲を越えたポインタがたまたま他の配列
ムである.LCLint は,プログラム中の註釈として付
の中を指した場合を区別できないため△とした.時
加される型宣言,変数宣言,関数宣言についての制
制検査の列は,free された領域の参照,すなわち迷子
約を利用することでプログラムのさまざまな性質を解
のポインタ (dangling pointer ) についての検査できる
析するツールである.もともとは,迷子のポインタの
ことを示している.(OSY01) は記憶領域はゴミ集め
発見などに利用されていたが,制約システムを拡張す
されるためこの項目から除外した.型変換,特に整数
ることによってバッファ溢れの検出に応用している.
への型変換を考慮しているのは (OSY01) だけだが,
註釈として付加される制約は,変数が配置されたメ
Purify と (JK97) は標準的なポインタ表現を用いて
モリ領域のうち代入可能な部分 (maxSet),すでに初期
いるために,ポインタに関する情報が失なわれない.
され読み込み可能な部分 (maxRead) などを利用した
precondition(requires) と postcondition(ensures)
7 静的解析
によって表現される.以下は strcpy 関数の例である:
静的解析は,ソースプログラムや実行可能形式を解
char *strcpy (char *s1 , const char *s2 )
析することで,ソフトウェアを動作させることなく,
/*@ requires maxSet(s1 )≥maxRead (s2 ) *@/
ソフトウェアに内在するバッファ溢れを見つける手法
/*@ ensures maxRead (s1 )=maxRead (s2 )
である.静的解析の目的は,ソフトウェアを出荷する
∧ result =s1 @*/
前の段階でバッファ溢れのバグを発見し,そのバグを
LCLint では,文字列操作関数など 7 つの関数に対
修正できる点である.また,別の応用としては,すで
して制約条件を定義し,それを元にプログラム全体を
に見てきた動的な検査手法と組み合わせて,静的に
解析する.プログラマは,LCLint の制約の検証を助
コンピュータソフトウェア
12
表3
名前
( 12 )
範囲検査法の比較
空間
時制
型変換
互換性
実装法
Bcc [18]
○
×
×
△
ソース変換
RTCC [34]
○
×
×
△
コンパイラ
Purify [15]
△
○
○
◎
バイナリ変換
(ABS94) [2]
○
○
×
△
ソース変換
(JK97) [17]
○
○
○
◎
コンパイラ
(OSY01) [27]
○
—
◎
△
ソース変換
ける目的でプログラムに制約を付加できる.
が単純かつ効率的になる.ここで得られる制約は,プ
LCLint の特徴は,解析の高速性である.高速性
ログラムに現れる文字列の大きさと長さに関する整
を実現するために制約条件の生成とその検証アルゴ
数制約系である.この研究では,データフロー的な手
リズムでは思い切った単純化がなされている.この
法によって制約解消を行っている.LCLint が行単位
ため,理論的には問題点を多く残しつつも,実用性
で,制約解消をしていたのに対して,このシステムは
は高い.LCLint は,すでにいくつかのバッファ溢れ
大域的に解消を試みる.
バグが報告されている wu-ftpd-2.5.0(17,000 行)と
制約解消の結果は,文字列の大きさと長さのそれぞ
BIND8.2.2p7(47,000 行)に対して適用されそれぞ
れについての取り得る値の集合となる.仮に s が大き
れ 1 分と 3.5 分で解析を終えている.前者の場合,143
さとして [a, b],長さとして [c, d] を範囲とした場合,
個所で制約の検証に失敗している.これらの個所が
b < c ならば,s に関するバッファ溢れがないことが,
バグの可能性のある個所といえる.このうち,人手に
a > d ならば,s が確実にバッファ溢れすることが分
よって実際にバグが確認されたの 18 個所であったこ
かる.
とが報告されている.
このシステムを sendmail(32,000 行)に対して適
用したところ 15 分で解析が終了し,44 個所で制約
7.2 文字列範囲検査 [36]
の充足に失敗した.このうち,本当にバッファ溢れが
Wagner らは C 言語の文字列を strcpy などの操作が
発見されたのは,4 個所で,残りは false positive で
定義された抽象データ型として捉えること,さらに文
あった.この論文では,残りをさらに詳しく調査した
字列の状態を割当てられた記憶域の大きさ (alloc(s))
上で,flow-sensitive かつ context-sensitive な解析に
と初期化された部分の長さ (len(s)) の組として抽象
加えポインタ解析をすることで false positive を激減
化することを基本的なアイデアとしている.抽象デー
できると予想している.この方式の弱点は,ポイン
タ型としての文字列に対しては,メモリ割当て,初期
タ演算から得られるポインタによって生じる aliasing
化,長さ (strlen),コピー (strcpy ),連結 (strcpy ) な
やバッファへの書き込みが連続的になされない場合に
どが定義され,それぞれが生成する制約を定めてい
対応していない点などである.
る.以下は strcpy (dest , src) の例である:
len(str ) ⊆ len(dest )
7.3 機械語に対する検証 [38]
制 約 生 成 に あ たって は ,flow-insensitive か つ
Xu らは,果敢にも SPARC の機械語でのバッファ
monomorphic な解析をするため,文の順序関係は
溢れの問題に取り込んでいる.この手法では,レジス
無視され,関数の引数に依存した性質は失われる.こ
タ,メモリスロット,記憶領域のそれぞれについて,
のため,正確性は大幅に失なわれるものの,システム
型,状態,アクセス権限からなる typestate を与え,
それらが機械語のプログラムの実行に伴なって変化す
( 13 )
Vol. 1 No. 1 Jan. 1984
13
る様子を計算する.そして,与えられた初期条件でプ
メモリ安全性を保証しないプログラミング言語を用
ログラム実行を通して,別に与えられる安全性に関
いて記述されていたことにある.過去においては,実
するポリシーが満たされることを検証する.初期条
用的なシステム記述言語として他に候補がなかった事
件とポリシーは typestate に対する条件として表現さ
情もあるが,現代的なプログラミング言語では,自動
れる.
的な記憶管理機能を持ち,したがってメモリ安全性が
機械語命令をモデルするために,SPARC の機械語
保証されるものが主流となっている.例えば,Java
のそれぞれに対して抽象的な操作的意味を与えてい
や ML のように静的に型付けされた言語はもちろん,
る.さらに命令ごとに付加される安全条件が定義され
Lisp や Smalltalk のような動的に型がつく言語でも,
ている.機械語を扱う上での困難のひとつに,命令の
原理的にバッファ溢れが起きない.
持つ意味の多重性がある.例えば,加算命令は整数の
これらの安全な言語を用いて記述されたソフトウェ
加算にもポインタ演算にも利用される.両者は,機械
アでも,モバイルプログラミング [30] などでコードが
語レベルでは同じ実装だが,それぞれの結果がスカラ
ネットワーク越しに送られる場合は,ソースプログラ
かポインタという違いであり,typestate の解析に大
ムに対するメモリ安全性の保証では心もとない.バイ
きく影響を与える.この研究では,命令の多重性を入
トコードや機械語に対してメモリ安全性を保証するた
力レジスタの typestate によって解決している.この
めの枠組としては Java の型付きバイトコードとロー
命令の定義を抽象解釈することによって,初期状態の
ド時の型検査 [21],型つきアセンブリ言語 [24].これ
typestate を各命令実行に対して伝播させつつ,安全
らは,実行可能なコードに対する型情報と実行時の範
性を検証するための制約が生成できる.
囲検査によってメモリ安全性を保証する枠組である.
この時点でプログラム全体に渡って検証すべき制
[24] では,Popcorn と呼ばれる C 風のプログラミン
約が求まるので,それらを検証する.検証にあたって
グ言語から,Intel のアセンブリコードに多層的な型
は,命令の typestate から求まる局所制約とその他の
を与えたものを出力し,実行時に静的型検査を行う.
大域的な制約に分けることで性能の向上をはかってい
彼らは,Popcorn での経験をもとに,より C に近く,
る.ループ上の制約の検証にあたっては,プログラム
しかも,安全な C 言語の変種として Cyclone を提案
の自動並列化の技術で提案された解析方法を応用し
している. [16] また,証明付きコード [25] では,コン
ている.
パイラがコードとともに出力する証明を利用するこ
システムの評価には 10 命令-900 命令の大きさの
機械語モジュールを利用している.最も大きい例は,
とで,コードのメモリ安全性を保証できる.
最後に,最新に成果として,近々 ,発表される
MD5 アルゴリズムであり,その解析に 14 秒を費や
CCured システム [26] を紹介する.CCured では,C
している.いくつかのプログラムについてはその中の
言語のプログラムに現れるポインタを静的に型が定ま
バッファ溢れのバグを発見している.
るポインタ,静的に型が定まる配列の中を指すポイン
タ,静的に型が定まらないポインタに分類し,union
まとめ
やキャスト操作も含めて精密な型検査を行っている.
以上,三種の静的解析法を紹介した.この手法の
静的に判断できない部分については,6 節に述べたよ
研究は最近,盛んになっており,今後の成果が期待で
うに動的な検査を実施する.静的型情報を利用した
きる.
最適化の結果,動的な検査に要するオーバーヘッドを
0-150%に抑えることに成功している.
8 安全なプログラミング言語
これまでに述べた防御方法は,既存のソフトウェア
9 おわりに,
をバッファ溢れ攻撃から守る手法であった.そもそも
ここまで,バッファ溢れ攻撃とその防御方法につい
の問題は,これらのソフトウェアが C 言語のように
て,簡単にまとめた.動的な検査方法については,基
14
コンピュータソフトウェア
( 14 )
本的なアイデアは出尽した感がある.実行時にメモリ
波大学),佐々政孝先生(東京工業大学),柴山悦哉
アクセスの範囲を検査する手法については,静的な解
先生(東京工業大学),関口龍郎氏(東京大学)に感
析と併せた最適化によって実用的な性能が得られるか
謝いたします.草稿に目を通して下さった内山雄司氏
が興味深い.静的な解析については,今後の理論的な
(東京工業大学)に感謝します.
追及が待たれる.
バッファ溢れ攻撃とその防御方法については,こ
れまでにもよい解説がいくつかある [32] [6] [31].文献
[10] は,スタック破壊攻撃以外のいくつかのバッファ
溢れ攻撃を概説している.著者のウェブサイト [37] に
は,本稿に紹介しなかった関連論文の文献リストがあ
るので,参考にしていただきたい.
さて,バッファ溢れ攻撃についての調査を初めた当
初から,筆者が抱いていた疑問があり,未だにその結
論が見つかっていない.
「もし,スタックが高位アドレ
スに向かって成長するように設計された環境ではバッ
ファ溢れ攻撃は難しかったのではないだろうか?少な
くとも,単純なスタック破壊攻撃や return-into-libc
攻撃は防げたのではないだろうか?では,なぜそれほ
ど直感的と思えない,スタックの低位番地に向けた成
長方向が業界標準になっているのだろうか?」
実は,スタックを低位番地に向かって成長させるこ
とはハードウェア設計の必然とは限らないようだ.ス
タックが高位アドレスに成長するハードウェアの例と
しては ARM がある.なかには,MIPS Tandem の
ように,成長方向をソフトウェアで指定できるものも
ある.M17 は戻り番地を保存するための専用スタッ
ク (return stack ) を持っていた.IBM 370 も類似の
機構を持ち,この種の機械でバッファ溢れ攻撃の報告
が少ないひとつの背景となっているようだ.仮に,業
界標準のアーキテクチャが高位アドレスに成長するス
タックを実装させていたら,われわれのバッファ溢れ
攻撃に対する悩みは少なかったのであろうか.
.
.ご存
じの方はご教示下さい.
謝辞
本稿の執筆の機会を与えて下さいました米崎直樹先
生(東京工業大学)ならびに本誌編集委員会の方々に
感謝いたします.また執筆にあたり参考文献を紹介し
て下さった江藤博明氏(日本 IBM),大岩寛氏(東京
大学),大山恵弘氏(筑波大学),加藤和彦先生(筑
参考文献
[ 1 ] Aleph One: Smashing the stack for fun and
profit, 1996. Posted to Phrack 49, Volume 7, Issue 49, File 14 of 16.
http://www.shmoo.com/phrack/Phrack49/p49-14.
[ 2 ] Austin, T. M., Breach, S. E., and Sohi, G. S.:
Efficient detection of all pointer and array access
errors, proceedings of the conference on programming language design and implementation, 1994,
pp. 290–301.
[ 3 ] Baratloo, A., Singh, N., and Tsai, T.: Transparent Run-Time Defense Against Stack Smashing
Attacks, proceedings of the USENIX Annual Technical Conference, Jun. 2000.
[ 4 ] Cowan, C., Beattie, S., et al.: Protecting systems from stack smashing attacks with StackGuard,
Linux Expo, Raleigh, NC, May 1999.
[ 5 ] Cowan, C., Pu, C., et al.: StackGuard: Automatic adaptive detection and prevention of bufferoverflow attacks, 7th USENIX security conference,
San Antonio, TX, Jan. 1998, pp. 63–77.
[ 6 ] Cowan, C., Wagle, P., Pu, C., Beattie, S., and
Walpole, J.: Buffer overflow: Attacks and Defenses
for the vulnerability of the Decade, proceedings of
DARPA information survivability conference and
exposition, 1999.
[ 7 ] Designer, S.: Getting around non-executable
stack (and fix), Mailing list, Aug. 1997.
http://ouah.bsdjeunz.org/solarretlibc.html.
[ 8 ] Dik, C. H. S.: Non-Executable Stack for Solaris,
posted to comp.security.unix, Jan. 1997.
http://x10.dejanews.com/.
[ 9 ] Eichin, M. W. and Rochlis, J. A.: With Microscope and Tweezers: An analysis of the internet virus of November 1988, proceedings of the 1989
IEEE computer society symposium on security and
privacy, 1989.
[10] Etoh, H.: GCC extension for protecting applications from stack-smashing attacks, Web page, 2001.
http://www.trl.ibm.com/projects/security/ssp/.
[11] 江藤博明: propolice: スタックスマッシング攻撃検
出手法の改良, 情報処理学会コンピュータセキュリティ
研究会, 東京, Jul. 2001.
[12] Evans, D.: Static detection of dynamic memory errors, proceedings of the conference on programming language design and implementation,
Philadelphia, PA USA, ACM, May 1996, pp. 21–24.
[13] Forrest, S., Somayaji, A., and Ackley, D.: Building Diverse Computer Systems, proceedings of the
6th workshop on hot topics in operating systems,
Albuquerque, NM, USA, IEEE, May 1997.
( 15 )
Vol. 1 No. 1 Jan. 1984
[14] Ghosh, A. K., O’Connor, T., and McGraw, G.:
An automated approach for identifying potential
vulnerabilities in software, proceedings of the IEEE
symposium on security and privacy, Oakland, CA,
May 1998.
[15] Hastings, R. and Joyce, B.: Purify: Fast detection of memory leaks and access errors, winter
USENIX conference, 1992, pp. 125–136.
[16] Jim, T., Morrisette, G., et al.: Cyclone: A safe
dialect of C, 2001.
http://www.cs.cornell.edu/projects/cyclone/.
[17] Jones, R. W. M. and Kelly, P. H. J.: Backwardscompatible bounds checking for arrays and pointers
in C programs, proceedings of third international
workshop on automated debugging, 1997.
[18] Kendall, S. C.: Bcc: run-time checking for C
programs, USENIX Toronto 1983 summer conference, El. Cerrito, CA, 1983.
[19] Kolishak, A.: BOWall, Web page, 2000.
http://www.security.nnov.ru/bo/eng/BOWall/.
[20] Larochelle, D. and Evans, D.: Statically Detecting Likely Buffer Overflow Vulnerabilities, 2001
USENIX security symposium, Washington, D. C.,
Aug. 2001.
[21] Lindholm, T. and Yellin, F.: The JavaTM Virtual Machine Specification, Addison-Wesley, 1997.
http://java.sun.com/docs/books/vmspec/.
[22] Linux security auidit project: Welcome to the
Linux security audit project homepage.
http://www.lsap.org/.
[23] Miller, T. C.: strlcpy and strlcat — consistent, safe, string copy and concatenation, USENIX
annual technical conference ’99, FREENIX track,
Monterey, CA, Jun. 1999.
[24] Morrisett, G., Crary, K., D., N. G., and Walker:
Stack-based typed assembly language, proceedings
of Workshop on Types in Compilation, Lecture
Notes in Computer Science, Vol. 1473, SpringerVerlag, Berlin, Germany, 1998, pp. 28–52.
[25] Necula, G. C.: Proof-Carrying Code, proceedings of the 24th Annual SIGPLAN-SIGACT Symposium on Principles of Programming Languages,
1997, pp. 106–117. Available from
http://www.acm.org/dl/.
15
[26] Necula, G. C., McPeak, S., and Weimer, W.:
CCured: Type-safe retrofitting of legacy code, proceedings of the 29th Annual SIGPLAN-SIGACT
Symposium on Principles of Programming Languages, Portland, OR, Jan. 2002.
[27] 大岩寛, 住井英二郎, 米澤明憲: 安全性を保証する
ANSI-C 実行系の実装手法, ソフトウェア科学会第 18
会大会(2001 年度)論文集, 函館, 日本ソフトウェア科
学会, Sep. 2001.
[28] OpenBSD: OpenBSD security.
http://www.openbsd.org/security.html.
[29] Project, O.: Linux kernel patch from the Openwall project.
http://www.openwall.com/linux/.
[30] 佐藤一郎: モバイルエージェント, コンピュータソフ
トウェア, Vol. 17,No. 2(2000), pp. 153–162.
[31] Simon, I.: A comparative analysis of methods of
defense against buffer overflow attacks, Web page,
Jan. 2001.
[32] Smith, N.: Stack smashing vulnerabilities in the
UNIX operating system.
http://www.hack.gr/users/dawn/archives/ps/natebuffer.ps.gz.
[33] Snarskii, A.: Increasing overall security...., Web
page, Feb 1997.
http://www.lexa.ru/snar/libparanoia/.
[34] Steffen, J. L.: Adding run-time checking to the
portable C compiler, Software — Practice and experience, Vol. 22,No. 4(1992), pp. 305–316.
[35] Vendicator: Stack Shield: A “stack smashing”
technique protection tool for Linux, Web page.
http://www.angelfire.com/sk/stackshield/.
[36] Wagner, D., Foster, J., Brewer, E., and Aiken,
A.: A first step towards automated detection of
buffer overrun vulnerabilities, proceedings of network and distributed system security (NDSS), 2000.
[37] 脇田建: スタック溢れ攻撃とその防御方法に関する
文献リスト, Webpage, Sep 2001.
http://www.is.titech.ac.jp/˜wakita/surveys/.
[38] Xu, Z., Miller, B. P., and Reps, T.: Safety
Checking of Machine Code, Proceedings of the conference on programming language design and implementation, June 2000.