Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Calling std::unique_ptr::release() will relinquish ownership of the managed pointer value. Destruction of, move assignment of, or calling std::unique_ptr::reset() on a std::unique_ptr object will also relinquish ownership of the managed pointer value, but results in destruction of the managed pointer value. If a call to std::shared_ptr::unique() returns true, then destruction of, or calling std::shared_ptr::reset() on that std::shared_ptr object will relinquish ownership of the managed pointer value, but results in destruction of the managed pointer value.

Some smart pointers, such as std::shared_ptr, allow multiple smart pointer objects to manage the same underlying pointer value. In such cases, the initial smart pointer object owns the pointer value, and subsequent smart pointer objects are related to the original smart pointer. Two smart pointers are related when the initial smart pointer is used in the initialization of the subsequent smart pointer objects. For instance, copying a std::shared_ptr object to another std::shared_ptr object via copy assignment creates a relationship between the two smart pointers, while creating a std::shared_ptr object from the managed pointer value of another std::shared_ptr object does not.

Do not create an unrelated Do not create a smart pointer object with a pointer value is that owned by another smart pointer object. This includes resetting a smart pointer's managed pointer to an already-owned pointer value, such as by calling reset().Storing

Noncompliant Code Example

In this noncompliant code example, two unrelated smart pointers are constructed from the same underlying pointer value. When the local, automatic variable p2 is destroyed, it deletes the pointer value it manages. Then, when the local, automatic variable p1 is destroyed, it deletes the same pointer value in more than one smart pointer object simultaneously will eventually result in undefined behavior when the smart pointer attempts to destroy the managed pointer value., resulting in a double-free vulnerability.

Code Block
bgColor#FFcccc
langcpp
#include <memory>

void f() {
  int *i = new int;
  std::shared_ptr<int> p1(i);
  std::shared_ptr<int> p2(i);
}

Compliant Solution

In this compliant solution, the std::shared_ptr objects are related to one another through copy construction. When the local, automatic variable p2 is destroyed, the use count for the shared pointer value is decremented, but still nonzero. Then, when the local, automatic variable p1 is destroyed, the use count for the shared pointer value is decremented to zero, and the managed pointer is destroyed. This compliant solution also calls std::make_shared() instead of allocating a raw pointer and storing its value in a local variable.

Code Block
bgColor#ccccff
langcpp
#include <memory>

void f() {
  std::shared_ptr<int> p1 = std::make_shared<int>();
  std::shared_ptr<int> p2(p1);
}

Noncompliant Code Example

In this noncompliant code example, the B * pointer value owned by a std::shared_ptr object is cast to the D * pointer type with dynamic_cast in an attempt to obtain a std::shared_ptr of the polymorphic derived type. However, this eventually results in undefined behavior as the same pointer is stored in two different std::shared_ptr objects. When g() exits, the pointer stored in Derived is freed by the default deleter. Any further use of Poly results in accessing freed memory. When f() exits, the same pointer stored in Poly is destroyed, resulting in a double-free vulnerability.

Code Block
bgColor#FFcccc
langcpp
#include <memory>

struct B {
  virtual ~B() = default; // Polymorphic object
  // ...
};
struct D : B {};

void g(std::shared_ptr<D> Derived);

void f() {
  std::shared_ptr<B> Poly(new D);
  // ...
  g(std::shared_ptr<D>(dynamic_cast<D *>(Poly.get())));
  // Any use of Poly will now result in accessing freed memory.
}

Compliant Solution

In this compliant solution, the dynamic_cast is replaced with a call to std::dynamic_pointer_cast(), which returns a std::shared_ptr of the polymorphic type with the valid shared pointer value. When g() exits, the reference count to the underlying pointer is decremented by the destruction of Derived, but because of the reference held by Poly (within f()) the stored pointer value is still valid after g() returns.

Code Block
bgColor#ccccff
langcpp
#include <memory>

struct B {
  virtual ~B() = default; // Polymorphic object
  // ...
};
struct D : B {};

void g(std::shared_ptr<D> Derived);

void f() {
  std::shared_ptr<B> Poly(new D);
  // ...
  g(std::dynamic_pointer_cast<D, B>(Poly));
  // Poly is still referring to a valid pointer value.
}

Risk Assessment

Passing a pointer value to a deallocation function that was not previously obtained by the matching allocation function results in undefined behavior, which can lead to exploitable vulnerabilities.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MEM56-CPP

High

Likely

Medium

P18

L1

Automated Detection

Tool

Version

Checker

Description

 

 

 

 

Related Vulnerabilities

Search for other vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]Subclause 20.8, "Smart Pointers"

...