以下、図面を用いて本発明の実施の形態を説明する。まず、第1の実施形態を説明し、その後、第2の実施形態を説明する。
図1は、本発明の第1の実施形態であるソースコード脆弱性検査装置の構成図である。
101はソースコード脆弱性検査装置の本体(コンピュータ)で、CPU102、記憶装置103、及びメモリ104を備える。CPU102は、ソースコード検査を行うプログラムの実行や各種制御を行う。記憶装置103には、ソースコード検査を行うプログラム、検査対象のソースコード、及び、後述する仮引数間遷移DB204や脆弱性DB205などが保存される。メモリ104は、CPU102で実行されるプログラムなどの扱う各種データを一時的に記憶する。
キーボード105はソースコード脆弱性検査装置に指示を送るための入力装置、CRT106はソースコード脆弱性検査装置の表示装置である。外部記憶装置107は、ソースコード脆弱性検査装置に検査対象のソースコードを入力したり、検査結果を出力するための記憶装置である。バス108はこれら各部102〜107を相互に接続するバスであり、CPU102の指示で移動されるデータの通り道である。
図2は、ソースコード脆弱性検査装置の機能の概要図である。ソースコード脆弱性検査装置は、構文解析手段201、及び脆弱性検出手段202を備える。これらの手段は、CPU102がソースコード検査を行うプログラムを実行することにより実現されるものである。
構文解析手段201は、構文解析ルール203に従って検査対象ソースコード208を構文解析して解析木210を構築する。
脆弱性検出手段202は、解析木210をたどり、検査対象ソースコード208内で定義された関数(ユーザ定義関数)を列挙し、動的仮引数間遷移DB206を作成する。更に検査対象ソースコード208内で定義された変数を変数テーブル207に登録する。そして、検査対象ソースコード208の使用するライブラリで提供される関数(ライブラリ関数)について予め作成された仮引数間遷移DB204と、解析木210をたどりながら作成・更新する、動的仮引数間遷移DB206に従い、変数間のデータ遷移を追跡し、変数の保持する値の種類を変数テーブル207に逐次記録する。ここで、ファイルI/Oや通信などで外部から入力されたデータを参照する変数が、脆弱性DB205に予め登録された危険なライブラリ関数(「危険な関数」とされるライブラリ関数)の実引数として与えられる場合、検査対象ソースコード内の該当する箇所を脆弱な箇所として脆弱性検査結果209に出力する。
図3は、ソースコード脆弱性検査装置の構文解析手段201及び脆弱性検出手段202の詳細な処理手順である。手順301は構文解析手段201により実施され、それ以降の手順は脆弱性検出手段202により実施される。以下、各手順の詳細、及び図2の203〜207について説明する。
手順301において、構文解析手段201は、検査対象ソースコード208と構文解析ルール203を読込み、解析木210を構築する。
ここでは、図4に示すC言語で記述した検査対象ソースコード208を例にしてソースコード脆弱性検査装置の処理手順を説明する。図4の左端の行番号は、説明の便宜上、記したものである。なお、本処理手順は他のプログラミング言語で記述されたソースコードにも適用可能である。
構文解析ルール203は、検査対象ソースコード208の記述に用いられたプログラミング言語の文法を解釈するために予め定義されたルールである。本実施形態はルールの記述方法や内容、構文解析の手順を特定しないが、構文解析では少なくともソースコード内で使用されているグローバル変数、ローカル変数、仮引数、ユーザ定義関数、及びライブラリ関数を識別し、またソースコードを処理の最小単位である式に分解して解析木210を構築可能であることを前提とする。
図5は、図4のソースコードから構築された解析木210の例である。説明の便宜上、左端に行番号を記している。図中の囲み文字は、ソースコードの解析単位であるノードを表す。解析木210の各末端ノード(子ノードを持たないノード)のカッコ内の文字は、その末端ノードに対応するソースコードの断片である。図5では解析されたソースコードの主要なノードのみ記しており、括弧やセミコロンなどの区切り文字、演算子などは省略されている。
本実施形態では解析木210の構造を特に指定しないが、解析木210は、解析されたソースコードの全ノード(関数定義、式、変数など)の親子関係を、ルートノードを頂点とする木構造に表したものとする。また、所定の順序(図5の例ではルートノードを始点に上から下、左から右に向かって順に)に従うと末端ノードのソースコードの断片をソースコード内での出現順にたどれるものとする。
なお本実施形態の説明では、リスト構造のデータを'['、']'、集合を'('、')'で囲み、それぞれの各要素を','で区切って表記する。
また、検査対象ソースコード208をコンパイルして実行した際に検査対象ソースコード208内の各変数や仮引数が保持する値を、本実施形態の説明では抽象化して表記する。基本的な表記方法は、変数の値については変数テーブル207に登録された変数に割り当てられる識別番号に抽象化して表記するものとし、仮引数の値についてはライブラリ関数やユーザ定義関数の各仮引数を左から順に1、2、3と割り当てた仮引数番号に抽象化して表記するものとし、さらにその変数や仮引数で可能な間接参照の深さに応じた数の'*'を前置するものとする。例えば、識別番号(仮引数番号)として3を割り当てた変数(仮引数)の可能な間接参照の深さが0(ポインタではない)ならば、その値を'3'と表記する。可能な間接参照の深さが1(ポインタや1次元配列数)ならば、ポインタ変数(仮引数)自身の値を'3'、その間接参照先の値を'*3'と表記する。可能な間接参照の深さが2(ポインタのポインタや2次元配列)ならば、ポインタのポインタである変数(仮引数)自身の値を'3'、その間接参照先の値を'*3'、さらにその間接参照先の値を'**3'と表記する。ライブラリ関数やユーザ定義関数の戻り値を表す仮引数番号として0を用いる。また、可変長仮引数を表記する場合は可変長仮引数の仮引数番号に'...'を後置する。変数や仮引数がファイルI/Oや通信など外部入力を取得するライブラリ関数の呼出しで直接取得した外部入力データを保持する場合は'IN'と表記する。変数や仮引数の保持する値が外部へ出力される場合を示す場合は'OUT'と表記する。以上で説明した、変数や仮引数の保持する値を抽象的に表記したものを、本実施形態では参照データと呼ぶ。
手順301の後、手順302において、脆弱性検出手段202は、解析木210及び仮引数間遷移DB204を読込む。
図6は、仮引数間遷移DB204の例である。仮引数間遷移DB204は、内部での処理により仮引数の間でその保持する値のコピーや移動等のデータ遷移を伴うライブラリ関数を予め列挙したDBであり、検査対象ソースコード208のプログラミング言語や使用するライブラリ、コンパイラの処理系によって内容は異なる。ここではANSI C標準ライブラリの関数の例を示している。
図6の仮引数間遷移DB204の1列目は関数名である。2列目はどの仮引数の値がどの仮引数に遷移するか示すデータ遷移のリストである。このリストを仮引数間遷移リスト、リスト内の各要素を仮引数間遷移エレメントと、呼ぶ。各仮引数間遷移エレメントは、矢印'→'の左の参照データ(遷移元参照データ)で表記される仮引数の保持する値の全部または一部が、'→'の右の参照データ(遷移先参照データ)で表記される仮引数に複製されることを意味する。なお、仮引数間遷移リスト内の各エレメントは左から順に評価されるものとする。値渡しの仮引数は関数呼出しの前後でその値が必ず不変なので、このことを表す仮引数間遷移エレメント(例えば'1→1')は仮引数間遷移リストに加えない。参照渡しの仮引数についてはその仮引数で可能な間接参照先が上書きされて残らない場合を除き、仮引数間遷移エレメント(例えば可能な間接参照の深さが2の仮引数なら'*1→*1'と'**1→**1')を必ず仮引数間遷移リストに加えるものとする。
図6の例によると、関数getsは外部入力データが第1仮引数の間接参照先に遷移し、さらに第1仮引数のポインタが戻り値として返される。関数sprintfは第2仮引数と第3仮引数以降の可変長仮引数の間接参照先が不変で、これら間接参照先の値が第1仮引数の間接参照先に遷移する。関数putsは第1仮引数の間接参照先が不変で、かつ外部に出力される。
なお、仮引数間遷移DB204や動的仮引数間遷移DB206への登録対象は、特定のデータ型の仮引数を扱う関数に限定しても、データ型に関係なく全ての関数でもよい。本実施形態では、文字列の格納に使われるchar*型、const char*型を扱うものに限定している。
手順303において、脆弱性検出手段202は、解析木210をルートノードから所定の順序で全てたどり、見つかったユーザ定義関数を動的仮引数間遷移DB206に、グローバル変数を変数テーブル207に、それぞれ登録する。ここでの登録内容は暫定的なもので、手順305〜312において更新される。
図7は、図5の解析木210をたどって作成された動的仮引数間遷移DB206の例である。動的仮引数間遷移DB206の1列目と2列目は仮引数間遷移DB204と同じである。3列目は外部入力データを受け取ることのある仮引数の集合で、集合の各要素である外部入力仮引数を参照データとして表記する。手順303の時点では仮引数間のデータ遷移は不明なので、2列目の仮引数間遷移リストの初期値には参照渡しの仮引数だけについて参照先が不変であることを示す仮引数間遷移リストを与える。各仮引数が外部入力データを受け取るかどうかもこの時点では不明なため、3列目の外部入力仮引数集合の初期値を空集合とする。ただし、関数mainだけは第2引数(char型のポインタ配列)がプログラム実行パラメータを受け取るとC言語の仕様で定められているため、そのことを表す外部入力仮引数集合'(**2)'を初期値とする。
図5の解析木210にはグローバル変数が含まれていないので、ここでは変数テーブル207への登録はない。登録する場合の初期値は、手順308で説明するローカル変数のそれと同じである。
手順304において、脆弱性検出手段202は、初めて手順304を実施する場合、あるいは手順305〜312での解析木全探索を終了して手順304に戻ったときにその全探索の前後で動的仮引数間遷移DB206の内容が更新されている場合、次に手順305を実施する。それ以外の場合、手順313に進む。手順305において、脆弱性検出手段202は解析木210の探索をルートノードから始める。手順306において、脆弱性検出手段202は、解析木210の探索が終了していれば次に手順304を実施し、終了していなければ手順307に進む。
手順307において、脆弱性検出手段202は、これまでに探索済みのノードの次から解析木210を所定の順序でたどり、最初に到達した関数定義ノードを頂点とする部分解析木を手順308〜312の処理の対象とする。この部分解析木で表されるユーザ定義関数を以下では対象関数と呼ぶ。
図5の解析木210ではまず関数format_stringが対象関数となる。以降の説明では特に断らない限り、変数とは対象関数の仮引数とローカル変数、及びグローバル変数を意味する。
手順308において、脆弱性検出手段202は、対象関数の部分解析木を所定の順序でたどり、対象関数の仮引数や関数本体で定義されたローカル変数が見つかれば、それらを変数テーブル207に登録する。
図8は、図5に示す部分解析木の関数format_stringの仮引数(図5の5〜11行目)について手順308を実施した後の変数テーブル207の内容である。変数(仮引数)毎に1行のデータが変数テーブル207に追加される。1列目は変数テーブル207に登録された変数の識別番号で、登録順に1から付けられる。2列目は変数の種別を表し、仮引数を登録する場合は'A'、ローカル変数なら'L'、グローバル変数なら'G'を格納する。3列目は変数の型、4列目は変数名、5列目は変数の保持する値を表す参照データ(変数参照データ)の集合(変数参照データ集合)から構成されるリスト(変数参照データ集合リスト)である。
図8の変数テーブル207の変数参照データ集合リストの要素(変数参照データ集合)の数は各変数で可能な間接参照の深さで決まる。変数が基本データ型(int型やchar型などポインタや配列でない単純なデータ型)なら要素数は1、ポインタまたは1次元配列なら2、ポインタのポインタまたは2次元配列なら3である。また、リスト内の各変数参照データ集合の位置はその変数の間接参照の繰り返し数に対応し、リストの先頭の変数参照データ集合はこの変数の値を表す変数参照データの集合、2つめの変数参照データ集合は変数の間接参照先の値を表す変数参照データの集合、3つめの変数参照データ集合は変数の間接参照先の間接参照先の値を表す変数参照データの集合である。変数の値やその間接参照先の値を変数参照データの集合とする理由は、例えば変数が複数の変数からコピーされた文字列を連結した文字列を保持する状態に対応するためである。なお、変数テーブル207における各変数参照データ内の数字は変数の識別番号である。
図8の変数テーブル207の変数参照データ集合リストの初期値は変数の種別により異なる。ローカル変数では、その初期化が行われていない場合、及び定数値による初期化の場合は、次のとおりである。まずローカル変数で可能な間接参照の深さごとに、そのローカル変数の変数テーブル207での識別番号に間接参照の深さに応じた'*'を前置した変数参照データを生成する。そして、各変数参照データを唯一つの要素とする変数参照データ集合を要素とするリストを初期値とする。なお、定数値で初期化される変数の場合、一番深い間接参照に対応する変数参照データ集合は、定数値を表す参照データ'C'を用いて'(C)'とする。例えば、ポインタのポインタであるローカル変数において、その可能な間接参照の深さは0、1、2なので、初期化されていない場合の変数参照データ集合リストの初期値は'[ (1), (*1), (**1) ]'、定数値で初期化されている場合の初期値は'[ (1), (*1), (C) ]'となる。ローカル変数の初期化に他の変数や関数呼び出しが含まれる場合、前記初期値に、初期化データを生成するための式に手順309の処理を加えたものとする。
グローバル変数の場合は、ローカル変数の場合と同じである。
仮引数の場合は、初期化されないローカル変数と同様の変数参照データ集合リストを初期値とする。ただし、外部入力データを渡される可能性のある仮引数については、該当する間接参照の深さの変数参照データ集合に擬似外部入力データ'PIN'を初期値に追加する。ここでの外部入力データの有無の判定は次のようにして行う。まず、変数テーブル207に登録しようとする仮引数の仮引数番号を取得する。この時点では解析木210の仮引数リストノードを頂点とする部分解析木をたどっている途中なので、登録しようとする仮引数が何番目の引数であるか、つまり仮引数番号が分かる。次に対象関数の名前をキーとして動的仮引数間遷移DB206を検索し、一致する行の外部入力仮引数集合を読み出す。この集合から前記仮引数番号を含む外部入力仮引数を全て取得する。そして、各外部入力仮引数について、間接参照の深さ(仮引数番号に前置された'*'の数)を求め、変数参照データ集合リスト内で左から前記深さに1を加えた位置の変数参照データ集合に'PIN'を追加する。例えば、対象関数の第2仮引数を変数テーブル207に登録する際、この第2引数の変数テーブル207内での変数識別番号が4、対象関数の名前をキーとして動的仮引数間遷移DB206を検索して読み出した外部入力仮引数集合が'(*1, **2)'だったとする。この集合内で、現在、処理対象の引数番号'2'を含む外部入力仮引数は'**2'であり、その間接参照の深さは2である。したがって、変数参照データ集合リストの初期値は、外部入力仮引数が無い時の初期値'[ (4), (*4), (**4) ]'の3番目の集合に'PIN'を加えた'[ (4), (*4), (**4, PIN) ]'となる。なお、関数mainの第2仮引数には必ず外部入力データが与えられるので、この第2仮引数について、変数テーブル207の変数参照データ集合リストの初期値は、仮にその変数識別番号が3とすると、'[ (3), (*3), (IN) ]'とする。
以上で説明した手順308を対象関数format_stringについて実施すると、図5の解析木からまずchar型へのポインタである仮引数stroutが見つかる(6〜8行目)。この時点での動的仮引数間遷移DB206(図7)の関数名の列内を対象関数の名前format_stringで検索すると、一致した行の外部入力仮引数集合は空である。仮引数stroutは変数テーブル207に登録する最初のデータなので識別番号は1となり、変数参照データ集合リストの初期値は'[ (1), (*1) ]'となる(図8の番号1の行)。続いて図5の解析木から仮引数strinが見つかって(9〜11行目)変数テーブル207での識別番号が2となり、この仮引数に該当する外部入力仮引数は存在しないので、変数参照データ集合リストの初期値は'[ (2), (*2) ]'となる(図8の番号2の行)。
図4のソースコードの例には含まれていないが、戻り値を返す対象関数の場合、その戻り値の参照データを記録するための行も変数テーブル207に追加する。その種別はAで仮引数扱い、変数型は戻り値の型、名前は'return'(C言語では予約語のため変数名に使えず、ユーザ定義変数と重複しない)、変数参照データ集合リストは戻り値の間接参照の深さに応じた空集合を並べたリスト(例えば深さが2なら、[ ( ), ( ), ( ) ])として登録する。
なお、手順308においては、変数テーブル207に登録した仮引数の仮引数番号と、その仮引数を変数テーブル207に登録した際に割り当てられた変数識別番号の組も、仮引数ごとに記憶しておく。
手順309において、脆弱性検出手段202は、探索済みのノードの次から解析木210を所定の順序でたどり、変数の値が別の変数に移る可能性のあるノードに到達すると、そのノードを頂点とする部分解析木について、仮引数間遷移DB204及び動的仮引数間遷移DB206を参照してデータ遷移を追跡し、変数テーブル207を更新する。
変数の値が別の変数に遷移する可能性のあるノードには関数呼出しや代入文などがある。関数呼出しのノードにたどり着いたら、そのノードを頂点とする部分解析木を処理対象とする。例として図4に示すソースコードの関数format_stringについて手順309を実施すると、図5の解析木210の探索を再開後に最初に到達する、変数間での値の遷移が起きる可能性のあるノードは、関数呼出しノードである(図5の15行目)。このノードを頂点とする部分解析木をここでの処理対象とする(同15〜26行目)。
以下、図9を用いて、関数呼出しの部分解析木を解析するときの変数テーブル更新処理(手順309)の詳細な手順を説明する。
手順901において、脆弱性検出手段202は、部分解析木内をたどって呼び出される関数の名前を取得する。図5の解析木210の例において、関数呼出しノードの次に到達する識別子ノード(同16行目)のソースコード断片から、呼び出される関数の名前sprintfを得る。
手順902において、脆弱性検出手段202は、部分解析木をたどり、この関数呼出しの全実引数を取得して各実引数が変数であるか定数であるか調べる。図5の解析木210の例では、各実引数ノードから、第1実引数strout、第2実引数"input=%s"、第3実引数strinを得る。式ノードを直接の親ノードとする識別子ノードは変数を意味するので、第1実引数と第3実引数は変数であることが分かる。第2実引数に当たるノードは、文字列ノードであるので、定数値であることが分かる。
手順903において、脆弱性検出手段202は、先に取得した関数名をキーとして仮引数間遷移DB204及び動的仮引数間遷移DB206の関数名欄を検索し、一致する行から仮引数間遷移リストを得る。なお、各DBを検索して一致行が見つからない場合(処理対象の関数呼出しに使われた関数が仮引数間遷移DB204に登録されてない場合)は図9の手順を終える。上記の例では、関数名sprintfをキーとして両DBを検索すると、図6の仮引数間遷移DB204の3行目に一致する行が見つかる。そこで、この行から仮引数間遷移リスト'[*2→*1, *2→*2, *3...→*1, *3...→*3...]'を取得する。
手順904において、脆弱性検出手段202は、先に得た仮引数間遷移リストの左から順に仮引数間遷移エレメントを取り出して手順905から手順909の処理を行い、変数間のデータ遷移を評価する。全ての仮引数間遷移エレメントについて処理を終えたら、次に手順910を行う。
手順905において、脆弱性検出手段202は、評価対象の仮引数間遷移エレメントから遷移元参照データと遷移先参照データを取り出す。両者に仮引数番号が含まれている場合、及び遷移元参照データが'IN'で遷移先参照データが仮引数番号を含む場合、遷移先参照データに含まれる仮引数番号に該当する実引数が変数であれば、次の処理に移る。これら以外の場合、手順904に戻り、次の仮引数間遷移エレメントを評価対象とする。図5の例では、まず1つめの仮引数間遷移エレメント'*2→*1'について、遷移元、遷移先ともに仮引数番号が含まれるので手順906に移る。なお、3つめ、4つめの仮引数間遷移エレメントの場合も手順906に進めるが、2つめの仮引数間遷移エレメント'*2→*2'は遷移先参照データに含まれる仮引数番号に対応する実引数が定数であるので手順904に戻る。
手順906において、脆弱性検出手段202は、評価対象の仮引数間遷移エレメントについて、仮引数番号を含む遷移元参照データ及び遷移先参照データの間接参照の深さを調べる。これは仮引数番号に付加されたアスタリスク'*'を数えて求める。上記の例では、遷移元参照データ'*2'、遷移先参照データ'*1'から間接参照の深さは共に1である。
手順907において、脆弱性検出手段202は、遷移元参照データに仮引数番号が含まれていれば、部分解析木内の該当する実引数の参照データ集合を取得する。仮引数番号に対応する実引数が変数であれば、その変数の名前をキーとして変数テーブル207の名前欄を検索し、一致する行の変数参照データ集合リストを得る。このリスト内から、先に求めた遷移元の間接参照の深さに対応する変数参照データ集合(以下、遷移元変数参照データ集合と呼ぶ)を取り出す。仮引数番号に対応する実引数が定数であれば'(C)'を遷移元変数参照データ集合とする。遷移元参照データに可変長仮引数を表す'...'が含まれているため実引数に該当する変数や定数が複数ある場合、それら全てについて上記処理を行い、その結果の和集合(ただし要素の重複を許さない)を遷移元変数参照データ集合とする。遷移元参照データが'IN'であれば、'(IN)'を遷移元変数参照データ集合とする。上記の例では、遷移元参照データ'*2'から遷移元が第2引数と分かり、これは定数値であるので'(C)'を遷移元変数参照データ集合とする。
手順908において、脆弱性検出手段202は、呼び出される関数がユーザ定義関数であり、かつ遷移元変数参照データ集合に'IN'または'PIN'が含まれている場合、呼び出される関数に外部入力データが渡されることを記録するため、この関数について動的仮引数間遷移DB206の外部入力仮引数集合を更新する。先に取得した呼び出される関数の関数名をキーとして動的仮引数間遷移DB206を検索し、一致する行の外部入力仮引数集合に遷移元参照データが含まれていなければ、この遷移元参照データそのものを外部入力仮引数集合に追加する。上記の例では該当する場合がないので具体例の説明は省略する。
手順909において、脆弱性検出手段202は、遷移先参照データに含まれる仮引数番号に対応する実引数の変数の名前をキーとして変数テーブル207の名前欄を検索して一致する行を探し、その変数識別番号を得る。そして、この変数識別番号と手順906で得た遷移先参照データの間接参照の組に、手順907で得た遷移元変数参照データ集合をさらに組にして、暫定遷移先変数参照データ集合として一時的に記憶する。暫定遷移先変数参照データ集合は遷移先(変数識別番号と間接参照の深さの組)毎に作成する。なお、同一の遷移先(変数識別番号と間接参照の深さの組)に複数のデータ遷移が存在する場合には、新たな遷移元変数参照データ集合が得られる度に暫定遷移先変数参照データ集合内の遷移元変数参照データ集合に連結する。上記の例では、遷移先実引数の変数の名前stroutをキーとして図8の変数テーブル207の名前欄を検索し、一致した行の番号欄からその変数識別番号1を得る。また先に得た遷移先の間接参照の深さは1である。これらの組に先に得た遷移元変数参照データ集合'(C)'をさらに組にして一時的に記憶する。この暫定遷移先変数参照データ集合を、便宜的に、遷移先変数の変数識別番号、その関節参照の深さ、遷移元変数参照データ集合の順に'(1, 1, (C))'と記述する。上述の手順909の後、手順904に戻る。
ここまでの処理で、上記の例では、3つめの仮引数間遷移エレメントを処理する際に1つめと同じ変数識別番号と間接参照の深さの組からなる暫定遷移先変数参照データ集合を得るので、これらをマージした暫定遷移先変数参照データ集合は'(1, 1, (C, *2))'を得る。4つめの仮引数間遷移エレメントの処理から得られる暫定遷移先変数参照データ集合は'(2, 1, (*2))'である。
手順910において、脆弱性検出手段202は、暫定遷移先変数参照データ集合のそれぞれについて以下の処理を行い、変数テーブル207を更新する。暫定遷移先変数参照データ集合から遷移先変数の変数識別番号を取得し、これをキーとして変数テーブル207の番号欄を検索して一致した行から変数参照データ集合リストを得る。このリストについて、暫定遷移先変数参照データ集合から得られる間接参照の深さに対応する位置に、同じく暫定遷移先変数参照データ集合から得られる遷移元変数参照データ集合を上書きする。この結果として得られる新たな変数参照データ集合リストを変数テーブル207の元の場所に上書きして、次の暫定遷移先変数参照データ集合の処理に移る。全ての暫定遷移先変数参照データ集合について上記処理を終えたら、手順309における変数テーブル207の更新処理を終了する。例えば、上記の例において図8の変数テーブル207を更新すると、上記1つめの暫定遷移先変数参照データ集合'(1, 1, (C, *2))'について、遷移先変数の変数識別番号1を得る。これをキーとして図8の変数テーブル207の番号欄を検索し、一致した行の変数参照データ集合リスト'[ (1), (*1) ]'を得る。また上記1つめの暫定遷移先変数参照データ集合から遷移先変数の間接参照の深さ1を得る。これに対応する上記変数参照データ集合リストの2番目の位置に、さらに上記1つめの一時記憶データから得た遷移元変数参照データ集合'(C, *2)'を上書きし、'[ (1), (C, *2) ]'を得る。この結果を図8の変数テーブル207の元の位置に上書きする。同様に、上記2つめの暫定遷移先変数参照データ集合について処理を行うと、図8の変数テーブル207の番号2の行の変数参照データ集合リストの2番目の要素を遷移元変数参照データ集合'(*2)'で上書きし、さらに図8の変数テーブル207の元の位置に上書きする。以上で変数テーブル207の更新処理を終了する。更新後の変数テーブル207を図10に示す。
以上で図9の説明を終える。
図9の処理手順は関数呼出しの場合であるが、代入文など演算子を介した変数の更新処理についても、その部分解析木が前記sprintf関数呼出しについての部分解析木(図5の15〜26行目)と同様に構築されるので、ほぼ同じ手順で処理できる。
例えばポインタの代入'p=q'の場合(変数p、qはchar*型とする)、左辺の仮引数番号を1、右辺の仮引数番号を2として、その仮引数間遷移リストは'[2→1, →*1, *2→*2]'である。代入後はポインタがコピーされ変数pの間接参照先は変数qと同じなので、前記リストではポインタ自身のコピー('2→1')と、変数pの間接参照先のクリア('→*1')を指示している。仮にchar*型のポインタ変数a、bについてポインタの代入'a=b'を処理する際、代入前の変数テーブル207が図11の状態であれば、代入後は図12のように更新され、変数aの間接参照先に当たる変数参照データ集合は空集合となる。なお、変数aがこの状態のときに変数aの間接参照先の参照データが関数呼出しや代入文の処理において必要とされた場合、空の変数参照データ集合より間接参照が一つ浅い(図12においては変数参照データ集合リスト内の左隣の)集合内の変数識別番号を取り出す。そして、この番号で識別される変数の変数参照データ集合リスト内から元々必要とされた間接参照の深さの変数参照データ集合を読み出して使用する。これも空集合であれば、空集合でないものが見つかるまで上記処理を繰り返す。
また、関数呼出しの実引数や演算子の演算対象として別の関数呼出しや演算子による演算の結果が使われる場合、その結果を一時的なローカル変数の値とみなして変数テーブル207に登録し、この結果が返される位置に該当する部分解析木内のノードを上記一時ローカル変数のノードとみなすことで、手順309の処理手順を適用して変数テーブル207を更新できる。
return文で変数の値が返される場合、変数テーブル207に変数名returnとして登録した行の変数参照データ集合リストに、前記変数の変数参照データ集合リストをマージする。この際、同一の間接参照の深さごとに両者の変数参照データ集合の和集合を取り、その結果を変数名returnの新たな変数参照データ集合リストとする。return文で定数値が返される場合は'(C)'をマージする。
再び図3に戻って、手順310において、脆弱性検出手段202は、対象関数の部分解析木の探索が終了していれば手順311に、終了していなければ手順308に戻る。
手順311において、脆弱性検出手段202は、変数テーブル207の内容に基づき、対象関数について動的仮引数間遷移DB206の内容を更新する。対象関数の仮引数それぞれについて、その仮引数の変数参照データ集合リストを変数テーブル207から取得して仮引数間遷移エレメントを生成し、最後にそれらエレメントを連結して仮引数間遷移リストとする。
手順311での処理の詳細を説明する。まず、変数テーブル207の種別欄で種別が'A'(仮引数)となっている行を選び、その変数識別番号をすべて取得する。本実施形態では対象としているC言語では入れ子の関数定義が許されていないので、変数テーブル207に仮引数として登録されているものは対象関数の仮引数のみである。
取得した変数識別番号のそれぞれについて以下の処理を行う。まず、変数テーブル207から処理対象の変数識別番号の行の変数参照データ集合リストを取り出す。そして、リスト内の変数参照データ毎に仮引数間遷移エレメントを生成する。その遷移元参照データには、手順308の処理で記憶した仮引数番号と変数識別番号の組を参照して、処理対象の変数参照データに含まれる変数識別番号を仮引数番号に変換した参照データを用いる。遷移先参照データには、同様に仮引数番号と変数識別番号の組を参照して処理対象の変数識別番号を仮引数番号に変換したものに、処理対象の変数参照データが含まれる変数参照データ集合の表す間接参照の深さを組み合わせた、参照データを用いる。なお、変数参照データ集合が空集合の場合、仮引数間遷移エレメントの遷移元参照データは無しとする。また、処理対象の変数参照データにローカル変数の変数識別番号や擬似外部入力データを表す'PIN'や定数値を表す'C'が含まれる場合は無視し、グローバル変数が含まれる場合はその変数識別番号に'G'をつけて仮引数間遷移エレメントに含める。
以上のように生成した仮引数間遷移エレメントを全て連結して対象関数の新たな仮引数間遷移リストを構成する。対象関数の名前をキーとして動的仮引数間遷移DB206を検索し、一致する行の仮引数間遷移リストを更新する。なお、遷移元参照データ及び遷移先参照データに含まれる仮引数番号が同一で、かつ両者の間接参照の深さが0の仮引数間遷移エレメントは仮引数間遷移リストに連結しない。
上述の手順311の処理を図10の変数テーブル207について行うと、種別欄が'A'である行は変数識別番号が1と2のものである。まず変数識別番号1の行について、変数参照データ集合リスト'[ (1), (C, *2) ]'を取り出す。最初の変数参照データ'1'に関して生成される仮引数間遷移エレメントは'1→1'、2つめの変数参照データ'*2'に関して生成される引数遷移エレメントは定数を表す'C'を無視して'*2→*1'となる。同様に、変数識別番号2の行について、生成される仮引数間遷移エレメントは順に'2→2'、'*2→*2'である。これらを連結して構成される新たな仮引数間遷移リストは、連結対象から除外する場合を考慮して、'[*2→*1, *2→*2]'となる。この結果、動的仮引数間遷移DB206は図13のように更新される。
手順312において、脆弱性検出手段202は、対象関数に係わる仮引数とローカル変数についての登録データを変数テーブル207から削除する。
なお、グローバル変数について変数テーブル207に登録された変数参照データ集合リストに、ポインタの代入処理などのためローカル変数や仮引数の変数識別番号を含む参照データが含まれている場合、その変数識別番号について変数テーブル207を参照し、その参照データの間接参照の深さに対応する変数参照データ集合と置き換える。
なお、本実施形態の説明では簡略化のため変数のスコープを考慮していないが、ローカル変数と仮引数の変数テーブル207への登録はそのスコープに入ったとき、削除はスコープの外に出たときに行う。解析木210を所定の順序でたどり、変数定義または変数宣言のノードがあればその変数のスコープが始まる。解析木210内でのこのノードの深さを変数テーブル207に記録して解析木210の探索を続け、探索中のノードの深さが変数テーブル207に登録したものより浅くなれば前記変数はスコープから外れたと分かるので、変数テーブル207から該当データを削除すればよい。
以上で、1つの対象関数についての処理が終わり、手順306に戻る。
図4のソースコードについて引き続き手順306〜312を実施すると、動的仮引数間遷移DB206及び変数テーブル207は次のように更新される。
図4のソースコードの関数get_inputについて手順308終了時の変数テーブル207は、図14である。
同関数について手順309終了時の変数テーブル207は図15に更新され、動的仮引数間遷移DB206は図13のままである。ここでは関数getsの呼出しについて、仮引数間遷移DB204を基に、図14の変数テーブル207に登録された変数inの変数参照データ集合リストが更新されている。
同関数について手順311終了時の動的仮引数間遷移DB206は図16に更新される。ここでは図15の変数テーブル207を基に、図13の動的仮引数間遷移DB206内の関数get_inputについての仮引数間遷移リストが更新されている。
図4のソースコードの関数put_inputについて手順308終了時の変数テーブル207は、図17である。
同関数について手順309終了時の変数テーブル207は図17のまま、動的仮引数間遷移DB206も図16のままである。
同関数について手順311終了時の動的仮引数間遷移DB206は、図16のままである。
図4のソースコードの関数mainの18行目について手順308終了時の変数テーブル207は、図18である。ここでは関数mainについて動的仮引数間遷移DB206に登録された外部入力仮引数集合を参照すると、第2仮引数に外部入力データが与えられている。したがって、第2仮引数argvについて変数テーブル207に登録される変数参照データ集合リストには、その参照の深さ2に対応する変数参照データ集合の初期値に'(IN)'が設定されている。
同関数の19行目について手順309終了時の変数テーブル207は図19に更新され、動的仮引数間遷移DB206は図16のままである。ここでは図18の変数テーブル207に登録されている変数inbufの変数参照データ集合リストが、ユーザ定義関数get_inputの呼出しについて図16の動的仮引数間遷移DB206に従い更新されている。
同関数の20行目について手順309終了時の変数テーブル207は図20に更新され、動的仮引数間遷移DB206は図21に更新される。ここでは、図19の変数テーブル207に登録されている変数outbufの変数参照データ集合リストが、ユーザ定義関数format_stringの呼出しについて図16の動的仮引数間遷移DB206に従い更新されている。また、同変数テーブル207に登録されている変数inbufの変数参照データ集合リストに'IN'が含まれているので、ユーザ定義関数format_stringについて図16の動的仮引数間遷移DB206に登録された外部入力仮引数集合が図21に更新されている。
同関数の21行目について手順309終了時の変数テーブル207は図20のままで、動的仮引数間遷移DB206は図22に更新される。図20の変数テーブル207に登録されている変数outbufの変数参照データ集合リストに'IN'が含まれているので、同関数について図21の動的仮引数間遷移DB206に登録された外部入力仮引数集合が図22に更新されている。
手順311は、同関数が仮引数間遷移DB204に登録されているため、実施しない。
以上で図5の解析木210の探索が終わるので手順304に戻る。現在の動的仮引数間遷移DB206(図22)は前回の手順304の実施時のそれ(図7)と異なるので、手順305〜312を再び実施する。
図4のソースコードの関数format_stringについて2回目の手順308終了時の変数テーブル207は、図23である。ここでは同関数について図22の動的仮引数間遷移DB206に登録された外部入力仮引数集合に基づき、変数テーブル207に登録される仮引数strinの変数参照データ集合リストの初期値に擬似外部入力データ'PIN'が含まれている。
同関数について2回目の手順309終了時の変数テーブル207は図24に更新され、動的仮引数間遷移DB206は図22のままである。ここでは関数sprintfの呼出しについて仮引数間遷移DB204に基づき、図23の変数テーブル207の変数stroutの変数参照データ集合リストが図24に更新されている。
同関数について2回目の手順311終了時の動的仮引数間遷移DB206は、図22のままである。1回目の同手順実施時とは異なり変数参照データ集合リストに'PIN'が含まれているが、仮引数間遷移リストの生成には利用されないので、図22の動的仮引数間遷移DB206は更新されない。
図4のソースコードの関数get_inputについて2回目の手順306〜312を実施時の変数テーブル207の変化の様子は1回目のそれと同じである(図14)。そのため、同関数について動的仮引数間遷移DB206に登録される仮引数間遷移リストも1回目のそれと変わらず、図22の動的仮引数間遷移DB206がそのまま維持される。
図4のソースコードの関数put_inputについて2回目の手順308終了時の変数テーブル207は、図25である。ここでは、同関数について図22の動的仮引数間遷移DB206に登録された外部入力仮引数集合に基づき、変数テーブル207に登録される仮引数outの変数参照データ集合リストの初期値に擬似外部入力データ'PIN'が含まれている。
同関数について2回目の手順309終了時の変数テーブル207は図25のまま、動的仮引数間遷移DB206も図22のままである。
同関数について2回目の手順311終了時の動的仮引数間遷移DB206は、図22のままである。
図4のソースコードの関数mainについて2回目の手順306〜312を実施時の変数テーブル207の変化の様子は1回目のそれと同じである(図18〜21)。そのため、同関数について動的仮引数間遷移DB206に登録される仮引数間遷移リストも1回目のそれと変わらず、図22の動的仮引数間遷移DB206がそのまま維持される。
以上で図5の解析木210の2回目の探索が終わるので手順304に戻る。現在の動的仮引数間遷移DB206(図22)は前回の手順304の実施以降で更新されていないので、手順313に移る。
手順313において、脆弱性検出手段202は、脆弱性DB205を読込む。
脆弱性DB205の例を、図26に示す。1列目は脆弱性を生じる可能性のある関数の名前である。2列目は外部入力データが与えられると脆弱性を生じる可能性のある仮引数を参照データ形式でリスト化した脆弱仮引数リストで、数字は仮引数番号である。
手順314において、脆弱性検出手段202は、解析木210をルートノードから所定の順序でたどり、脆弱性DB205の脆弱性仮引数リストに登録された仮引数に'IN'または'PIN'を含む変数が与えられる箇所を脆弱性検査結果209として出力する。
手順314では、これまでに構築された動的仮引数間遷移DB206と変数テーブル207に基づき、手順305〜312を実施する。ただし、動的仮引数間遷移DB206の更新に関する手順(手順311、及び手順309の処理を詳細化した図9の手順908)は省略する。また、手順910の処理の直前において、脆弱性DB205を参照して脆弱性検出ルールに合致する場合は脆弱性の恐れを指摘する、脆弱性検知手順を実施する。
前記脆弱性検知手順の処理は次のようなものである。まず、手順901で取得した、呼び出される関数の名前をキーとして図26の脆弱性DB205の関数名欄を検索する。一致する関数が見つからなければ脆弱性検知手順を終了する。見つかれば、その行の脆弱性仮引数リストを読込む。次に手順905〜907と同様の処理を行い、脆弱性仮引数リスト内の各仮引数参照データに対応する実引数について、その参照データを変数テーブル207から読込む。この参照データに外部入力データを表す'IN'、または擬似外部入力データを表す'PIN'が含まれていれば、脆弱性を生じる恐れがあるとして脆弱性検査結果209として出力する。
図4のソースコードについて前記脆弱性検知手順を実施する場合を説明する。
図4のソースコードの3行目の関数sprintfの関数呼出しについて前記脆弱性検知手順を実施する直前の変数テーブル207の状態は図23、動的仮引数間遷移リスト206の状態は図22である。まず、関数名sprintfで図26の脆弱性DB205の関数名欄を検索し、その一致する行の脆弱仮引数リスト'[*2, *3...]'を読込む。同リスト内の仮引数参照データ'*2'に対応する実引数は"input=%s"なので、この参照データは存在しない。同リスト内の次の仮引数参照データ'*3...'に対応する実引数は変数strinで、前記仮引数参照データの間接参照の深さが1であることから、前記変数strinの変数参照データ集合を図23の変数テーブル207から取得すると'(*2, PIN)'である。この変数参照データ集合には'PIN'が含まれているので、前記関数sprintfの関数呼出しは脆弱性を生じる可能性があり、この旨を脆弱性検査結果209として出力する。
図4のソースコードの8行目の関数getsの関数呼び出しについて同様に前記脆弱性検知手順を実施する直前の変数テーブル207の状態は図14、動的仮引数間遷移リスト206の状態は図22である。関数getsについて脆弱性DB205に登録されている脆弱仮引数リストを読込むと'[ ]'を得る。同リストが空の場合、前記関数呼出しがいつでも脆弱であることを意味する。したがって、前記関数getの関数呼出しは脆弱性を生じる可能性があり、この旨を脆弱性検査結果209として出力する。
図4のソースコードの13行目の関数putsの関数呼び出しについて同様に前記脆弱性検知手順を実施する直前の変数テーブル207の状態は図25、動的仮引数間遷移リスト206の状態は図22である。関数putsについて脆弱性DB205に登録されている脆弱性仮引数リストを読込むと'[*1]'を得る。この参照データに対応する実引数は変数outで、前記参照データの間接参照の深さが1である。これらに該当する変数参照データ集合を図25の変数テーブル207から読込むと'(*1, PIN)'を得る。この変数参照データ集合には'PIN'が含まれているので、前記関数putsの関数呼出しは脆弱性を生じる可能性があり、この旨を脆弱性検査結果209として出力する。
以上で、本実施形態における基本的な手順の説明を全て終える。
次に、検査対象ソースコード208に条件分岐やループが存在した場合の処理を以下で説明する。
if文やswitch文による条件分岐では、条件式の評価結果により、分岐処理A、分岐処理B、分岐処理C、…のいずれか1つが実行される。if文により条件分岐する検査対象ソースコード208の例を図27に、このソースコードから構文解析手段201により生成された解析木210の例を図28に示す。これらの図を用いてif文の場合の処理を説明する。
脆弱性検出手段202は、手順309において図27の検査対象ソースコード208から構築された図28の解析木210を所定の順序でたどり条件分岐のノードに到達したら、この条件分岐ノードを頂点とする部分解析木について以下の処理を行う。まず前記部分解析木を所定の順序でたどり、1つめの式ノード(図28の4行目)について手順309を適用する。続いて、この時点での動的仮引数間遷移DB206、変数テーブル207の複製を作成する。そして、図28の解析木210を所定の順序でたどり、次に現れる2つめの式ノード(図28の5行目)を頂点とする解析木210について、手順308〜310を適用して前記複製元の動的仮引数間遷移DB206、変数テーブル207を更新する。図28の解析木210を所定の順序でたどりELSEノードに到達したら、さらにその次に現れる3つめの式ノード(図28の7行目)を頂点とする解析木210について、手順308〜310を適用して前記複製された動的仮引数間遷移DB206及び変数テーブル207を更新する。図28の解析木210を所定の順序でたどり、前期3つめの式ノードを頂点とする解析木210をたどり終えたら、前記複製元の動的仮引数間遷移DB206及び変数テーブル207と、前記複製された動的仮引数間遷移DB206及び変数テーブル207とを、それぞれマージする。動的仮引数間遷移DB206のマージでは、同一の関数名を持つ行ごとに、仮引数間遷移リスト同士、外部入力仮引数集合同士を重複する要素を除いて連結する。変数テーブル207のマージでは、同一の変数識別番号を持つ行ごとに、変数参照データ集合リスト内の同一の間接参照の深さである変数参照データ集合同士を重複する要素を除いて連結する。
switch文の場合、脆弱性検出手段202は、その条件式に対して手順309を適用した後、それぞれの分岐処理毎に動的仮引数間遷移DB206、変数テーブル207の複製を用意して手順309を適用して更新処理を行い、それらの結果をマージして新たな動的仮引数間遷移DB206、変数テーブル207とする。
for文、while文、do-while文によるループの場合、脆弱性検出手段202はその条件式(for文の場合は初期化と更新の式も含む)及びループ本体に対し一度だけ手順309を適用し、動的仮引数間遷移DB206、変数テーブル207を更新する。
上記第1の実施形態では脆弱性DB205に登録された関数を直接呼び出す箇所のみ脆弱性として検出しているが、そのような箇所を含むユーザ定義関数を呼び出す箇所も脆弱性として指摘することも上記第1の実施形態の手順を一部修正することで可能である。
その一つの方法は、上記のようなユーザ定義関数と外部入力データを受け取ることのある仮引数を組にして記憶し、その使用箇所を手順314と同様の処理で検出するというものである。
また別の方法は、ユーザ定義関数のそれぞれの仮引数について、そのユーザ定義関数の呼出し箇所における実引数の参照データの種類を、外部入力データ'IN'、擬似外部入力データ'PIN'、その他に分類して動的仮引数間遷移DB206に記憶しておく。手順304において同DBの更新がなくなったときに、その他に分類された参照データを受け取ることのない仮引数を持ち、かつその仮引数で受け取った外部入力データの使用により脆弱性として検出される箇所を内部に持つユーザ定義関数について、その関数名と仮引数を組にして記憶し、その使用箇所を手順314と同様の処理で検出するというものである。
以上で、第1の実施形態の説明を終える。
次に第2の実施形態について説明する。第2の実施形態のソースコード脆弱性検査装置の構成は、第1の実施の形態で説明した図1と同じであるので、構成の説明は省略する。ただし、各変数や仮引数の保持する値の表記方法などは第1の実施形態とは異なる。
図29は、第2の実施形態のソースコード脆弱性検査装置の備える機能構成図である。本実施形態のソースコード脆弱性検査装置は、ソースコード検査手段201、固定関数遷移DB2902、脆弱遷移先DB2903、不定関数遷移DB2904、及び変数DB2905を備える。ソースコード検査手段201は、ソースコードの構文を解析する構文解析手段2911、及び脆弱な箇所を検出する脆弱性検出手段2912を備える。
図30は、あらかじめ登録された脆弱遷移先DB2903の例である。1列目は脆弱遷移先、2列目は脆弱回避値である。
図31は、固定関数遷移DB2902の例である。固定関数遷移DB2902は、標準ライブラリ関数などのデータ遷移をあらかじめ登録したデータベースである。1列目は関数名、2列目はどの値がどこに遷移するかを示すデータ遷移のリストである。データ遷移は、矢印'→'を用いて表記し、'→'の左を遷移元、'→'の右を遷移先とする。遷移元には、後述する変数DB2905に正の整数で登録される第何引数かを表す引数番号、外部から入力されたデータに由来するデータを表すIN、脆弱性を回避する処理を経たことを示す脆弱性回避値のいずれか、もしくはこれらの組み合わせが設定される。遷移先には、引数番号、グローバル変数の変数番号、戻り値を表すR、脆弱性を生じる可能性のある遷移先である脆弱遷移先のいずれかが設定される。
図32は、不定関数遷移DB2904の例である。不定関数遷移DB2904には、プログラマが定義した関数についてのデータ遷移情報を、ソースコード解析中に登録する。これは、固定関数遷移DB2902と同様の構造であるが、新たに解析を終了したかどうかを表すための解析済みフラグの項目を追加している。解析済みフラグは0か1の値をとり、解析済みフラグが0である関数は、後述の手順3406から3412までの解析が終了していないことを表し、一方1である関数は前記解析が終了していることを表す。初期値は0とする。以下、前記解析が終了していない関数の状態を未解析であると呼ぶ。不定関数遷移DB2904で遷移先に設定され得る値には、固定関数遷移DB2902の場合に設定され得る値に加え、後述する変数DB2905に負の整数で登録されるグローバル変数の変数番号がある。遷移値には、手順3401終了時、初期値DEFが与えられている。この値は、後述の手順3406または手順3408にて更新される。
図33は、変数DB2905の例である。1列目は変数DB2905に登録された変数の識別番号である。この変数番号は、グローバル変数の場合、負の整数値で−1、−2と降順に付ける。仮引数の場合、引数番号と一致する正の整数値を付ける。ローカル変数の場合、既に登録されている仮引数の変数番号に続くように昇順に付ける。正の整数値が一つも登録されていない場合、1から順に昇順に付ける。2列目は変数の種別を表し、仮引数を登録する場合は'A'、ローカル変数なら'L'、グローバル変数なら'G'を格納する。3列目は変数名である。4列目は変数の保持する値であり、初期値は変数番号と同一の値とする。値が定数の場合は、Cとする。
図34は、本実施形態のソースコード脆弱性検査装置の処理手順である。以下の処理で、関数定義、代入、及び関数呼び出しなどのソースコード中の構文の解析は、構文解析手段2911が行う。
ソースコード検査プログラムが起動されると、与えられたソースコードを全てたどり、見つかったプログラマ定義関数を不定関数遷移DB2904に、グローバル変数を変数DB2905に、それぞれ登録する。グローバル変数の変数番号は、負の整数値で−1から降順に付ける(手順3401)。
次に、ソースコードの先頭を探索開始位置にする(手順3402)。複数のファイルで構成されるソースコードを検査する場合、検査するファイルの順序を決めておき、その先頭を探索開始位置にする。次に、ソースコード中から、未解析である関数定義を探索する(手順3403)。未解析の関数が無い場合は後述する手順3413に移る(手順3403)。該当関数が見つかれば、次に、該当関数の定義の中で、未解析の関数呼び出しが行われていないかを探索する(手順3404)。未解析の関数呼び出しが行われている場合、探索開始位置をソースコード中の次の関数定義位置に移し(手順3405)、手順3403に戻る。未解析の関数呼び出しが行われていない場合、本関数を処理対象関数とし、解析を開始する(手順3406)。
まず、対象関数の仮引数があればそれを変数DB2905に登録する(手順3407)。このとき、仮引数の変数番号は、その引数番号と一致するように登録する。対象関数内で定義されたローカル変数が見つかれば、それらを変数DB2905に登録する(手順3408)。変数番号は、正の整数で昇順に付ける。
さらに、関数呼び出し箇所があれば、関数名で固定関数遷移DB2902および不定関数遷移DB2904を探索し、該当関数の遷移値を取得する。遷移値がDEFである場合、手順3410へ進む。そうでない場合、次に記す処理を行う。呼び出された関数が引数を持つ関数である場合、実引数の値を取得する。関数呼び出しに伴う遷移の遷移元が正の整数(引数番号)である場合、その遷移元を引数番号に相当する実引数値に修正後、以下の処理を行う。遷移元が負の整数(グローバル変数の変数番号)である場合、変数DB2905から該当する変数値を参照し、その値に修正した後、以下の処理を行う。それ以外の場合、何もせず以下の処理を行う。
データ遷移の遷移先の値に応じて次の処理を行う(手順3409)。まず、遷移先が変数番号のデータ遷移の箇所があれば、変数DB2905の該当する変数値を更新する。遷移先が脆弱遷移先DB2903の脆弱遷移先のいずれかに合致し、遷移元が変数種別'A'または'G'の変数番号のデータ遷移があれば、不定関数遷移DB2904に追加する。遷移先が脆弱遷移先DB2903の脆弱遷移先のいずれかに合致し、遷移元にINを含み、脆弱遷移先に対応する脆弱性回避値を含まないデータ遷移があれば、不定関数遷移DB2904に追加すると共に、ソース中の該当箇所への警告を検査結果として出力する。
なお、代入文についても、右辺→左辺のデータ遷移として、関数呼び出しと同様に処理する。
対象関数の処理を終えるまで、手順3408、3409を繰り返す。対象関数の処理を終えたら、解析済みフラグを1に設定し、手順3411に移る(手順3410)。
変数DB2905の種別欄で種別が'A'(仮引数)または'G'(グローバル変数)となっている行を選び、その値を取得する。値がINまたは、それ自身以外の変数番号でかつ種別が'A'または'G'である場合、それぞれINまたは変数番号を遷移元とし、該当行の変数番号を遷移先とする遷移を不定関数遷移DB2904に登録する(手順3411)。
変数DB2905から仮引数とローカル変数についての登録データを削除する。グローバル変数の値を初期化する(手順3412)。対象関数についての処理を終え、手順3403に戻る。
手順3413では不定関数遷移DB2904に、前回解析時と比較して、グローバル変数を遷移先とする新たな遷移値の登録があるかどうかを調べる。該当する遷移値の登録がある場合、不定関数遷移DB2904の解析済みフラグの値をすべて0に初期化し、手順3403へ戻る。一方、無い場合は処理を終了する。
後述する検査対象ソースコードにポインタ変数の遷移がないため、記述していないが、変数DB2905に参照の深さと参照値を加えることと、固定関数遷移DB2902および不定関数遷移DB2904の遷移値に設定できるものの一つにこの参照値を加えることと、手順3409で遷移元がポインタ変数の変数番号である場合、変数リストから、その値を取得し、遷移元を修正すること、を行うことでポインタ変数の遷移に対応できる。
以上で図34の説明を終える。
図46は、本実施形態のソースコード脆弱性検査装置を説明するための検査対象ソースコードである。C言語で記述されており、説明の便宜上一番左に行番号を記述している。sanitizeOs関数の定義を省いているが、含めても以下の結果は変わらない。
まず、従来のパターンマッチング法を、図46のソースコードに適用した場合について説明する。パターンマッチング法による検査では、関数に渡されるデータが外部由来かどうかを判断することなく、あらかじめ登録された脆弱性を生じる可能性のあるライブラリ関数使用箇所のすべてを検出するため、22行目のOSコマンドインジェクションを生じる可能性のあるライブラリ関数であるsystem関数に対し、警告が出力される。しかし、system関数は直接main関数から呼ばれることはなく、call_do_system関数を通して呼ばれており、system関数が実行される5行目、8行目、12行目の3箇所のうち、8行目の一箇所だけが実際に脆弱である。5行目は、扱うデータが外部由来ではない。また、12行目は、扱うデータが事前に適切な脆弱性回避処理がなされたデータである。プログラマは、パターンマッチング法を用いた脆弱性検査を行った後、このように警告箇所が実際に脆弱であるかを、自ら目視で判断する必要がある。
また、プログラマが脆弱性の警告への対処として行う脆弱性回避処理は、目視で実際に脆弱であると判断された箇所で行う必要がある。もし、警告された22行目の直前でその扱うデータに対し脆弱性回避処理を施すと、8行目で脆弱性回避処理を行う必要のないデータに対し脆弱性回避処理が行われ、12行目で二重に脆弱性回避処理が行われることになり、この2箇所で想定外の動作が生じる可能性がある。
次に図46のソースコードに対して、34の処理を適用した場合の処理を説明する。図32は、手順3401終了時の不定関数遷移DB2904の例を示し、検査対象ソースコードに定義されている関数が登録されている。この検査対象ソースコードにはグローバル変数は定義されていない。
手順3401,3402の後、ソースコードの先頭から未解析の関数を探索し、main関数が検出される(手順3403)。main関数の定義内で、未解析の関数呼び出しの有無の検査を行う(手順3404)。5行目でcall_do_system関数が呼ばれており、不定関数遷移DB2904より解析済みフラグが0であることが分かる(手順3404)。すなわち未解決の関数呼び出しが行われているので、探索開始位置を次の関数定義に移す(手順3405)。
次に、do_gets関数が、未解析で、定義内で未解析の関数呼び出しが行われていないことが分かる(手順3403および3404)。よってdo_getsを解析対象の関数(対象関数)とする(手順3406)。以降、手順3406にて対象関数とされる関数の順番は、do_gets、do_system、call_do_system、mainである。
まず対象関数do_getsに対し、仮引数とローカル変数を変数DB2905に登録する(手順3407、3408)。do_getsについて手順3408終了時の変数DB2905が、図33である。17行目の関数getsの呼び出しによるデータ遷移の処理を行う(手順3409)。getsの遷移値を、固定関数遷移DB2902および不定関数遷移DB2904より検索し取り出す。すると、IN→1の遷移が取得される。この遷移先は、変数番号であるので、変数DB2905の該当変数の変数値を更新する。以上で対象関数do_getsの処理を終える(手順3410)。手順3410終了時の変数DB2905が、図35である。
変数DB2905のうち、種別が'A'の変数値がINであるので、これに相当する遷移IN→1を、不定関数遷移DB2904に登録する(手順3411)。手順3411終了時の不定関数遷移DB2904が、図36である。
次にdo_systemを対象関数とした処理を説明する。上記と同様にして、仮引数とローカル変数を変数DB2905に登録する(手順3407、3408)。手順3408終了時の変数DB2905が、図37である。22行目の関数systemの呼び出しによるデータ遷移の処理を行う(手順3409)。systemの遷移値を、固定関数遷移DB2902および不定関数遷移DB2904より検索し取り出す。すると、1→OSCOMMの遷移が取得される。第一引数の実引数値は1であり、遷移元をこの値に修正する。遷移先OSCOMMは、脆弱遷移先DB2903の脆弱遷移先に登録されている。遷移元は変数種別'A'の変数番号であるので、不定関数遷移DB2904に遷移を登録する(手順3409)。以上で対象関数do_systemの処理を終える(手順3410)。手順3410終了時の不定関数遷移DB2904が、図38である。
次にcall_do_systemを対象関数とした処理を説明する。上記と同様にして、仮引数とローカル変数を変数DB2905に登録する(手順3407、3408)。手順3408終了時の変数DB2905が、図39である。27行目の関数do_systemの呼び出しによるデータ遷移の処理を行う(手順3409)。do_systemの遷移値を、固定関数遷移DB2902および不定関数遷移DB2904より検索し取り出す。すると、1→OSCOMMの遷移が取得される。第一引数の実引数値は1であり、遷移元をこの値に修正する。この遷移先OSCOMMは、脆弱遷移先DB2903の脆弱遷移先に登録されている。遷移元は変数種別'A'の変数番号であるので、不定関数遷移DB2904に遷移を登録する(手順3409)。以上で対象関数call_do_systemの処理を終える(手順3410)。手順3410終了時の不定関数遷移DB2904が、図40である。
次にmainを対象関数とした処理を説明する。上記と同様にして、仮引数とローカル変数を変数DB2905に登録する(手順3407、3408)。手順3408終了時の変数DB2905が、図41である。5行目の関数call_do_systemの呼び出しによるデータ遷移の処理を行う(手順3409)。すなわち、まずdo_systemの遷移値を、固定関数遷移DB2902および不定関数遷移DB2904より検索し取り出す。すると、1→OSCOMMの遷移が取得される。第一引数の実引数値は定数を表すCであり、遷移元をこの値に修正する。この遷移先OSCOMMは、脆弱遷移先DB2903の脆弱遷移先に登録されている。しかし、遷移元が定数値Cであるので、何も行わない(手順3409)。
次に7行目の関数get_inputの呼び出しによるデータ遷移の処理を行う(手順3409)。17行目と同様に処理する。手順3409終了時の変数DB2905が、図42である。
次に8行目の関数call_do_systemの呼び出しによるデータ遷移の処理を行う(手順3409)。同様に1→OSCOMMの遷移が取得される。第一引数の実引数値はINであり、遷移元をこの値に修正する。この遷移先OSCOMMは、脆弱遷移先DB2903の脆弱遷移先に登録されている。遷移元がINであるので、不定関数遷移DB2904に登録するとともに、この箇所が脆弱であるという警告を検査結果として出力する。
次に10行目の関数strlcpyの呼び出しによるデータ遷移の処理を行う(手順3409)。同様に2→1の遷移が取得される。第二引数の実引数値はINであり、遷移元をこの値に修正する。この遷移先は、変数番号であるので、変数DB2905の該当変数の変数値を更新する。手順3409終了時の変数DB2905が、図43である。
次に11行目の関数sanitizeOsの呼び出しによるデータ遷移の処理を行う(手順3409)。同様に1,SAFE_OS→1の遷移が取得される。第一引数の実引数値はINであり、遷移元の1をこの値に修正する。この遷移先は、変数番号であるので、変数DB2905の該当変数の変数値を更新する。手順3409終了時の変数DB2905が、図44である。
次に12行目の関数call_do_systemの呼び出しによるデータ遷移の処理を行う(手順3409)。同様に1→OSCOMMの遷移が取得される。第一引数の実引数値はIN, SAFE_OSであり、遷移元をこの値に修正する。この遷移先OSCOMMは、脆弱遷移先DB2903の脆弱遷移先に登録されている。遷移元にINを含むが、脆弱遷移先DB2903の脆弱回避値SAFE_OSも同時に含むので、何も行わない。
以上で対象関数mainの処理を終える(手順3410)。手順3410終了時の不定関数遷移DB2904が、図45である。
未解析の関数探索(手順3403)にて該当関数が見つからないので手順3413に移る。不定関数遷移DB2904に登録されている遷移値で、グローバル変数の変数番号を遷移先とする遷移の登録はないため、検査を終了する。
以上のように、本ソースコード脆弱性検査装置を、図46のソースコードに適用した場合、正確に8行目が脆弱であるという警告を出す。よってプログラマは、8行目の直前でその扱うデータに対し脆弱性回避処理を施せばいいことが分かる。このように従来のパターンマッチング法に比べ、誤った検出がなされず、プログラマが脆弱性回避処理を行うべき箇所を容易に特定できる。また、ソースコードに検査のための注釈を書き込む必要がない。
また本実施形態では記述していないが、変数に対し二重に脆弱性回避処理を行ってしまう場合に、警告を出すこともできる。手順3409にて変数DB2905の変数値を更新する際に、新しく更新された値に、脆弱遷移先DB2903の脆弱回避値の同一の値が二つ以上含まれる場合、その箇所に警告を出すようにすればよい。
また、遷移先が変数DB2905の種別'A'もしくは'G'の変数の変数番号である場合、図34の手順3409で一度変数DB2905にその遷移を反映した後、手順3411にて遷移を読み取り不定関数遷移DBに登録するのではなく、手順3411を無くし、直接不定関数遷移DBに遷移を登録してもよい。
以上で第2の実施形態の説明を終える。
101…ソースコード脆弱性検査装置の本体(コンピュータ)、102…CPU、103…記憶装置、104…メモリ、105…キーボード、106…CRT、107…外部記憶装置、108…バス。