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

MEM02-C. メモリ割り当て関数の結果は、割り当てた型へのポインタに即座にキャストする

MEM02-C. メモリ割り当て関数の結果は、割り当てた型へのポインタに即座にキャストする

型が void * のオブジェクトは汎用のデータポインタである。このポインタは任意のデータオブジェクトを指すことができる。C 言語では、あらゆる不完全型またはオブジェクト型 T について、T * から void *、または void * から T * への暗黙的変換が認められている。標準 C ライブラリ関数では、様々な型のオブジェクトに対して動作する関数の引数と返り値の型を void * を使って宣言している。標準メモリ割り当て関数である aligned_alloc()malloc()calloc()、および realloc() がこれに該当する。

たとえば C ライブラリは malloc() を次のように宣言している。

void *malloc(size_t);

malloc(s) を呼び出すと、サイズが s バイトであるオブジェクトのためにメモリ割り当てが行われ、ナルポインタまたは割り当てられたメモリへのポインタのいずれかが返される。malloc() から返されたポインタは、他のポインタ型に暗黙的に変換されることがある。

違反コード

malloc() の引数は、size_t 型 (符号無し) の任意の値である。割り当てられた記憶域を使用して、プログラムが malloc() に要求したサイズよりも大きいオブジェクト(配列もありうる)を表現しようとすると、動作は未定義となる。ポインタの暗黙的な型変換に対してコンパイラは警告を出さず、見逃されてしまう。

以下のコード例を考えてみよう。

#include <stdlib.h>

typedef struct gadget gadget;
struct gadget {
  int i;
  double d;
};

typedef struct widget widget;
struct widget {
  char c[10];
  int i;
  double d;
};

widget *p;

/* ... */

p = malloc(sizeof(gadget)); /* ここが問題 */
if (p != NULL) {
  p->i = 0;               /* 未定義の動作 */
  p->d = 0.0;             /* 未定義の動作 */
}

処理系が、gadgetwidget にパディングを追加して sizeof(gadget)sizeof(widget) が等しくなることもあるかもしれないが、その可能性はきわめて低い。たいていの場合、sizeof(gadget)sizeof(widget) よりも小さくなるだろう。その場合、

p = malloc(sizeof(gadget)); /* ここが問題 */

このコードによって p に設定されたポインタが指す記憶域は、widget を保持するには小さすぎる。この後、p->i および p->d への代入によって、メモリオーバーランが発生する可能性が高い。

malloc() の結果を適切なポインタ型にキャストすることで、コンパイラが不適切な変換を捕捉できるようになる。ここで「適切なポインタ型」とは、malloc() に渡される sizeof 式内の型へのポインタである。

以下のコード例では、malloc()gadget の領域を割り当て、返されたポインタをすぐに gadget * 型に変換している。

widget *p;

/* ... */

p = (gadget *)malloc(sizeof(gadget)); /* 不適切な代入 */

gadget *widget * に変換しようとしているため、コンパイラは不適切な代入を検出できる。

適合コード (手作業によるコーディング)

以下の適合コードでは、sizeof 式とポインタのキャストで同じ型を繰り返し使っている。

widget *p;

/* ... */

p = (widget *)malloc(sizeof(widget));
適合コード (マクロ)

sizeof 式とポインタのキャストで同じ型を繰り返し使うのは簡単だが、それでも間違いは起きる。次のように繰り返しをマクロ化することで、間違いの可能性をさらに減らすことができる。

#define MALLOC(type) ((type *)malloc(sizeof(type)))

widget *p;

/* ... */

p = MALLOC(widget);     /* OK */
if (p != NULL) {
  p->i = 0;           /* OK */
  p->d = 0.0;         /* OK */
}

ここでは、割り当て式全体(代入演算子の右側の式)で widget に対する記憶域を割り当て、widget * を返す。pwidget * 型ではない場合、コンパイラは代入に対して警告を発するだろう。

T の要素 N 個からなる配列を割り当てる場合、キャスト式の適切な型は依然として T * であるが、malloc() の引数は N * sizeof(T) の形式でなければならない。ここでも、次のように繰り返しをマクロ化することで、割り当て式でのエラーの可能性が減る。

