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