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

MEM33-C. フレキシブル配列メンバを含む構造を動的に割り当ててコピーする

C 標準では、フレキシブル配列メンバが C 言語で提供されている。フレキシブル配列メンバは有用だが、十分に理解し注意して使用する必要がある。

以下は、フレキシブル配列メンバを含む構造の例である。

struct flexArrayStruct {
  int num;
  int data[];
};

この定義では、記憶域の割り当て時に最初のメンバ、num のみが考慮される。このため、struct flexArrayStruct 型の変数のメンバ data にアクセスした結果は未定義である。「DCL38-C. フレキシブル配列メンバには正しい構文を使用する」に、フレキシブル配列メンバを使用して struct を宣言する正しい方法が説明されている。

未定義の動作となる可能性を避けるため、フレキシブル配列メンバを含む構造は、常に動的に割り当てて操作する必要がある。

違反コード (記憶域割り当て)

以下のコード例では、フレキシブル配列メンバを含む構造の記憶域を静的に割り当てている。

struct flexArrayStruct flexStruct;
size_t array_size;
size_t i;

/* array_size を初期化 */

/* 構造を初期化 */
flexStruct.num = 0;

for (i = 0; i < array_size; i++) {
  flexStruct.data[i] = 0;
}

このコードの問題は、flexArrayStruct が実際には整数配列データの記憶域を予約しない点である。サイズが指定されていないからである。このため、num メンバのゼロへの初期化が許可されていても、データに 1 つの値でも (つまり、data[0]) を書き込み試行するとオブジェクトの制限を超えてメモリが上書きされる可能性がある。

適合コード (記憶域割り当て)

この適合コードは、struct flexArrayStruct の記憶域を動的に割り当てる。

struct flexArrayStruct *flexStruct;
size_t array_size;
size_t i;

/* array_size を初期化 */

/* 構造のメモリを動的に割り当てる */
flexStruct = (struct flexArrayStruct *)malloc(
  sizeof(struct flexArrayStruct) + sizeof(int) * array_size
);
if (flexStruct == NULL) {
  /* malloc エラーの処理 */
}

/* 構造を初期化 */
flexStruct->num = 0;

for (i = 0; i < array_size; i++) {
  flexStruct->data[i] = 0;
}

C 標準 [ISO/IEC 9899:2011] セクション 6.7.2.1、第 18 パラグラフで説明されている通り、flexStructdata[] メンバにアクセスできる。

違反コード (コピー)

以下のコード例では、割り当てによって、フレキシブル配列メンバ (struct flexArrayStruct) を含む構造のインスタンスをコピーしようとしている。

struct flexArrayStruct *flexStructA;
struct flexArrayStruct *flexStructB;
size_t array_size;
size_t i;

/* array_size を初期化 */

/* flexStructA のメモリを割り当てる */

/* flexStructB のメモリを割り当てる */

/* flexStructA を初期化 */

/* ... */

*flexStructB = *flexStructA;

このコード例の問題は、構造のコピー時にフレキシブル配列メンバのサイズが考慮されておらず、構造の最初のメンバ、num のみがコピーされる点である。

適合コード (コピー)

この適合コードでは、memcpy() を使用して flexStructA の内容を flexStructB に適切にコピーしている。

struct flexArrayStruct *flexStructA;
struct flexArrayStruct *flexStructB;
size_t array_size;
size_t i;

/* array_size を初期化 */

/* flexStructA のメモリを割り当てる */

/* flexStructB のメモリを割り当てる */

/* flexStructA を初期化 */

/* ... */

memcpy(
  flexStructB, 
  flexStructA, 
  (sizeof(struct flexArrayStruct) + sizeof(int) * array_size)
);

この適合コードは、フレキシブル配列メンバを含む構造全体が正しくコピーされることを保証している。

違反コード (関数の引数)

以下のコード例では、フレキシブル配列構造が、配列要素の出力を試行する関数に直接渡される。

void print_array(struct flexArrayStruct structP) {
  size_t i;

  puts("Array is: ");
  for (i = 0; i < structP.num; i++) {
    printf("%d", structP.data[i]);
  }
  puts("\n");
}

struct flexArrayStruct *structP;
size_t array_size;
size_t i;

/* array_size を初期化 */

/* 構造体のためのメモリ領域を割り当て */
structP = (struct flexArrayStruct *)malloc(
  sizeof(struct flexArrayStruct) + sizeof(int) * array_size
);
if (structP == NULL) {
  /* malloc エラーの処理 */
}

structP->num = array_size;

for (i = 0; i < array_size; i++) {
  structP->data[i] = i;
}

print_array(*structP);

C は引数を値によって渡すため、構造はスタックにコピーされる。構造のコピー時にフレキシブル配列メンバのサイズが考慮されておらず、構造の最初のメンバ、num だけがコピーされる。

適合コード (関数の引数)

この適合コードでは、print_array() 関数が、構造自体ではなく構造を指すポインタを受け入れている。

void print_array(struct flexArrayStruct *structP) {
  size_t i;

  puts("Array is: ");
  for (i = 0; i < structP->num; i++) {
    printf("%d", structP->data[i]);
  }
  puts("\n");
}

struct flexArrayStruct *structP;
size_t array_size;
size_t i;

/* array_size を初期化 */

/* 構造体のためのメモリ領域を割り当て */
structP = (struct flexArrayStruct *)malloc(
  sizeof(struct flexArrayStruct) + sizeof(int) * array_size
);
if (structP == NULL) {
  /* malloc エラーの処理 */
}

structP->num = array_size;

for (i = 0; i < array_size; i++) {
  structP->data[i] = i;
}

print_array(structP);
リスク評価

フレキシブル配列メンバを持つ構造を正しく使用できないと、未定義の動作となる。

ルール

深刻度

可能性

修正コスト

優先度

レベル

MEM33-C

P3

L3

自動検出

フレキシブル配列 struct は以下のいずれにも該当してはならない。

ツール

バージョン

チェッカー

説明

ROSE

 

 

これらのすべてを検出できる。

参考資料
[ISO/IEC 9899:2011] Section 6.7.2.1, "Structure and Union Specifiers"
[JTC1/SC22/WG14 N791]  
翻訳元

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

MEM33-C. Allocate and copy structures containing flexible array members dynamically (revision 74)

Top へ

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