コピー元のデータ量に基づきコピー先の記憶領域を動的に確保
コピー元のデータ量に基づいてコピー先の記憶領域を動的に確保する場合には、以下のようなコードになるでしょう。動的に確保するコピー先領域のサイズを計算する際、演算結果が整数オーバーフローを引き起こさないことをチェックする必要があります。
void func(const int *src, size_t num_elem) { int *dest; if (num_elem > SIZE_MAX/sizeof(int)) { /* 整数オーバーフロー時の処理 */ } dest = (int *)malloc(sizeof(int) * num_elem); if (dest == NULL) { /* 動的領域確保に失敗した場合の処理 */ } memcpy(dest, src, sizeof(int) * num_elem); /* ... */ free(dest); }
サイズを示す変数は符号なし整数を用いて符号エラーを避ける
以下に、上記のコードを一部変更したコード例を示します。コピーする配列の要素数を示す値を符号付き整数(int num_elem)で受けており、渡された配列の要素数が上限値未満であることを確認しています。しかしこのコードでは、配列要素数として負の値が渡される可能性を考慮していません。もしも配列要素数として負の値が渡された場合、上限値検査をパスし、malloc()の引数として符号付き型から符号なし型への(暗黙の)変換が発生、その結果、巨大な正の値として取り扱われてしまいます。
void func(const int *src, int num_elem) { int *dest; if (num_elem < SIZE_MAX/sizeof(int)) { //負の値に対する下限チェックがない dest = (int *)malloc(sizeof(int) * num_elem); ・・・ }else{ /* 整数オーバーフロー時の処理 */ } ・・・
この例では、負の値を取りうる配列要素数に対して下限検査が行われていないため、プログラマが意図していない非常に大きな記憶領域が確保されてしまう可能性があります。また、memcpy()などの引数として負の値が渡されるケースでは、コピー先領域を超えたサイズを指定することになり、バッファオーバーフローに繋がってしまうでしょう。
上記のようなコード例においては、下限チェックを加えることにより引数が正の値であることを確認する必要があります。そもそも、オブジェクトのサイズや配列のインデックスのような正の値を取るべき変数には、最初からsize_t型やunsigned int型などの符号なしの型を利用すべきでしょう。
文字列をはじめとする配列データの操作のために領域を確保したり、必要なサイズを計算することは多くあります。そのような場合に整数オーバーフローやバッファオーバーフローに繋がらないよう、コーディング時に注意を払いましょう。
今後の連載について
これまで6回にわたりセキュアなコーディングを行うために心がけるポイントや方法について解説してきたこの連載ですが、いったん、ここで終了させたいと思います。
しかし、まだまだカバーできていないトピックが「CERT C セキュアコーディングスタンダード」には残っています。これらについては、記事の形式をコードレビュー形式に変えた上で、近いうちに新しい連載として開始する予定です。
こうご期待。