Variadic functions access their variable arguments by using va_start()
to initialize an object of type va_list
, iteratively invoking the va_arg()
macro, and finally calling va_end()
. The va_list
may be passed as an argument to another function, but calling va_arg()
within that function causes the va_list
to have an indeterminate value in the calling function. As a result, attempting to read variable arguments without reinitializing the va_list
can have unexpected behavior. According to C99 [[ISO/IEC 9899:1999]], section 7.15:
If access to the varying arguments is desired, the called function shall declare an object (generally referred to as ap in this subclause) having type va_list. The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.
Noncompliant Code Example
The following code tries to check that none of its variable arguments are zero by passing a va_list
to a helper function contains_zero()
. After the call to contains_zero()
, the value of ap
is indeterminate, which could potentially cause undesired behavior.
int contains_zero(int count, va_list ap){ int i; for(i=1;i<count;i++){ if(va_arg(ap, double) == 0.0) return 1; } return 0; } int print_reciprocals(int count, ...){ int i; va_list ap; va_start(ap, count); if(contains_zero(count, ap)){ va_end(ap); return 1; } for(i=0;i<count;i++) printf("%f ", 1.0/va_arg(ap, double)); va_end(ap); return 0; }
Compliant Solution
The compliant solution modifies print_reciprocals
to create a copy of the va_list
by using the va_copy
macro. The copy is passed to contains_zero
, and the variable arguments are accessed through the original.
int print_reciprocals(int count, ...) { int status; va_list ap1; va_list ap2 va_start(ap1, count); va_copy(ap2, ap1); if (contains_zero(count, ap2)) status = 1; else { for (int i = 0; i < count; i++) printf("%f ", 1.0 / va_arg (ap1, double)); status = 0; } va_end(ap2); va_end(ap1); return status; }
Risk Assessment
Reading variable arguments using a va_list
that has an indeterminate value could lead to unexpected results.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
MSC39-C |
low |
unlikely |
low |
P3 |
L3 |
References
[[ISO/IEC 9899:1999]] Section 7.15, "Variable Arguments".
MSC38-C. Do not treat as an object any predefined identifier that might be implemented as a macro 49. Miscellaneous (MSC) 50. POSIX (POS)