CertC++-MEM50¶
Do not access freed memory
Required inputs: IR, StaticSemanticAnalysis
Evaluating a pointer-including dereferencing the pointer, using it as an operand of an arithmetic operation, type casting it, and using it as the right-hand side of an assignment-into memory that has been deallocated by a memory management function is undefined behavior. Pointers to memory that has been deallocated are called dangling pointers. Accessing a dangling pointer can result in exploitable vulnerabilities.
It is at the memory manager's discretion when to reallocate or recycle the freed memory. When memory is freed, all pointers into it become invalid, and its contents might either be returned to the operating system, making the freed space inaccessible, or remain intact and accessible. As a result, the data at the freed location can appear to be valid but change unexpectedly. Consequently, memory must not be written to or read from once it is freed.
Noncompliant Code Example (
new and
delete)
In this noncompliant code example,
s is dereferenced after it has been deallocated. If this access
results in a write-after-free, the
vulnerability
can be
exploited to
run arbitrary code with the permissions of the vulnerable
process. Typically, dynamic memory allocations and deallocations are far
removed, making it difficult to recognize and diagnose such problems.
#include <new>
struct S {
void f();
};
void g() noexcept(false) {
S *s = new S;
// ...
delete s;
// ...
s->f();
}
The function
g() is marked
noexcept(false) to comply with
MEM52-CPP.
Detect and handle memory allocation errors.
Compliant Solution (
new and
delete)
In this compliant solution, the dynamically allocated memory is not deallocated until it is no longer required.
#include <new>
struct S {
void f();
};
void g() noexcept(false) {
S *s = new S;
// ...
s->f();
delete s;
}
Compliant Solution (Automatic Storage Duration)
When possible, use automatic storage duration instead of dynamic storage
duration. Since
s is not required to live beyond the scope of
g(), this compliant solution uses automatic storage duration to
limit the lifetime of
s to the scope of
g().
struct S {
void f();
};
void g() {
S s;
// ...
s.f();
}
Noncompliant Code Example (
std::unique_ptr)
In the following noncompliant code example, the dynamically allocated memory
managed by the
buff object is accessed after it has been implicitly deallocated
by the object's destructor.
#include <iostream>
#include <memory>
#include <cstring>
int main(int argc, const char *argv[]) {
const char *s = "";
if (argc > 1) {
enum { BufferSize = 32 };
try {
std::unique_ptr<char[]> buff(new char[BufferSize]);
std::memset(buff.get(), 0, BufferSize);
// ...
s = std::strncpy(buff.get(), argv[1], BufferSize - 1);
} catch (std::bad_alloc &) {
// Handle error
}
}
std::cout << s << std::endl;
}
This code always creates a null-terminated byte string, despite its use
of
strncpy(), because it leaves the final
char in the buffer set to 0.
Compliant Solution (
std::unique_ptr)
In this compliant solution, the lifetime of the
buff object extends past the point at which the memory managed by
the object is accessed.
#include <iostream>
#include <memory>
#include <cstring>
int main(int argc, const char *argv[]) {
std::unique_ptr<char[]> buff;
const char *s = "";
if (argc > 1) {
enum { BufferSize = 32 };
try {
buff.reset(new char[BufferSize]);
std::memset(buff.get(), 0, BufferSize);
// ...
s = std::strncpy(buff.get(), argv[1], BufferSize - 1);
} catch (std::bad_alloc &) {
// Handle error
}
}
std::cout << s << std::endl;
}
Compliant Solution
In this compliant solution, a variable with automatic storage duration of
type
std::string is used in place of the
std::unique_ptr<char[]>, which reduces the complexity and
improves the security of the solution.
#include <iostream>
#include <string>
int main(int argc, const char *argv[]) {
std::string str;
if (argc > 1) {
str = argv[1];
}
std::cout << str << std::endl;
}
Noncompliant Code Example (
std::string::c_str())
In this noncompliant code example,
std::string::c_str() is being called on a temporary
std::string object. The resulting pointer will point to released
memory once the
std::string object is destroyed at the end of the assignment
expression, resulting in
undefined
behavior when accessing elements of that pointer.
#include <string>
std::string str_func();
void display_string(const char *);
void f() {
const char *str = str_func().c_str();
display_string(str); /* Undefined behavior */
}
Compliant solution (
std::string::c_str())
In this compliant solution, a local copy of the string returned by
str_func() is made to ensure that string
str will be valid when the call to
display_string() is made.
#include <string>
std::string str_func();
void display_string(const char *s);
void f() {
std::string str = str_func();
const char *cstr = str.c_str();
display_string(cstr); /* ok */
}
Noncompliant Code Example
In this noncompliant code example, an attempt is made to allocate zero bytes of
memory through a call to
operator new(). If this request succeeds,
operator new() is required to return a non-null pointer
value. However, according to the C++
Standard, [basic.stc.dynamic.allocation], paragraph 2 [
ISO/IEC
14882-2014], attempting to dereference memory through such a pointer
results in
undefined
behavior.
#include <new>
void f() noexcept(false) {
unsigned char *ptr = static_cast<unsigned char *>(::operator new(0));
*ptr = 0;
// ...
::operator delete(ptr);
}
Compliant Solution
The compliant solution depends on programmer intent. If the programmer intends
to allocate a single
unsigned char object, the compliant solution is to use
new instead of a direct call to
operator new(), as this compliant solution demonstrates.
void f() noexcept(false) {
unsigned char *ptr = new unsigned char;
*ptr = 0;
// ...
delete ptr;
}
Compliant Solution
If the programmer intends to allocate zero bytes of memory (perhaps to obtain a
unique pointer value that cannot be reused by any other pointer in the program
until it is properly released), then instead of attempting to dereference the
resulting pointer, the recommended solution is to declare
ptr as a
void *, which cannot be dereferenced by a
conforming
implementation.
#include <new>
void f() noexcept(false) {
void *ptr = ::operator new(0);
// ...
::operator delete(ptr);
}
Risk Assessment
Reading previously dynamically allocated memory after it has been deallocated can lead to abnormal program termination and denial-of-service attacks. Writing memory that has been deallocated can lead to the execution of arbitrary code with the permissions of the vulnerable process.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| MEM50-CPP | High | Likely | Medium | P18 | L1 |
Related Guidelines
| SEI CERT C++ Coding Standard | |
| SEI CERT C Coding Standard | MEM30-C. Do not access freed memory |
| MITRE CWE |
CWE-415, Double Free CWE-416, Use After Free |
Bibliography
| [ ISO/IEC 14882-2014] | Subclause 3.7.4.1, "Allocation Functions" Subclause 3.7.4.2, "Deallocation Functions" |
| [ Seacord 2013b] | Chapter 4, "Dynamic Memory Management" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
possible_use_after_free |
Dynamic memory possibly used after it was previously released |
None |
False |
use_after_free |
Dynamic memory used after it was previously released |
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
functions_with_ignored_deallocators¶
functions_with_ignored_deallocators : set[str] = set()
report_freed_this_at_call¶
report_freed_this_at_call : bool = False
report_read_pointer_args_in_calls_to_undefined¶
report_read_pointer_args_in_calls_to_undefined : bool = True
resources¶
resources
Set of resources to be checked (selection of rules in the Resources group).Type: set[str]
Default:
{'C++ArrayHeapMemory', 'C++HeapMemory', 'CudaAsyncMemory', 'CudaDeviceMemory', 'CudaDriverAsyncMemory', 'CudaHostMemory', 'CudaManagedMemory', 'FileHandle', 'HeapMemory', 'UniquePtrHeapMemory'}
witness_paths¶
witness_paths : bool = True