CertC++-MEM52¶
Detect and handle memory allocation errors
Required inputs: IR, StaticSemanticAnalysis
The default memory allocation operator,
::operator new(std::size_t), throws a
std::bad_alloc exception if the allocation fails. Therefore, you
need not check whether calling
::operator new(std::size_t) results in nullptr. The nonthrowing form,
::operator new(std::size_t, const std::nothrow_t &), does not
throw an exception if the allocation fails but instead returns
nullptr. The same behaviors apply for the
operator new[] versions of both allocation functions.
Additionally, the default allocator object (
std::allocator) uses
::operator new(std::size_t) to perform allocations and should be
treated similarly.
T *p1 = new T; // Throws std::bad_alloc if allocation fails T *p2 = new (std::nothrow) T; // Returns nullptr if allocation fails T *p3 = new T[1]; // Throws std::bad_alloc if the allocation fails T *p4 = new (std::nothrow) T[1]; // Returns nullptr if the allocation fails
Furthermore,
operator new[] can throw an error of type
std::bad_array_new_length, a subclass of
std::bad_alloc, if the
size argument passed to
new is negative or excessively large.
When using the nonthrowing form, it is imperative to check that the return
value is not
nullptr before accessing the resulting pointer. When using either
form, be sure to comply with
ERR50-CPP.
Do not abruptly terminate the program.
Noncompliant Code Example
In this noncompliant code example, an array of
int is created using
::operator new[](std::size_t) and the results of the
allocation are not checked. The function is marked as
noexcept, so the caller assumes this function does not throw any
exceptions. Because
::operator new[](std::size_t) can throw an exception if the
allocation fails, it could lead to
abnormal
termination of the program.
#include <cstring>
void f(const int *array, std::size_t size) noexcept {
int *copy = new int[size];
std::memcpy(copy, array, size * sizeof(*copy));
// ...
delete [] copy;
}
Compliant Solution (
std::nothrow)
When using
std::nothrow, the
new operator returns either a null pointer or a pointer to the
allocated space. Always test the returned pointer to ensure it is not
nullptr before referencing the pointer. This compliant
solution handles the error condition appropriately when the returned pointer is
nullptr.
#include <cstring>
#include <new>
void f(const int *array, std::size_t size) noexcept {
int *copy = new (std::nothrow) int[size];
if (!copy) {
// Handle error
return;
}
std::memcpy(copy, array, size * sizeof(*copy));
// ...
delete [] copy;
}
Compliant Solution (
std::bad_alloc)
Alternatively, you can use
::operator new[] without
std::nothrow and instead catch a
std::bad_alloc exception if sufficient memory cannot be allocated.
#include <cstring>
#include <new>
void f(const int *array, std::size_t size) noexcept {
int *copy;
try {
copy = new int[size];
} catch(std::bad_alloc) {
// Handle error
return;
}
// At this point, copy has been initialized to allocated memory
std::memcpy(copy, array, size * sizeof(*copy));
// ...
delete [] copy;
}
Compliant Solution (noexcept(false))
If the design of the function is such that the caller is expected to handle
exceptional situations, it is permissible to mark the function explicitly as
one that may throw, as in this compliant solution. Marking the function is not
strictly required, as any function without a
noexcept specifier is presumed to allow throwing.
#include <cstring>
void f(const int *array, std::size_t size) noexcept(false) {
int *copy = new int[size];
// If the allocation fails, it will throw an exception which the caller
// will have to handle.
std::memcpy(copy, array, size * sizeof(*copy));
// ...
delete [] copy;
}
Noncompliant Code Example
In this noncompliant code example, two memory allocations are performed within
the same expression. Because the memory allocations are passed as arguments to
a function call, an exception thrown as a result of one of the calls to
new could result in a memory leak.
struct A { /* ... */ };
struct B { /* ... */ };
void g(A *, B *);
void f() {
g(new A, new B);
}
Consider the situation in which
A is allocated and constructed first, and then
B is allocated and throws an exception. Wrapping the call to
g() in a
try/
catch block is insufficient because it would be impossible to free
the memory allocated for
A.
This noncompliant code example also violates
EXP50-CPP.
Do not depend on the order of evaluation for side effects, because
the order in which the arguments to
g() are evaluated is unspecified.
Compliant Solution (
std::unique_ptr)
In this compliant solution, a
std::unique_ptr is used to manage the resources for the
A and
B objects with
RAII.
In the situation described by the noncompliant code example,
B throwing an exception would still result in the destruction and
deallocation of the
A object when then
std::unique_ptr<A> was destroyed.
#include <memory>
struct A { /* ... */ };
struct B { /* ... */ };
void g(std::unique_ptr<A> a, std::unique_ptr<B> b);
void f() {
g(std::make_unique<A>(), std::make_unique<B>());
}
Compliant Solution (References)
When possible, the more resilient compliant solution is to remove the memory allocation entirely and pass the objects by reference instead.
struct A { /* ... */ };
struct B { /* ... */ };
void g(A &a, B &b);
void f() {
A a;
B b;
g(a, b);
}
Risk Assessment
Failing to detect allocation failures can lead to abnormal program termination and denial-of-service attacks.
If the vulnerable program references memory offset from the return value, an attacker can exploit the program to read or write arbitrary memory. This vulnerability has been used to execute arbitrary code [ VU#159523].
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| MEM52-CPP | High | Likely | Medium | P18 | L1 |
Related Guidelines
| SEI CERT C Coding Standard | ERR33-C. Detect and handle standard library errors |
| MITRE CWE |
CWE 252, Unchecked Return Value CWE 391, Unchecked Error Condition CWE 476, NULL Pointer Dereference CWE 690, Unchecked Return Value to NULL Pointer Dereference CWE 703, Improper Check or Handling of Exceptional Conditions CWE 754, Improper Check for Unusual or Exceptional Conditions |
Bibliography
| [ ISO/IEC 9899:2011] | Subclause 7.20.3, "Memory Management Functions" |
| [ ISO/IEC 14882-2014] | Subclause 18.6.1.1, "Single-Object Forms" Subclause 18.6.1.2, "Array Forms" Subclause 20.7.9.1, "Allocator Members" |
| [ Meyers 1996] | Item 7, "Be Prepared for Out-of-Memory Conditions" |
| [ Seacord 2013] | Chapter 4, "Dynamic Memory Management" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
multiple_allocations |
Multiple allocations in a single expression. |
None |
False |
unchecked_bad_alloc |
Routine might throw uncaught std::bad_alloc. |
None |
False |
unchecked_malloc |
Result of call to malloc, calloc, or realloc is not checked. |
None |
False |
unchecked_new |
Result of call to non-throwing operator new is not checked. |
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
check_nothrow_new¶
check_nothrow_new : bool = True
null_check_macro¶
null_check_macro : bauhaus.analysis.config.MacroName = ''
NULL.