CERT C++ Secure Coding Guidelines
Software vulnerabilities typically cost organizations an average of $300,000 per security incident. Efforts aimed at eliminating software vulnerabilities must focus on secure coding, preventing the vulnerabilities from being deployed into production code. "Between 2010 and 2015, buffer overflows accounted for between 10-16% of publicly reported security vulnerabilities in the U.S. National Vulnerability Database each year," Microsoft researcher David Tarditi wrote in a recent report. In March, the Secure Coding Team in the SEI's CERT Division published the 2016 edition of our SEI CERT C++ Coding Standard and made it freely available for download. In this blog post I will highlight some distinctive rules from the standard.
Our goal with the SEI CERT Coding Standards--in addition to C++, we also have Secure Coding Standards for Android, C, Java and Perl--is to enable developers to produce safe, reliable, and secure systems. One way this goal can be accomplished is by eliminating undefined behaviors that can lead to unexpected program behavior and exploitable vulnerabilities. Conformance to the coding rules defined in the standard is necessary (but not sufficient) to ensure the safety, reliability, and security of software systems developed in the C++ programming language. It is also necessary, for example, to have a safe and secure software design.
As outlined in the SEI CERT C++ Coding Standard, safety-critical systems typically have stricter requirements than are imposed by this standard, requiring that all memory be statically allocated, for example. The application of this standard, however, will result in high-quality systems that are reliable, robust, and resistant to attack.
The standard currently consists of more than 80 rules divided into 11 chapters. Each rule consists of a title, a description, non-compliant code examples, compliant solutions, a Risk Assessment section, related rules both internal to CERT and external, and a small bibliography. In-line links to terms go to an alphabetized glossary and bibliography for the SEI CERT C++ Coding Standard.
In this blog post, I will showcase three rules, illustrating what they require with code examples, and comparing them to analogous rules in C (if any).
Closing Files (FIO51-CPP)
Let us examine rule FIO51-CPP. Close files when they are no longer needed. This rule is a C++ analogue of C rule FIO42-C. That is, both rules give the same advice. The C++ rule exists because C++ provides a technique to simplify compliance, as follows:
Consider the following noncompliant code:
In this noncompliant code example, a
file is constructed. The constructor for
std::basic_filebuf<T>::open(), which leaves the file open when
std::terminate() is invoked, terminating the program without performing any cleanup. Consequently, the underlying
std::basic_filebuf<T> object maintained by the file object is not properly closed.
In the following compliant code,
std::fstream::close() is called before
std::terminate() is called, ensuring that the file resources are properly closed:
Of course, this technique is common in C, and is advocated by the analogous C rule FIO42-C. C++ also supports RAII (Resource Acquisition Is Initialization), however, which provides a simpler approach:
In this compliant code, the stream is implicitly closed when the block containing its declaration is exited. This happens before std::terminate() is called, ensuring that the file resources are properly closed.
C-style Variadic Functions (DCL50-CPP)
We next turn to rule DCL50-CPP. Do not define a C-style variadic function. This rule countermands a C feature: The CERT C recommendation DCL11-C. Understand the type issues associated with variadic functions offers guidance on defining variadic functions but does not forbid them. DCL50-CPP offers background on why variadic functions are forbidden in C++, and suggests some safer techniques to achieve the same effect. Thus, this rule presents another good example of why C++ makes programs safer and is "a better C".
As a quick example, in contrast to C's
printf() family of (type-unsafe) variadic functions, output in C++ is implemented with the overloaded single-argument
std::cout::operator<<() operators from the
As a more illustrative example, consider the following noncompliant code:
This noncompliant code example defines a C-style variadic function to add a list of integers together. The function reads arguments, ending when the value 0 is found. Calling this function without passing the value 0 as an argument (after the first two arguments) results in undefined behavior. Furthermore, passing any type other than an
int also results in undefined behavior:
C++ provides several safer techniques to provide this functionality. Consider this compliant code:
This compliant code provides a variadic function using a function parameter pack to implement the add() function, allowing identical behavior for call sites. Unlike the C-style variadic function used in the noncompliant code example, this compliant solution does not result in undefined behavior if the list of parameters is not terminated with 0. Additionally, if any values passed to the function are not integers, the code is ill-formed rather than producing undefined behavior. Ill-formed code is always caught by the compiler, thus warning the developer of the problem and preventing the deployment of insecure code.
This rule does allow for several exceptional cases: It allows the definition of a C-style variadic function if that function also has external C language linkage. For instance, the function may be a definition used in a C library API that is implemented in C++. Also, C-style variadic functions that are declared but never defined are permitted. For example, when a function call expression appears in an unevaluated context, such as the argument in a
sizeof expression, overload resolution is performed to determine the result type of the call but does not require a function definition.
Lambda Captured Object Lifetimes (EXP61-CPP)
The final rule that I will highlight is EXP61-CPP. A lambda object must not outlive any of its reference captured objects. This rule, mentioned in a previous blog post on this standard, has no analogue in other SEI CERT coding standards, because C++ is the only language that both provides lambdas and lacks memory safety. Lambdas were introduced in the 2011 edition of the ISO C++ standard (C++11).
This rule is a specific instance of CERT C++ rule EXP54-CPP. Do not access an object outside of its lifetime; it specifically applies that rule to lambda objects. To illustrate this rule, consider the following noncompliant code:
Here, the function g() returns a lambda, which implicitly captures the automatic local variable i by reference (using [&]). When that lambda is returned from the call to g(), the reference it captured will refer to the local variable i whose lifetime has already ended. Consequently, when the lambda is executed in f(), the use of the dangling reference to i in the lambda results in undefined behavior. On many platforms, the memory representing i on the stack will have been freed, and possibly re-used for other purposes. Consequently, trying to read that memory will produce unexpected results, including a potential memory fault.
In the following compliant code, the lambda captures i by copy (using [=]) instead of by reference. Consequently, the lambda contains an implicit non-static data member whose lifetime is that of the lambda.
Wrapping Up and Looking Ahead
The creation of the SEI CERT C++ Coding Standard was an important first step to eliminating coding errors that lead to vulnerabilities in C++ programs. This work would not be possible without the help of the wider secure coding community. The latest draft version of our C++ standard is, as always, publicly available on the CERT Secure Coding wiki. Feedback on each rule can be provided in the comments section at the bottom of each page. As with past standards, all constructive contributors are recognized in the published standard. We are grateful for the feedback and dedication of our public contributors.