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

EXP54-CPP. Do not access an object outside of its lifetime

MEM52-CPP. Detect and handle memory allocation errors

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"
Excerpt from SEI CERT C++ Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-cpp-coding-standard/rules/memory-management-mem/mem50-cpp], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

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

functions_with_ignored_deallocators

functions_with_ignored_deallocators : set[str] = set()

Set of functions (given by their qualified name) where all deallocators are ignored. For these functions, the check will never report a use-after-free. It will also assume that these functions never create freed pointers, neither by return value, out param, nor by modifying global state.
 

report_freed_this_at_call

report_freed_this_at_call : bool = False

This option controls findings when a freed pointer is used in C++ to call a non-static member function. When set to true, the use at the call is directly reported. When false, the analysis waits for an actual dereference (of the this-pointer then) inside the callee, and only reports those.
 

report_read_pointer_args_in_calls_to_undefined

report_read_pointer_args_in_calls_to_undefined : bool = True

Report when freed pointers are passed to undefined (external) functions.
 

resources

resources

Type: set[str]

Default: {'C++ArrayHeapMemory', 'C++HeapMemory', 'CudaAsyncMemory', 'CudaDeviceMemory', 'CudaDriverAsyncMemory', 'CudaHostMemory', 'CudaManagedMemory', 'FileHandle', 'HeapMemory', 'UniquePtrHeapMemory'}

Set of resources to be checked (selection of rules in the Resources group).
 

witness_paths

witness_paths : bool = True

Whether witness paths should be determined and included in the issue.