配列コピー時に犯しやすい誤り
データをコピーする先の記憶領域として十分なサイズが確保されていないと、バッファオーバーフローに繋がります。例えば、コピー先の領域として確保するサイズが不十分だったり、コピー先の領域は意図どおり確保していても、コピーするデータのサイズを誤って指定してしまう場合などです。
典型的な誤りとしては、文字列コピーにおけるNULL終端バイト分のサイズの配慮し忘れがあります(関連ルール「STR31-C. 文字データとNULL終端文字を格納するために十分なサイズの領域を確保する」を参照)。
また、コピーサイズを計算する際に整数オーバーフローが発生したり、正の値を処理することを想定しているロジックに負の値を持った符号付き整数を渡すことで、暗黙の変換により大きな正の値として扱われて問題を起こすケースもあります(関連ルール「STR31-C. 文字データとNULL終端文字を格納するために十分なサイズの領域を確保する」「INT32-C. 符号付き整数演算がオーバーフローを引き起こさないことを保証する」を参照)。
こういったコーディング上の誤りが脆弱性に繋がるケースも少なくありません。例えば、オープンソースのネットワーク侵入検知システムであるSourcefire Snortの旧バージョンには、改ざんされたパケットを送りつけることでサービス停止妨害や任意のコード実行が可能になる脆弱性(CVE-2006-5276)が存在しました。
コーディング時における基本的な対策として、(1)コピー先のサイズに応じてコピーするデータ量を制限する、あるいは(2)コピー先に十分なサイズの領域を確保する、ということを確実に行う必要があるのです。
今回はこういった配列コピー時に犯しやすい誤りについて解説します。
コピーサイズとして誤った値を指定
次に示すコード例は、memcpy()を用いて配列のコピーを行います。memcpy()のサイズ引数としてコピー元の記憶領域サイズを指定していますが、これはコピー先の記憶領域サイズよりも大きいため、バッファオーバーフローが発生します。
enum { WORKSPACE_BOUND = 256 }; void func(const int *src, size_t num_elem) { int dest[WORKSPACE_BOUND]; memcpy(dest, src, num_elem * sizeof(int)); //コピー先の領域に基づいた制限を行っていない /* ... */ }
文字列操作関数strncpy()の使用においても同じような誤りを犯しがちです。
また、配列コピーの際、配列要素型のサイズを考慮し忘れるとコピーデータの欠損に繋がります。
enum { WORKSPACE_BOUND = 256 }; void func(const int *src, size_t num_elem) { int dest[WORKSPACE_BOUND]; memcpy(dest, src, WORKSPACE_BOUND); //配列要素型のサイズを考慮していない /* ... */ }
これらのコーディングエラーは、単体テストレベルで明らかになる部類のものですが、コーディング段階で十分注意しておきたいものです。
コピーするデータ量をコピー先の記憶領域に基づき制限
コピーするデータ量をコピー先の記憶領域に基づいて制限する場合、コピー元のデータがコピー先の領域に収まることをあらかじめ確認する必要があります。
enum { WORKSPACE_BOUND = 256 }; void func(const int *src, size_t num_elem) { int dest[WORKSPACE_BOUND]; if (num_elem > WORKSPACE_BOUND) { /* コピー先の記憶領域が不足するためエラー処理などの対応 */ } memcpy(dest, src, sizeof(int) * num_elem); /* ... */ }