#define MALLOC_ARRAY(number, type) \
    ((type *)malloc((number) * sizeof(type)))

enum { N = 16 };
widget *p;

/* ... */

p = MALLOC_ARRAY(N, widget); /* OK */

以下のマクロを使うことで、標準メモリ割り当て関数の通常の使用を安全に行うことができる。REALLOC() マクロは意図的に省略している(「MEM08-C. realloc() は動的に割り当てられた配列のサイズ変更だけに使用する」を参照)。

/* malloc() を使用して単一オブジェクトを割り当てる。 */
#define MALLOC(type) ((type *)malloc(sizeof(type)))

/* malloc() を使用してオブジェクトの配列を割り当てる。 */
#define MALLOC_ARRAY(number, type) \
    ((type *)malloc((number) * sizeof(type)))

/* malloc() を使用してフレキシブル配列メンバを持つ
 * 単一オブジェクトを割り当てる。 */
#define MALLOC_FLEX(stype, number, etype) \
    ((stype *)malloc(sizeof(stype) \
    + (number) * sizeof(etype)))

/* calloc() を使用してオブジェクトの配列を割り当てる。 */
#define CALLOC(number, type) \
    ((type *)calloc(number, sizeof(type)))

/* realloc() を使用してオブジェクトの配列の割り当てをやり直す。 */
#define REALLOC_ARRAY(pointer, number, type) \
    ((type *)realloc(pointer, (number) * sizeof(type)))

/* realloc() を使用してフレキシブル配列メンバを持つ
 * 単一オブジェクトの割り当てをやり直す。 */
#define REALLOC_FLEX(pointer, stype, number, etype) \
    ((stype *)realloc(pointer, sizeof(stype) \
    + (number) * sizeof(etype)))

マクロの使用例を以下に示す。

enum month { Jan, Feb, /* ... */ };
typedef enum month month;

typedef struct date date;
struct date {
  unsigned char dd;
  month mm;
  unsigned yy;
};

typedef struct string string;
struct string {
  size_t length;
  char text[];
};

date *d, *week, *fortnight;
string *name;

d = MALLOC(date);
week = MALLOC_ARRAY(7, date);
name = MALLOC_FLEX(string, 16, char);
fortnight = CALLOC(14, date);

これらのマクロ定義のなかで使用している乗算のオペランドが、信頼できないデータによる影響を受ける可能性がある場合は、マクロを呼び出す前に乗算でオーバーフローが発生しないことを確認する必要がある(「INT32-C. 符号付き整数演算がオーバーフローを引き起こさないことを保証する」を参照)。

型総称関数形式マクロの使用は、「PRE00-C. 関数形式マクロよりもインライン関数やスタティック関数を使う」の例外として認められている(PRE00-EX4)。

リスク評価

メモリ割り当て関数呼び出しの結果を、割り当てた型へのポインタに変換しなかった場合、不適切なポインタ変換が行われる可能性がある。このレコメンデーションに適合したコードは、C++ の場合も正しくコンパイル、実行される。

レコメンデーション

深刻度

可能性

修正コスト

優先度

レベル

MEM02-C

P3

L3

自動検出(最新の情報はこちら

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

「EXP36-C. ポインタをより厳密にアラインされるポインタ型に変換しない」の検査により、このレコメンデーションの違反の一部を検出できる。

ECLAIR 1.1 funcalls 実装済み

Fortify SCA

5.0

 

CERT C Rule Packを使ってこのレコメンデーションの違反を検出できる

LDRA tool suite

V. 8.5.4

 

 

PRQA QA-C 8.1 0695 実装済み
関連するガイドライン
CERT C++ Secure Coding Standard MEM02-CPP. Immediately cast the result of a memory allocation function call into a pointer to the allocated type
参考資料
[Summit 2005] Question 7.7
Question 7.7b
翻訳元

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

MEM02-C. Immediately cast the result of a memory allocation function call into a pointer to the allocated type (revision 83)

Top へ

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