360 likes | 512 Views
A First Step Towards Automated Detection of Buffer Overrun Vulnerabilities. 情報理工学系研究科 米澤研 M1 田渕 直 ( tabee@tabee.com ). 概要. C 言語で書かれたプログラムに存在する、潜在的な Buffer Overrun バグをソースコードの static な解析によって検出する方法を提案する。. C プログラムの問題点. C 言語はいくつかの点で安全ではない 配列境界をチェックしない
E N D
A First Step Towards Automated Detection of Buffer Overrun Vulnerabilities 情報理工学系研究科 米澤研 M1 田渕 直 (tabee@tabee.com)
概要 • 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 の構図に基づき、以下の制限を設けた実装とした。 • 制御フローを無視。 • ポインタの解析はしない。 • 構造体は一括して扱う(次項)。
構造体の扱いについて • Cの慣習として、構造体はポインタと組み合わせて使われることが多い。 • 構造体を無視しては実世界のアプリケーションを検証することはできない。 ⇒ 同じ型の構造体のインスタンスは制約システム中で全て一括して扱うという方針で、妥協的に解決した。
アーキテクチャ Source C parser Integer constraint generation Constraint solver 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) 文字列操作に対応する制約の例。
制約の生成 (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 = 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] として… • b ≦ c ⇒ overrun は起こらない。 • a > d ⇒ 常にoverrunが起こる。 • 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 の例 • if (sizeof dst < strlen(src)+1) break;strcpy(dst, src); • 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 だった。
解析手法の強化でどの程度精度が向上するかを見積もった。解析手法の強化でどの程度精度が向上するかを見積もった。 制御フロー ポインタ解析 文脈の情報 Linear invariants False alerm の多さ (2/2) ⇒ もう少し精度を重視しても良かったかもしれない。
検出ミスの可能性 • 検出ミスの数を完全に見積もることは難しい。 • Sendmail 8.7.5 の検証では… chfn に関するバグは検出できなかった(ポインタを直接操作していたため)。 その他の、未公表のまま修正されていた問題は全て検出した。
出力の情報量不足 • ツールの報告は「どのバッファが溢れるか」のみ。「どこで溢れるか」は示されない (制約に基づくシステムの原理的な限界)。 ⇒ 「バッファ溢れに関係する変数名を同時に報告する」というヒューリスティクスを採用して、多少の改善を試みた。
結論 • Buffer overrun の静的解析技法として、2つのアイデアを提案した。 • 文字列のモデル化 • 制約に基づく解析 • このアプローチは十分に有効で実用的。 • 精度を犠牲にして、大きなプログラムに適用できるだけのscalabilityを達成できた。 • 人手では発見できない小さなバグも発見できた。 • 精度に関しては改善の余地がある。