If a function is reentered during the initialization of a static object inside that function, the behavior of the program is undefined. Please note that this problem is not the same as infinite recursion. For this problem to occur, a function need only recur once.
The C++ Standard, [stmt.dcl], paragraph 4, states [ISO/IEC 14882-2003] Section 6.7, "Declaration Statement" describes the initialization of static and thread storage duration objects. The direct quote is as follows2014]:
The zero-initialization (8.5) of all local objects block-scope variables with static storage duration or thread storage duration (3.7.1) or thread storage duration (3.7.2) is performed before any other initialization takes place. Constant initialization of a local (3.6.2) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. An An implementation is permitted to perform early initialization of other local objects block-scope variables with static or thread or thread storage duration under the same conditions that an implementation is permitted to statically initialize an object initialize a variable with static or thread storage duration in namespace scope (3.6.2). Otherwise such an object is initialized a variable is initialized the first time control passes through its declaration; such an object a variable is considered initialized upon the upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the enters the declaration concurrently while the object variable is being initialized, the concurrent execution shall wait for completion for completion of the initialization. If If control re-enters the declaration recursively while the object variable is being initializedbeing initialized, the behavior is undefined.
Noncompliant Code Example
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. Since the initialization of the static local array cache
involves recursion, the behavior of the function is undefined, even though the recursion is not infiniteThis noncompliant code example declares the variable y
as a static int. The value of test( x)
is assigned to y
within the test(int x)
function. However, when test(int x)
is called with an input that results in reaching the initialization of y
more than once, such as the value 12, undefined behavior occurs. Note that this code does not present an infinite recursion and still causes the undefined behavior mentioned.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdexcept> int test fact(int x i) noexcept(false) { x--; if (xi < 0 || x > 10) { return 0) { // Negative factorials are undefined. throw std::domain_error("i must be >= 0"); } else static const int cache[] = { static int y = test(x); //<--undefined behavior occurs herefact(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 ycache[i]; } return i > 0 ? i * fact(i - 1) : 1; } |
Implementation
...
Details
In the GCC Version 3 compiler, this code will recur as if y
were a non-static variable.In the GCC Version 4 compiler, upon 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 y
cache
for the second time, the program will terminate with the following message:
Code Block |
---|
terminate called after throwing an instance of '__gnu_cxx::recursive_init_error' what(): N9__gnu_cxx14recursive_initE Aborted (core dumped)std::exception |
Compliant Solution
In this compliant solution, y
is declared before being assigned a value. According to [ISO/IEC 14882-2003] Section 6.7.4, the initialization of y
will have been completed at the end of the declaration and before the assignment of a value, consequently removing the possibility of undefined behaviorThis 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 computes its value. It then returns the cached value when possible, or computes the value as needed.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdexcept> int test fact(int x i) noexcept(false) { x--; if (xi < 0 || x > 10) {) { // Negative factorials are undefined. returnthrow std::domain_error("i must be >= 0"); } // elseUse { the lazy-initialized cache. static int y; cache[17]; if (i < (sizeof(cache) / sizeof(int))) { if(0 == cache[i]) { ycache[i] = test(x); i > 0 ? i * fact(i - 1) : 1; } return ycache[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 |
---|---|---|---|---|---|
DCL38-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.
Related Guidelines
Bibliography
...
2014] |
...
6.7, "Declaration Statement" |