A First Step Towards Automated Detection of Buffer Overrun
Vulnerabilities情報理工学系研究科 米澤研
M1 田渕 直 ([email protected])
概要• C 言語で書かれたプログラムに存在す
る、潜在的な Buffer Overrun バグをソースコードの static な解析によって検出する方法を提案する。
C プログラムの問題点• C 言語はいくつかの点で安全ではない
– 配列境界をチェックしない– 配列境界に無頓着なライブラリ関数の存
在e.g. gets(), strcpy(), etc…
– ライブラリ関数の仕様の不統一e.g. strncpy(d, s, sizeof d) ○ strncat(d, s, sizeof d) ×
解決策の提案• 安全でない言語使用やライブラリの結果、
– 正しいプログラムを書くのは難しい。– 危険なプログラムはすぐに書ける。
• しかし、現実には Legacy な C 言語に頼らざるを得ない場面も多い。
⇒ 人手によらず、危険な個所を自動検出できるツールがあれば嬉しい。
システムの特徴 (1/4)
• 静的解析 : C ソースプログラムの静的な解析に基づいて危険な個所を検出する。
• Scalability > Precision: 検出結果の過不足には目をつぶって、現実のアプリケーションに適用できる程度の性能を目指す。
• 対象 : 解析の対象は文字列 ( と文字列用ライブラリ関数 ) に限定。
システムの特徴 (2/4)
• 静的解析– Runtime testing では全ての条件とパスをテス
トできるとは限らない。– 特に、セキュリティ上最も重要なコードは、
通常実行されることのないパスに潜んでいる。
e.g. if (strlen(src) > sizeof (dst)) break;strcpy(dst, src);
What if strlen(src) == sizeof (dst) ?
システムの特徴 (3/4)
• C 文字列を抽象データ型として扱う– ポインタは一般に静的解析と相性が悪い。– ほとんどの buffer overrun は文字列バッフ
ァについて起こる。
⇒ 文字列とライブラリ関数の動作を直接モデル化することによって、ポインタの解析を回避する。ポインタを直接操作するようなコードは、誤りを検出できない。
システムの特徴 (4/4)
• 「バッファ」のモデル化– 文字列バッファを「確保サイズ」・「文字
列長」の整数範囲のペアとしてモデル化
⇒ 文字列の内容を意識しない処理が可能。⇒ 問題を integer range constraint として定式化
できる。
システムの制限• Scalability > Precision の構図に基づき、
以下の制限を設けた実装とした。1. 制御フローを無視。2. ポインタの解析はしない。3. 構造体は一括して扱う ( 次項 ) 。
構造体の扱いについて• C の慣習として、構造体はポインタと
組み合わせて使われることが多い。• 構造体を無視しては実世界のアプリケ
ーションを検証することはできない。⇒ 同じ型の構造体のインスタンスは制約
システム中で全て一括して扱うという方針で、妥協的に解決した。
アーキテクチャ
C parserInteger constraint
generationConstraint solver
Source
Syntaxtree
Const-raints
Warnings
制約の表現 (1/4)
• Integer range:integer range は Z∞ = Z {±∞} ∪ の部分集合で、 [m, n] = {m, …, n}
• Range closure: S Z⊆ ∞ に対し、 range-closure(S) = [inf S, sup S]
制約の表現 (2/4)
• Operations on range closure:S, T Z⊆ ∞ に対し、
S + T = range-closure({ s+t | s S, t ∈ ∈
T})S – T, S * T も同様に定義される。
• {n} を n と略記する。e.g. 2T = {2} * T = { 2t | t T}∈
制約の表現 (3/4)
• Integer range expression:e ::= v | n | n * v | e + e | e – e
| max(e, …, e) | min(e, …, e)where n Z, v ∈ は変数。
• Integer range constraint:制約は e v ⊆ の形に表される。
制約の表現 (4/4)
• Assignment:assignment α は、各変数 v に対しα(v) Z⊆ ∞ ( 実際には range) を割当てる。
• 定義 : α が制約システムを満たす⇔ constraint 中の各変数 v を α(v) で置き換えた時に、全ての constraint が成立。
• 定義 : α β ⇔ α(v) β(v) for v⊆ ⊆ ∀
制約の生成 (1/3)
• C ソースの構文木から、一連の constraint を生成する。– 整数変数 v に対し、変数 v を対応させる。– 文字列変数 s に対し、確保長 alloc(s) と
文字列長 len(s) を対応させる (* len(s) は‘ \0’ を含む ) 。
– 代入 v = e に対し、 e v ⊆ を生成。– 各文字列操作に対し、対応する制約を生成。
制約の生成 (2/3)
Code Interpretation
char s[n]; n alloc(s)⊆
strlen(s) len(s) - 1
strcpy(d, s); len(s) len(d)⊆
s = “foo”; 4 len(s), 4 alloc(s)⊆ ⊆
p = malloc(n) n alloc(p)⊆
strcat(s, suf); len(s)+len(suf)-1 len(s)⊆
文字列操作に対応する制約の例。
制約の生成 (3/3)
• 関数 f(int p) の呼出し b = f(a);
は p = a 、 b = f_return と解釈。• ポインタ操作
q = p+j;は alloc(p) – j alloc(q)⊆ 、len(p) – j len(q) ⊆ と解釈 (aliasing等を無視 ) 。
制約の解決• 制約の解決は、全ての変数が取り得る値
の範囲をカバーする最小の bounding box を求めることに相当。
• 求まった bounding box から各 len(s) と alloc(s) の関係を見ることで overrun を検出。
アルゴリズム (1/2)
• とりあえず f(vi) = avi + b v⊆ j の形の制約のみで考える。– 各 vi に頂点 vi を割り当て– 制約 f(vi) v⊆ j に対し、枝 vi →f vj を割り当
て。– 各 vi に対し α(vi) = φ
– 制約 n v⊆ i に対し、α(vi) = range-closure(α(vi) {n})∪
アルゴリズム (2/2)
• 前項の初期化の後、グラフの枝に沿って情報を伝播させる。– f(α(vi)) ⊈ α(vj) であれば
α(vj) = range-closure(α(vj) f(α(v∪ i)))
• グラフが閉路を含んでいれば、その部分に関しては、別途直接計算で解を求める。
擬似コード (1/2)
Constraint-Solver:Set α(vi) = φ for all i, done = falseFor each constraint of the form n w⊆
Set α(w) = range-closure(α(w) {n})∪While done = false call One-Iteration
One-Iteration:Set C(vi) = white for all i, done = trueFor each vi
if (C(vi) == white)Set prev(vi) = null, call Visit(vi)
擬似コード (2/2)
Visit(v):Set C(v) = grayFor each constraint of the form f(v) w⊆
if (f(α(v)) ⊈ α(w)) Set α(w) = range-closure(f(α(v)) ∪ α(w)) Set done = false if (C(w) == gray)
Call Handle-Cycle(v, w, prev) if (C(w) == white) Set prev(w) = v, call Visit(w)
Set C(v) = black
Overrun の検出• 制約の解決後、各 alloc(s), len(s) の関係から overrun を検出。
• alloc(s) = [a, b], len(s) = [c, d] として…1. b c overrun ≦ ⇒ は起こらない。2. a > d ⇒ 常に overrun が起こる。3. Otherwise overrun ⇒ が起こる可能性有り。
性能評価• いくつかの実際に使用されているパッ
ケージに対してツールを適用し、性能評価を試みた。
• 人手でソースを解読して false alerm 率を測定。
評価 1. Linux net tools
• Linux net tools に関して、今まで報告されていない問題を検出できた。– DNS ・ NIS の応答を信頼している個所が
あり、悪意のある応答に対して脆弱。• 既知の問題をどの程度検出できたかに
ついては言及がなかった。
評価 2. Sendmail 8.9.3 (1/2)
• 既知の問題の多さ、プログラム規模等の面で sendmail は良い題材である。
• セキュリティホールは検出されなかったが、 queue の扱いに関して小さな off-by-one バグを検出した。
• False alerm 率の高さのため、人手による検証に多くの時間がかかった。
評価 2. Sendmail 8.9.3 (2/2)
• False alerm の例1. if (sizeof dst < strlen(src)+1) break;
strcpy(dst, src);
2. char* ptr;if (A) ptr = “abc”;else ptr = “defgh”;
→ 制御フローを無視することによるもの。
→ bounding box 解析の原理上の制限。
評価 3. Sendmail 8.7.5
• 公表されていない数多くのバグを検出した。
• chfn に関する既知の問題は検出できなかったらしい。
パフォーマンス• Sendmail の検証にかかった時間は
Pentium III で 15 分程度。• 検証結果を人手で確認する手間の方が大きいので、 CPU時間はそれほど問題にならないのではないか。
問題点• 現在のシステムの主な問題点は ,
– False alerm の多さ– 検出ミスの可能性– 出力の情報量不足
False alerm の多さ (1/2)
• Precision を犠牲にした設計のため、実際には問題のないコードが引っかかることが多く、結局人手での検証に負担がかかる。
e.g.Sendmail の検証では Probable とされた 44箇所のうち、 40箇所が false alerm だった。
False alerm の多さ (2/2)
• 解析手法の強化でどの程度精度が向上するかを見積もった。– 制御フロー– ポインタ解析– 文脈の情報– Linear invariants
手法 結果フロー 19/40
フロー +ポインタ
25/40
フロー + 文脈 + inv.
28/40
全部 38/40
⇒ もう少し精度を重視しても良かったかもしれない。
検出ミスの可能性• 検出ミスの数を完全に見積もることは
難しい。• Sendmail 8.7.5 の検証では…
chfn に関するバグは検出できなかった ( ポインタを直接操作していたため ) 。その他の、未公表のまま修正されていた問題は全て検出した。
出力の情報量不足• ツールの報告は「どのバッファが溢れるか」
のみ。「どこで溢れるか」は示されない ( 制約に基づくシステムの原理的な限界 ) 。
⇒ 「バッファ溢れに関係する変数名を同時に報告する」というヒューリスティクスを採用して、多少の改善を試みた。
結論• Buffer overrun の静的解析技法として、 2 つの
アイデアを提案した。– 文字列のモデル化– 制約に基づく解析
• このアプローチは十分に有効で実用的。• 精度を犠牲にして、大きなプログラムに適用
できるだけの scalability を達成できた。• 人手では発見できない小さなバグも発見でき
た。• 精度に関しては改善の余地がある。