VA.LIST.INDETERMINATE

不定値がある va_list で va_arg() を呼び出さない

VA.LIST.INDETERMINATE チェッカーは、コードが va_list オブジェクトに不定値があるようにしているインスタンスにフラグを立てます。

脆弱性とリスク

va_list オブジェクトの値は、それが別の関数に渡され、その関数内で va_arg が呼び出されている場合に、不定になる可能性があります。不定値がある va_list オブジェクトを再度使用すると、予期しない動作が発生するリスクがあります。

軽減と防止

va_list オブジェクトへのポインターを作成し、それを別の関数に渡す方が良いでしょう。それにより、他の関数が戻るときに、元の関数は元のリストを使用できます。

脆弱コード例

コピー
  #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;
 }

この準拠していないコードの例では、Klocwork は 25 行目で VA.LIST.INDETERMINATE エラーを報告しており、「不定値がある va_list で va_arg() を呼び出そうとしています」を示しています。va_list オブジェクト「ap」は、19 行目で contains_zero 関数に渡されます。va_arg は 7 行目でオブジェクトと共に呼び出されるため、va_list の「ap」は不定です。さらに、不定値がある va_list オブジェクト「ap」が 25 行目で再び使用されています。これは未定義の動作です。

修正コード例

コピー
  #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 へのポインターが contains_zero() 関数に渡されています。この関数が戻った後に、元の関数は元のリストをトラバースできます。未定義の動作はありません。