The C++ Standard, [stmt.dcl], paragraph 4 [ISO/IEC 14882-2014], states:
The zero-initialization (8.5) of all block-scope variables with static storage duration (3.7.1) or thread storage duration (3.7.2) is performed before any other initialization takes place. Constant initialization (3.6.2) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. An implementation is permitted to perform early initialization of other block-scope variables with static or thread storage duration under the same conditions that an implementation is permitted to statically initialize a variable with static or thread storage duration in namespace scope (3.6.2). Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.
Do not reenter a function during the initialization of a static variable declaration. If a function is reentered during the constant initialization of a static object inside that function, the behavior of the program is undefined. Infinite recursion is not required to trigger undefined behavior, the function need only recur once as part of the initialization.
Noncompliant Code Example
This noncompliant example attempts to implement an efficient factorial function using caching. Because the initialization of the static local array cache
involves recursion, the behavior of the function is undefined, even though the recursion is not infinite.
#include <stdexcept> int fact(int i) noexcept(false) { if (i < 0) { // Negative factorials are undefined. throw std::domain_error("i must be >= 0"); } static const int cache[] = { fact(0), fact(1), fact(2), fact(3), fact(4), fact(5), fact(6), fact(7), fact(8), fact(9), fact(10), fact(11), fact(12), fact(13), fact(14), fact(15), fact(16) }; if (i < (sizeof(cache) / sizeof(int))) { return cache[i]; } return i > 0 ? i * fact(i - 1) : 1; }
Implementation Details
In Microsoft Visual Studio 2013, the cache
array is zero-initialized as though the static initialization did not occur. In GCC 4.8.1, upon reaching the initialization of cache
for the second time, the program will terminate with the following message:
terminate called after throwing an instance of '__gnu_cxx::recursive_init_error' what(): std::exception
Compliant Solution
This compliant solution avoids initializing the static local array cache
and instead relies on zero-initialization to determine whether each member of the array has been assigned a value yet and, if not, recursively compute its value. It then returns the cached value when possible or computes the value as needed.
#include <stdexcept> int fact(int i) noexcept(false) { if (i < 0) { // Negative factorials are undefined. throw std::domain_error("i must be >= 0"); } // Use the lazy-initialized cache. static int cache[17]; if (i < (sizeof(cache) / sizeof(int))) { if(0 == cache[i]) { cache[i] = i > 0 ? i * fact(i - 1) : 1; } return cache[i]; } return i > 0 ? i * fact(i - 1) : 1; }
Risk Assessment
Recursively reentering a function during the initialization of one of its static objects can result in an attacker being able to cause a crash or denial of service.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
DCL56-CPP | Low | Unlikely | Medium | P2 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Bibliography
[ISO/IEC 14882-2014] | Subclause 6.7, "Declaration Statement" |