全体ミーティング (3/11) 野尻隆宏 1 紹介する論文 • Sufficient Preconditions for Modular Assertion Checking – Yannick Moy – VMCAI 2008 2 扱いたい問題 • ソースコードの安全性の検証を行いたい null ポインタの参照 配列境界外のアクセス 整数オーバーフロー • Assertion Checking ができればよい ptr != NULL 0 <= index < length(array) min_integer <= i + j <= max_integer 3 Assertion Checking を行うために • プログラムの一部分(関数など)だけをみて、 assertion が満たされることを証明したい • しかし、どのような context で、その部分が 実行されるか分からない • 通常は、証明するのに十分な事前条件を ユーザが与える必要がある • それらを自動的に計算して与えたい 4 Running Example {requires n <= 0 || n <= s} void foo(int s, int n) { int i = 0; while (1) { if (i < n) { assert(0 <= i && i < s); } i = i + 1; } } 5 これまでの手法 1 • Induction-Iteration – Suzuki and Ishihara, POPL ’77 • 最弱事前条件を用いる • ループに関する事前条件では、ループ変数を 全称量化する • Quantifier Elimination の手続きを使って、 量化子を消す 6 Induction-Iteration: Backwards from Assertion void foo(int s, int n) { int i = 0; while (1) { {W(0) ≡ i < n ==> (0 <= i && i < s)} if (i < n) { {0 <= i && i < s} assert(0 <= i && i < s); } i = i + 1; } } 7 Induction-Iteration: Backwards from Assertion • W(0) が while ループについて不変であれば、 assertion の正当性をいえる • そこで、次に W(0) が不変であるかどうかを 確かめる – W(0) を仮定した下で、ループを一回実行したら、 実行後にも W(0) が成り立っているか? 8 Induction-Iteration: Loop Invariant Generation void foo(int s, int n) { int i = 0; while (1) { {W(1) ≡ i+1 < n ==> (0 <= i+1 && i+1 < s)} if (i < n) { assert(0 <= i && i < s); } {i+1 < n ==> (0 <= i+1 && i+1 < s)} i = i + 1; {W(0)} } 9 } Induction-Iteration: Loop Invariant Generation • W(0) ==> W(1) が成り立つか? • 成り立つ場合、W(0)がループ不変式と分かる • 成り立たない場合、 – 式を一般化する: ∀i. W(0) ==> W(1) – 量化子を消す – これを新たな不変式の候補として、繰り返す 10 Induction-Iteration の問題点 • 今回の例の場合、 n <= s が得られる n<=0 || n<=s と比べて、強い条件 • ループ不変式の計算の際に、ループの外の 情報を使わないので、条件が強くなった – ループの外から得られる ”i >= 0” という情報を 考慮していなかったので、 n <= 0 が 得られなかった 11 これまでの手法 2 • Abstract Debugging – Bourdoncle, ESEC ’93 • 抽象解釈(Abstract Interpretation)を用いる – プログラムの各点で、各変数の取りうる値を 知りたいが、厳密な計算は難しい – 区間 [l, h] で表して近似的に計算する、など • 前進伝播、後進伝播の計算を行う 12 Abstract Debugging: Initial Forward Propagation void foo(int s, int n) { int i = 0; {i == 0} while (1) { if (i < n) { {0 <= i && i < n} assert(0 <= i && i < s); } i = i + 1; } } 13 Abstract Debugging: Backward Propagation void foo(int s, int n) { {n<=0 || (0<n && 0<s)} int i = 0; {(i==0 && n<=0) || (i==0 && 0<n && 0<s)} while (1) { {(0<=i && n<=i) || (0<=i && i<n && i<s)} if (i < n) { {0<=i && i<n && i<s} assert(0 <= i && i < s); } i = i + 1; } } 14 Abstract Debugging の問題点 • 事前条件として n <= 0 || (0 < n && 0 < s) が得られる – 必要だが、十分な条件ではない • 抽象解釈での近似による限界 – 区間による近似では、分岐の合流を扱いにくい – x != y などの表現は扱いにくい 15 提案する手法 • 抽象解釈と最弱事前条件を組み合わせる – ループの外からの情報も使いながら、 最弱事前条件を計算する 16 1. 前方抽象解釈を行う • 各点で変数の取りうる範囲を、 近似的に求める(各点での不変式になる) void foo(int s, int n) { int i = 0; while (1) { if (i < n) { {0<=i && i<n} assert(0 <= i && i < s); } i = i + 1; } } 17 2. assertion を不変式で弱める void foo(int s, int n) { int i = 0; while (1) { if (i < n) { {(0<=i && i<n) ==> i<s } assert(0 <= i && i < s); } i = i + 1; } } 18 3. 最弱事前条件を計算する void foo(int s, int n) { int i = 0; while (1) { {(0<=i && i<n) ==> i<s} if (i < n) { {(0<=i && i<n) ==> i<s} assert(0 <= i && i < s); } i = i + 1; } } 19 4. ループ変数を全称量化する void foo(int s, int n) { int i = 0; {∀i. (0<=i && i<n) ==> i<s} while (1) { {(0<=i && i<n) ==> i<s} if (i < n) { assert(0 <= i && i < s); } i = i + 1; } } 20 5. 量化された変数を削除する {requires n<=0 || n<=s} void foo(int s, int n) { {n<=0 || n<=s} int i = 0; {n<=0 || n<=s} while (1) { if (i < n) { assert(0 <= i && i < s); } i = i + 1; } } 21 得られた事前条件 • 十分条件 n <= 0 || n <= s が得られた • Induction-Iteration より優れている点 – ループの外からの情報を使える • Abstract Debugging より優れている点 – 最弱事前条件の計算により、 十分条件が得られる 22 ポインタが存在する場合 • エイリアシングを考慮する必要がある void bar(int *p, int *n) { int i = 0; while (1) { if (i < *n) { n と p+i が同じ *(p + i) = 0; 場所を指している } かもしれない i = i + 1; } } 23 エイリアシングへの対処 • 実際に人がプログラムを記述する際には、 エイリアスしていないことを前提としている 部分も多い • そこで、このような前提を仮定として用いる • 前の例では、n と p+i はエイリアスしていない、 と仮定してしまう 24 エイリアシングへの対処 • 具体的には、プログラムは次の規則に 従うものとして扱う ポインタ変数 x を通したメモリへの書き込みと 読み込みの間に、別のメモリへの書き込みが ある場合、その書き込みは x とは別の場所を 指している 25 エイリアシングへの対処 *x = 0; … *y = 10; … t = *x; x と y が同じ場所を 指していた場合、 初めの書き込みを無効化 してしまう そこで、 x と y は 異なる場所を指していると 考える • このような規則を満たすものは、前と同様の 手法を用いて事前条件を計算できることが 示せる 26 扱いやすくするために • 実際には、この規則を満たしているかどうか 確かめるのは難しいので、さらに強めた 次の条件を用いる • 書き込みが行われる領域は互いに重ならない • 読み込みだけが行われる領域と、 書き込みが行われる領域は互いに重ならない separated(p, q) と書くことにする 27 扱いやすくするために • 前の例 (関数 bar) では separated(p, n) p n • これを仮定して関数 bar を解析する • separated(p, n) を仮定して事前条件の計算を したので、 bar の呼出時には、この制約も 確かめる必要がある 28 実験結果 • Minix 3 Operating System 上で実装された C の標準 string 関数に対して実行 • 20 の関数について、メモリ安全性を示すのに 十分な事前条件を推論できた • strcat での例: separated(dest[…],src[…]) && offset_min(dest) <= 0 <= strlen(dest) <= offset_max(dest) && offset_min(src) <= 0 <= strlen(src) <= offset_max(src) && strlen(dest) + strlen(src) <= offset_max(dest) 29 実験結果 • Verisec buffer overflow benchmark に適用 – 実際のアプリケーション (sendmail, wu-ftpd など) に存在した脆弱性からとられたベンチマーク – 多くのものに対して十分な事前条件を推論できた • バイナリサーチにおいて、整数オーバー フローを起こさないための十分な事前条件を 推論できた 30 関連研究 (1/2) • 配列境界検査・ループ不変式推論 – Abstract Interpretation [Cousot and Halbwachs, POPL ’78] – Induction-Iteration [Suzuki and Ishihara, POPL ’77] • Modular Program Verification and Precondition Inference – Abstract Debugging [Bourdoncle, ESEC ’93] 31 関連研究 (2/2) • Memory Separation and Precondition Inference – Pointer Separation [Aiken et al., PLDI ’03] [Koes et al., MSP ’04] など • 抽象解釈と演繹的証明の組み合わせ – 抽象解釈と定理証明器を組み合わせて ループ不変式を作る [Leino and Logozzo, APLAS ’05] 32 まとめ • assertion が成り立つことを証明するために 十分な事前条件を推論する方法を提案した • 従来の方法を組み合わせて構成した – 抽象解釈 – 最弱事前条件 – 量化子の削除 • あるルールに従う、ポインタを含むプログラム に適用できることを示した 33
© Copyright 2024 ExpyDoc