JPCERT コーディネーションセンター

MEM04-C. サイズ 0 のメモリ割り当てを行わない

要求されたサイズが 0 の場合、メモリ割り当て関数 malloc()calloc() および realloc() の動作は処理系定義である。C 標準 [ISO/IEC 9899:2011] セクション 7.22.3 には次のように記載されている。

要求された領域の大きさが 0 であるとき、その動作は処理系定義とする。ただし、null ポインタを返すか、又は 0 ではない大きさを要求したときと同じ動作(このとき、返されたポインタをオブジェクトのアクセスに使用してはならない)、のいずれかでなければならない。

さらに、0 バイトが要求され、割り当て関数が正常終了したときに割り当てられる記憶域の容量は未規定である。C 標準、セクションJ.1「未規定の動作」の 41 項を参照のこと。

メモリ割り当て関数が null 以外のポインタを返した場合、割り当てられたメモリ領域に対する読み取り/書き込みは未定義の動作となる。通常、このポインタが参照するのは、メモリ管理用のデータのみで構成されるサイズ 0 のメモリ領域である。これらメモリ管理用データを上書きすると、メモリ管理のために使用されているデータ構造が破壊される。

違反コード (malloc())

malloc(0) を呼び出して 0 バイトを割り当てた場合の結果は処理系定義となる。以下のコード例では、size で表される数の要素を格納するために、整数からなる動的配列を割り当てているが、size が 0 の場合、malloc(size) の呼び出しが null ポインタではなく、サイズ 0 のメモリ領域への参照を返すかもしれない。データをこの場所にコピーすると、ヒープバッファオーバーフローが発生する。

size_t size;

/* size を初期化. 値はユーザによって操作されているかもしれない */

int *list = (int *)malloc(size);
if (list == NULL) {
  /* 割り当てエラーの処理 */
}
else {
/* list に対する処理を続行 */
}
適合コード (malloc())

malloc() のサイズ引数として 0 が渡されないようにするため、size が正の値であることを確認する。

size_t size;

/* size を初期化. 値はユーザによって操作されているかもしれない */

if (size == 0) {
  /* エラー処理 */
}
int *list = (int *)malloc(size);
if (list == NULL) {
  /* 割り当てエラーの処理 */
}
/* リストの処理を続行 */
違反コード (realloc())

realloc() 関数は古いオブジェクトを解放し、指定したサイズの新しいオブジェクトへのポインタを返す。新しいオブジェクトにメモリを割り当てることができない場合、realloc() 関数は古いオブジェクトを解放せず、その値も変更しない。realloc() 関数が NULL を返した場合、元のメモリを解放しないとメモリリークが発生する。このため、多くの場合メモリの再割り当てには次のようなコードが推奨される。

size_t nsize = /* ユーザが操作しているかもしれない何らかの値 */;
char *p2;
char *p = (char *)malloc(100);
if (p == NULL) {
  /* エラー処理 */
}

/* ... */

if ((p2 = (char *)realloc(p, nsize)) == NULL) {
  free(p);
  p = NULL;
  return NULL;
}
p = p2;

ただし、一般的には推奨されるこのコードも、サイズ 0 のメモリ割り当てに関しては問題がある。このコード例で nsize の値が 0 の場合、C 言語規格上は、null ポインタを返す、あるいは、無効な(たとえば、サイズ 0 などの)オブジェクトへのポインタを返す、というふたつの選択肢がある。realloc() 関数がメモリを解放して null ポインタを返す場合、このコードの実行は二重解放の脆弱性を招く。realloc() 関数がサイズ引数 0 に対して null でない値を返す場合、返されたオブジェクトのサイズは 0 なので、そこにデータがコピーされるとヒープオーバーフローが発生する。

処理系固有の詳細

上記の違反コードを GCC 3.4.6 でコンパイルし、libc 2.3.4 でリンクした場合、realloc(p, 0) を呼び出すと、サイズ 0 のオブジェクトへの null でないポインタが返される(malloc(0) と同様)。しかし、同じコードを Microsoft Visual Studio 7.1 または GCC 4.1.0 でコンパイルすると、realloc(p, 0) は null ポインタを返し、二重解放の脆弱性が発生する。

適合コード (realloc())

この適合コードでは、realloc() 関数のサイズ引数に 0 を渡さない。

size_t nsize;
/* nsize の初期化 */
char *p2;
char *p = (char *)malloc(100);
if (p == NULL) {
  /* エラー処理 */
}

/* ... */

p2 = NULL;
if (nsize != 0) {
  p2 = (char *)realloc(p, nsize);
}
if (p2 == NULL) {
  free(p);
  p = NULL;
  return NULL;
}
p = p2;
リスク評価

0 バイトのメモリ割り当てを行うと、プログラムが異常終了することがある。

レコメンデーション

深刻度

可能性

修正コスト

優先度

レベル

MEM04-C

P6

L2

自動検出

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

違反のいくつかは検出できる。特に、malloc() の引数が変数であり、その値が 0 かどうかを比較するコードがない、あるいはコンパイル時に 0 であることがわかっている場合に警告を出す。

関連するガイドライン
CERT C++ Secure Coding Standard MEM04-CPP. Do not perform zero-length allocations
MITRE CWE CWE-687, Function call with incorrectly specified argument value
参考資料
[ISO/IEC 9899:2011] Section 7.22.3, "Memory Management Functions"
[Seacord 2013] Chapter 4, "Dynamic Memory Management"
[Vanegue 2010] "Automated Vulnerability Analysis of Zero-Sized Heap Allocations"
翻訳元

これは以下のページを翻訳したものです。

MEM04-C. Do not perform zero-length allocations (revision 115)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter