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

MSC39-C. 値が不定の va_list に対して va_arg() を呼び出さない

可変引数関数はその可変引数にアクセスするとき、va_start() を使って va_list 型のオブジェクトを初期化し、va_arg() マクロを反復的に呼び出す。そして最後に va_end() を呼び出す。va_list は別の関数に引数として渡してもよいが、別の関数の中で va_arg() が呼び出されると、呼出し側の関数において va_list の値が 不定となる。そのため、va_list を再初期化せずに可変引数を読み取ろうとすると、予期せぬ動作を引き起こす可能性がある。C 標準、7.16節、第3パラグラフには次のように規定されている[ISO/IEC 9899:2011]。

可変個数の実引数へのアクセスを必要とするとき、呼び出された関数は、型 va_list をもつオブジェクト(この箇条では ap として参照する。)を宣言しなければならない。オブジェクト ap は、他の関数への実引数として渡してもよい。その関数が仮引数 ap を用いて va_arg マクロを呼び出した場合、呼出し側関数での ap の値は、不定となり、ap に対するすべての参照に先立って va_end マクロに渡さなければならない。253

253) va_list へのポインタを作り、そのポインタを他の関数に渡してもよい。その場合、元の関数は、va_list へのポインタを渡した関数から復帰した後でも元の va_list を使い続けてもよい。

違反コード

次の違反コード例では、可変引数がいずれも 0 でないことを検査するために、va_list をヘルパー関数である contains_zero() に渡している。contains_zero() を呼び出した後、ap の値は 不定となる。

#include <stdarg.h>
#include <stdio.h>

int contains_zero(size_t count, va_list ap) {
  for (size_t i = 1; i < count; ++i) {
    if (va_arg(ap, double) == 0.0) {
      return 1;
    }
  }
  return 0;
}

int print_reciprocals(size_t count, ...) {
  va_list ap;
  va_start(ap, count);

  if (contains_zero(count, ap)) {
    va_end(ap);
    return 1;
  }

  for (size_t i = 0; i < count; ++i) {
    printf("%f ", 1.0 / va_arg(ap, double));
  }

  va_end(ap);
  return 0;
}
適合コード

次の適合コードでは、va_list へのポインタを引数として取るように contains_zero() を修正している。その上で、va_copy マクロを使用して va_list のコピーを作成し、コピーに対して処理を行っている。処理が終ったら、作成したコピーに対して va_end マクロによる後処理を行っている。こうすることで、print_reciprocals 関数は、元の va_list に自由にアクセスすることができる。

#include <stdarg.h>
#include <stdio.h>

int contains_zero(size_t count, va_list *ap) {
  va_list ap1;
  va_copy(ap1, *ap);
  for (size_t i = 1; i < count; ++i) {
    if (va_arg(ap1, double) == 0.0) {
      return 1;
    }
  }
  va_end(ap1);
  return 0;
}

int print_reciprocals(size_t count, ...) {
  int status;
  va_list ap;
  va_start(ap, count);

  if (contains_zero(count, &ap)) {
    printf("0 in arguments!\n");
    status = 1;
  } else {
    for (size_t i = 0; i < count; i++) {
      printf("%f ", 1.0 / va_arg(ap, double));
    }
    printf("\n");
    status = 0;
  }

  va_end(ap);
  return status;
}
リスク評価

値が不定の va_list を使って可変引数の読み取りを行うと、予期せぬ結果が生じる可能性がある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

MSC39-C

P3

L3

参考資料
[ISO/IEC 9899:2011] Subclause 7.16, "Variable Arguments <stdarg.h>"
翻訳元

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

MSC39-C. Do not call va_arg() on a va_list that has an indeterminate value (revision 27)

Top へ

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