VA.LIST.INDETERMINATE

Do not call va_arg() on a va_list that has an indeterminate value

The VA.LIST.INDETERMINATE checker flags instances where code causes the va_list object to have an indeterminate value.

Vulnerability and risk

The value of a va_list object can become indeterminate if it’s passed to another function and va_arg has been called within that function. If the va_list object with an indeterminate value is used again, there is a risk of unexpected behavior.

Mitigation and prevention

It’s better to create a pointer to a va_list object and pass that to another function so that the original function can use the original list when the other function returns.

Vulnerable code example

Copy
  #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;
 }

In this non-compliant code example, Klocwork reports a VA.LIST.INDETERMINATE error on line 25, indicating, “Attempt to call va_arg() on a va_list that has an indeterminate value”. The va_list object ‘ap’ is passed to the contains_zero function at line 19. Because va_arg is invoked with the object at line 7, va_list ‘ap’ is indeterminate. Further, the va_list object ‘ap’ with an indeterminate value has been used again at line 25, which is undefined behavior.

Fixed code example

Copy
  #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;
 }

In this fixed example, the pointer to va_list has been passed to the contains_zero() function. After this function returns, the original function is able to traverse the original list. There will be no undefined behavior.