CertC-MSC39ΒΆ
Do not call va_arg() on a va_list that has an indeterminate value
Required inputs: IR
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 the C Standard, 7.16, paragraph 3 [
ISO/IEC
9899:2011],
If access to the varying arguments is desired, the called function shall declare an object (generally referred to as
apin this subclause) having typeva_list. The objectapmay be passed as an argument to another function; if that function invokes theva_argmacro with parameterap, the value ofapin the calling function is indeterminate and shall be passed to theva_endmacro prior to any further reference toap.253
253) It is permitted to create a pointer to ava_listand pass that pointer to another function, in which case the original function may take further use of the original list after the other function returns.
Noncompliant Code Example
This noncompliant code example attempts to check that none of its variable
arguments are zero by passing a
va_list to helper function
contains_zero(). After the call to
contains_zero(), the value of
ap is
indeterminate.
#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;
}
Compliant Solution
The compliant solution modifies
contains_zero() to take a pointer to a
va_list. It then uses the
va_copy macro to make a copy of the list, traverses the copy, and
cleans it up. Consequently, the
print_reciprocals() function is free to traverse the
original
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;
}
Risk Assessment
Reading variable arguments using a
va_list that has an
indeterminate
value can have unexpected results.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| MSC39-C | Low | Unlikely | Low | P3 | L3 |
Bibliography
| [ ISO/IEC 9899:2011] | Subclause 7.16, "Variable Arguments
<stdarg.h>"
|
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
va_arg_with_undeterminate_value |
Call of va_arg with possibly indeterminate value. |
None |
False |
Options
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
This rule has no individual options.