Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The C standard library macro va_start() defines several semantic restrictions on the type of the value of its second parameter. The C Standard, subclause 7.16.1.4, paragraph 4 [ISO/IEC 9899:2011], states:

...

  • You must not pass a reference type as the second argument to va_start().
  • Passing an object of a nontrivially copyable type class type which has a nontrivial copy constructor, nontrivial move constructor, or nontrivial destructor as the second argument to va_start is conditionally supported with implementation-defined semantics ([expr.call] paragraph 7).
  • You may pass a parameter declared with the register keyword ([dcl.stc] paragraph 3) or a parameter with a function type.

Passing an object of array type still produces undefined behavior in C++ because an array type as a function parameter requires use of a reference, which is prohibited. Additionally, passing an object of a type that undergoes default argument promotions still produces undefined behavior in C++ as well.

Noncompliant Code Example

In this noncompliant code example, the object passed to va_start() will undergo a default argument promotion, which results in undefined behavior:

Code Block
bgColor#FFcccc
langcpp
#include <cstdarg>
 
void f(float a, ...) {
  va_list list;
  va_start(list, a);
  // ...
  va_end(list);
}

Compliant Solution

In this compliant solution, f() accepts a double instead of a float:

Code Block
bgColor#ccccff
langcpp
#include <cstdarg>
 
void f(double a, ...) {
  va_list list;
  va_start(list, a);
  // ...
  va_end(list);
}

Noncompliant Code Example

In this noncompliant code example, a reference type is passed as the second argument to va_start():

Code Block
bgColor#FFcccc
langcpp
#include <cstdarg>
#include <iostream>
 
void f(int &a, ...) {
  va_list list;
  va_start(list, a);
  if (a) {
    std::cout << a << ", " << va_arg(list, int);
    a = 100; // Assign something to a for the caller
  }
  va_end(list);
}

Compliant Solution

Instead of passing a reference type to f(), this compliant solution passes a pointer type:

Code Block
bgColor#ccccff
langcpp
#include <cstdarg>
#include <iostream>
 
void f(int *a, ...) {
  va_list list;
  va_start(list, a);
  if (a && *a) {
    std::cout << a << ", " << va_arg(list, int);
    *a = 100; // Assign something to *a for the caller
  }
  va_end(list);
}

Noncompliant Code Example

In this noncompliant code example, a nontrivially copyable type is passed as the second argument to va_start(), which is conditionally supported depending on the implementation:

Code Block
bgColor#FFcccc
langcpp
#include <cstdarg>
#include <iostream>
#include <string>
 
void f(std::string s, ...) {
  va_list list;
  va_start(list, s);
  std::cout << s << ", " << va_arg(list, int);
  va_end(list);
}

Compliant Solution

This compliant solution passes a const char * instead of a std::string, which has well-defined behavior on all implementations:

Code Block
bgColor#ccccff
langcpp
#include <cstdarg>
#include <iostream>
 
void f(const char *s, ...) {
  va_list list;
  va_start(list, a);
  std::cout << (s ? s : "") << ", " << va_arg(list, int);
  va_end(list);
}

Risk Assessment

Passing a reference type or nontrivially copyable an object of an unsupported type as the second argument to va_start() can result in undefined behavior that might be exploited to cause data integrity violations.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

EXP58-CPP

Medium

Unlikely

Medium

P4

L3

Automated Detection

Tool

Version

Checker

Description

Clang
Include Page
Clang_V
Clang_V
-WvarargsDoes not catch all instances of this rule, such as the violation in the second noncompliant code example

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Bibliography

[ISO/IEC 9899:2011]Subclause 7.16.1.4, "The va_start Macro"
[ISO/IEC 14882-2014]Subclause 18.10, "Other Runtime Support"

...