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

1  #include <stdarg.h>
2  #include <stdio.h>
3 
4  int contains_zero(size_t count, va_list ap)
5  {
6      for (size_t i = 1; i < count; ++i) {
7          if (va_arg(ap, double) == 0.0) {
8              return 1;
9          }
10     }
11     return 0;
12 }
13
14 int print_reciprocals(size_t count, ...)
15 {
16     va_list ap;
17     va_start(ap, count);
18
19     if (contains_zero(count, ap)) {
20         va_end(ap);
21         return 1;
22     }
23
24     for (size_t i = 0; i < count; ++i) {
25         printf("%f ", 1.0 / va_arg(ap,double));  
26     }
27
28     va_end(ap);
29     return 0;
30 }

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

1  #include <stdarg.h>
2  #include <stdio.h>
3 
4  int contains_zero(size_t count, va_list *ap)
5  {
6      va_list ap1;
7      va_copy(ap1, *ap);
8      for (size_t i = 1; i < count; ++i) {
9          if (va_arg(ap1, double) == 0.0) {
10             return 1;
11         }
12     }
13     va_end(ap1);
14     return 0;
15 }
16
17 int print_reciprocals(size_t count, ...)
18 {
19     int status;
20     va_list ap;
21     va_start(ap, count);
22
23     if (contains_zero(count, &ap)) {
24         printf("0 in arguments!\n");
25         status = 1;
26     } else {
27         for (size_t i = 0; i < count; i++) {
28             printf("%f ", 1.0 / va_arg(ap, double)); 
29         }
30         printf("\n");
31         status = 0;
32     }
33
34     va_end(ap);
35     return status;
36 }

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.