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の慣習として、構造体はポインタと組み 合わせて使われることが多い。 • 構造体を無視しては実世界のアプリケー ションを検証することはできない。 ⇒ 同じ型の構造体のインスタンスは制約シ ステム中で全て一括して扱うという方針で、 妥協的に解決した。 アーキテクチャ Source Integer constraint Constraint solver generation Syntax Consttree raints C parser 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⊆ vj の形の制約 のみで考える。 – – – – 各vi に頂点 vi を割り当て 制約 f(vi) ⊆ vj に対し、枝 vi →f vj を割り当て。 各viに対し α(vi) = φ 制約 n ⊆ vi に対し、 α(vi) = range-closure(α(vi) ∪ {n}) アルゴリズム (2/2) • 前項の初期化の後、グラフの枝に沿って 情報を伝播させる。 – f(α(vi)) ⊈ α(vj) であれば α(vj) = range-closure(α(vj) ∪ f(α(vi))) • グラフが閉路を含んでいれば、その部分に 関しては、別途直接計算で解を求める。 擬似コード (1/2) Constraint-Solver: Set α(vi) = φ for all i, done = false For 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 = true For each vi if (C(vi) == white) Set prev(vi) = null, call Visit(vi) 擬似コード (2/2) Visit(v): Set C(v) = gray For 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 フロー + ポインタ フロー + 文脈 + inv. 25/40 全部 38/40 28/40 ⇒ もう少し精度を重視しても良かったかもしれない。 検出ミスの可能性 • 検出ミスの数を完全に見積もることは難し い。 • Sendmail 8.7.5 の検証では… chfn に関するバグは検出できなかった (ポインタを直接操作していたため)。 その他の、未公表のまま修正されていた 問題は全て検出した。 出力の情報量不足 • ツールの報告は「どのバッファが溢れる か」のみ。「どこで溢れるか」は示されない (制約に基づくシステムの原理的な限界)。 ⇒ 「バッファ溢れに関係する変数名を同時に 報告する」というヒューリスティクスを採用し て、多少の改善を試みた。 結論 • Buffer overrun の静的解析技法として、2つのア イデアを提案した。 – 文字列のモデル化 – 制約に基づく解析 • このアプローチは十分に有効で実用的。 • 精度を犠牲にして、大きなプログラムに適用でき るだけのscalabilityを達成できた。 • 人手では発見できない小さなバグも発見できた。 • 精度に関しては改善の余地がある。
© Copyright 2024 ExpyDoc