ARR00-C. 配列の仕組みを理解する
配列の誤用は、昔から攻撃可能な脆弱性の原因となっている。添字演算子 []
を使用して配列内で参照される要素は、プログラマが適切な範囲検査を行わない限り、検査されることはない。そのため array [pos] = value
のような式は、任意のコードに制御を移すために攻撃者に悪用されうる。
式 array [pos] = value
における値 pos
と value
の両方を攻撃者が制御できる場合、任意の上書きが可能となる(攻撃者が他の記憶域を別の内容で上書きする場合)。プログラムが権限を決めるための変数が書き換えられたり、脆弱なプロセスの権限で任意のコードが実行されたりするということである。また、添字の値が配列の範囲を超えたときにバッファオーバーフローの原因にもなりやすい。
配列とは、サイズと型が同じオブジェクトの並びである。配列の各オブジェクトは配列要素と呼ばれる。配列全体はメモリに連続的に格納される(要素間に隙間はない)。配列は一般に、ランダムアクセスの重要度は高いが要素を追加する必要はほとんどないような要素の並びを表現するために使う(配列では、新しい要素の挿入はコストのかかる操作)。
決まった数の要素を持つ配列は、次のように宣言できる。
enum { ARRAY_SIZE = 12 }; int array[ARRAY_SIZE];
この文は、12 個の整数からなり array
によって参照される配列に記憶域を割り当てる。配列は 0
からn-1
のインデックスで参照される(n
は配列境界を示す)。配列は次のように宣言することもできる。
int array[];
サイズが未定のため、これは不完全型と呼ばれる。サイズが未定の配列を初期化する場合、配列のサイズは、明示的な初期化子の、最も大きなインデックスで参照される要素によって決定される。初期化子並びの終了時点で、その配列はもはや不完全型をもたない。
int array[] = { 1, 2 };
コンパイル時に配列のサイズが決まっている場合はよいが、実行時にサイズが決まる場合には、この方法で配列を宣言することはできない。C 言語規格では、可変長配列、すなわちサイズが実行時に決まる配列に関する規定が追加されている。C99 で可変長配列が採用される前は、このような可変長の配列は通常、以下に示す例のように、malloc()
を使って割り当てられる要素の型へのポインタとして実現されていた。
int *dis = (int *)malloc(ARRAY_SIZE * sizeof(int));
「MEM32-C. メモリ割り当てエラーを検出し、対処する」に従い、malloc()
が NULL 以外のポインタを返すことを必ず検査しなければならない。
ポインタが参照するメモリを後で解放できるように、例えば定数ポインタを使って、malloc()
が返すポインタ値は保持すること。
int * const dat = (int * const)malloc( ARRAY_SIZE * sizeof(int) ); /* ... */ free(dat);
配列 dis
と dat
はいずれも次のように初期化することができる。
for (i = 0; i < ARRAY_SIZE; i++) { dat[i] = 42; /* 各要素に 42 を代入 */ /* ... */ }
また、配列 dis
は次のように初期化してもよい。
for (i = 0; i < ARRAY_SIZE; i++) { *dis = 42; dis++; } dis -= ARRAY_SIZE;
識別子 dat
はインクリメントできないので、式 dat++
はコンパイルエラーになる。配列 dis
とdat
は次のように初期化することもできる。
int *p = dat; for (i = 0; i < ARRAY_SIZE; i++) { *p = 42; /* 各要素に 42 を代入 */ p++; }
変数 p
は整数へのポインタとして宣言され、ループ内でインクリメントされる。この方法を使えば、dis
もdat
も初期化することができるし、配列の先頭を指すポインタを変更しないので、配列へのポインタをインクリメントする方法よりも優れている。
配列添字演算子 []
とポインタの間には一定の関係がある。式 dis[i]
は、i
のすべての整数値について、* (dis+i)
と同値である。つまり、dis
が配列オブジェクト(配列オブジェクトの最初の要素へのポインタと等しい)でありi
が整数である場合、dis[i]
は dis
の i
番目の要素を意味する。実際、* (dis+i)
は * (i+dis)
とも表現できるため、式 dis[i]
は i[dis]
と表すこともが、このような記法は望ましくない。配列のインデックスはゼロを基準とするため、配列の最初の要素を指定するには dis[0]
、または同じ意味で *(dis+0)
、あるいは単に *dis
とする。
リスク評価
配列は、使用頻度は高いがよく理解されていない場合もありC で書かれたプログラムの脆弱性の温床となっている。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
ARR00-C |
高 |
中 |
高 |
P6 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
LDRA tool suite |
V. 8.5.4 |
401 S |
部分的に実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | ARR00-CPP. Understand when to prefer vectors over arrays |
MITRE CWE | CWE-119, Failure to constrain operations within the bounds of an allocated memory buffer CWE-129, Unchecked array indexing |
翻訳元
これは以下のページを翻訳したものです。
ARR00-C. Understand how arrays work (revision 44)