Versions Compared

Key

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

...

The C++ Standard, [basic.life], paragraph 5 [ISO/IEC 14882-2014], describes the lifetime rules for pointers:

...

Paragraph 6 describes the lifetime rules for nonpointersnon-pointers:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a glvalue refers to allocated storage, and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:
    — an lvalue-to-rvalue conversion is applied to such a glvalue,
    — the glvalue is used to access a non-static data member or call a non-static member function of the object, or
    — the glvalue is bound to a reference to a virtual base class, or
    — the glvalue is used as the operand of a dynamic_cast or as the operand of typeid.

Do not use an object outside of its lifetime, except in the ways described above as being well-defined.

...

In this noncompliant code example, a pointer to an object is used , prior to its lifetime starting, to call a nonstatic non-static member function of the object prior to the beginning of the pointer's lifetime, resulting in undefined behavior:.

Code Block
bgColor#FFCCCC
langcpp
struct S {
  void mem_fn();
};
 
void f() {
  S *s;
  s->mem_fn();
}

...

In this compliant solution, storage is obtained for the pointer prior to calling S::mem_fn():.

Code Block
bgColor#ccccff
langcpp
struct S {
  void mem_fn();
};
 
void f() {
  S *s = new S;
  s->mem_fn();
  delete s;
}

An improved compliant solution would not dynamically allocate memory directly but would instead use an automatic local variable to obtain the storage and perform initialization. If a pointer were required, use of a smart pointer, such as std::unique_ptr, would be a marked improvement. However, these suggested compliant solutions would distract from the lifetime demonstration of this compliant solution and consequently are not shown.

...

In this noncompliant code example, a pointer to an object is implicitly converted to a virtual base class after the object's lifetime has ended, resulting in undefined behavior:.

Code Block
bgColor#FFCCCC
langcpp
struct B {};
 
struct D1 : virtual B {};
struct D2 : virtual B {};
 
struct S : D1, D2 {};
 
void f(const B *b) {}
 
void g() {
  S *s = new S;
  // Use s
  delete s;
 
  f(s);
}

Despite that the fact that f() never makes use of the object, its being passed as an argument to f() is sufficient to trigger undefined behavior.

...

In this compliant solution, the lifetime of s is extended to cover the call to f():.

Code Block
bgColor#ccccff
langcpp
struct B {};
 
struct D1 : virtual B {};
struct D2 : virtual B {};
 
struct S : D1, D2 {};
 
void f(const B *b) {}
 
void g() {
  S *s = new S;
  // Use s
  f(s);
 
  delete s;
}

...

In this noncompliant code example, the address of a local variable is returned from f(). When the resulting pointer is passed to h(), the lvalue-to-rvalue conversion applied to i results in undefined behavior:.

Code Block
bgColor#FFCCCC
langcpp
int *g() {
  int i = 12;
  return &i;
}
 
void h(int *i);
 
void f() {
  int *i = g();
  h(i);
}

...

In this compliant solution, the local variable returned from g() has static storage duration instead of automatic storage duration, extending its lifetime sufficiently for use within f():.

Code Block
bgColor#ccccff
langcpp
int *g() {
  static int i = 12;
  return &i;
}
 
void h(int *i);
 
void f() {
  int *i = g();
  h(i);
}

...

std::initializer_list<> object is constructed from an initializer list as though the implementation allocated a temporary array and passed it to the std::initializer_list<> constructor. This temporary array has the same lifetime as other temporary objects except that initializing a std::initializer_list<> object from the array extends the lifetime of the array exactly like binding a reference to a temporary [ISO/IEC 14882-2014].

In this noncompliant code example, a member variable of type std::initializer_list<int> is list-initialized within the constructor's ctor-initializer. Under these circumstances, the conceptual temporary array's lifetime ends once the constructor exits, and so accessing any elements of the std::initializer_list<int> member variable results in undefined behavior.

Code Block
bgColor#FFCCCC
langcpp
#include <initializer_list>
#include <iostream>

class C {
  std::initializer_list<int> Ll;
  
public:
  C() : Ll{1, 2, 3} {}
  
  int first() const { return *Ll.begin(); }
};

void f() {
  C c;
  std::cout << c.first();
}

...

In this compliant solution, the std::initializer_list<int> member variable is replaced with a std::vector<int>, which copies the elements of the initializer list to the container instead of relying on a dangling reference to the temporary array:.

Code Block
bgColor#ccccff
langcpp
#include <initializer_list>
#include <iostream>
#include <vector>
 
class C {
  std::vector<int> Ll;
  
public:
  C() : Ll{1, 2, 3} {}
  
  int first() const { return *Ll.begin(); }
};
 
void f() {
  C c;
  std::cout << c.first();
}

...

In this noncompliant code example, a lamdba lambda object is stored in a function object, which is later called (executing the lambda) to obtain a constant reference to a value. The lambda object returns an int value, which is then stored in a temporary int object that becomes bound to the const int & return type specified by the function object. However, the temporary object's lifetime is not extended past the return from the function object's invocation, which causes undefined behavior when the resulting value is accessed.

...

In this compliant solution, the std::function object returns an int instead of a const int &, ensuring that the value is copied instead of bound to a temporary reference. An alternative solution would be to call the lambda directly instead of through the std::function objectfunction<> object.

Code Block
bgColor#ccccff
langcpp
#include <functional>
 
void f() {
  auto l = [](const int &j) { return j; };
  std::function<int(const int &)> fn(l);
 
  int i = 42;
  int j = fn(i);
}

...

Code Block
bgColor#FFcccc
langcpp
class S { 
  int Vv; 
 
public: 
  S() : Vv(12) {} // Not a Non-trivial constructor 
 
  void f(); 
};   
 
void f() { 
 
  // ...   
 
  goto bad_idea;   
 
  // ... 
 
  S s; // Control passes over the declaration, so initialization does not take place.   
 
  bad_idea: 
    s.f(); 
}

Compliant Solution

This compliant solution ensures that s is properly initialized prior to performing the local jump:.

Code Block
bgColor#ccccff
langcpp
class S { 
  int Vv; 
 
public: 
  S() : Vv(12) {} // Not a Non-trivial constructor 
  
  void f(); 
};   
 
void f() { 
  S s; 
 
  // ... 
 
  goto bad_idea; 
 
  // ... 
 
  bad_idea: 
    s.f(); 
}

Noncompliant Code Example

In this noncompliant code example, f() is called with an iterable range of objects of type S. These objects are copied into a temporary buffer using std::copy(), and when processing of those objects is complete, the temporary buffer is deallocated. However, the buffer returned by std::get_temporary_buffer() does not contain initialized objects of type S, so when std::copy() dereferences the destination iterator, it results in undefined behavior because the object being assigned into referenced by the destination iterator has yet to start its lifetime. This is because while space for the object has been allocated, no constructors or initializers have been invoked.

Code Block
bgColor#FFcccc
langcpp
#include <algorithm>
#include <cstddef>
#include <memory>
#include <type_traits>
 
class S {
  int i;

public:
  S() : i(0) {}
  S(int i) : i(i) {}
  S(const S&) = default;
  S& operator=(const S&) = default;
};

template <typename Iter>
void f(Iter Ii, Iter Ee) {
  static_assert(std::is_same<typename std::iterator_traits<Iter>::value_type, S>::value,
                "Expecting iterators over type S");
  ptrdiff_t count = std::distance(Ii, Ee);
  if (!count) {
    return;
  }
  
  // Get some temporary memory.
  auto p = std::get_temporary_buffer<S>(count);
  if (p.second < count) {
    // Handle error; memory wasn't allocated, or insufficient memory was allocated.
    return;
  }
  S *vals = p.first; 
  
  // Copy the values into the memory.
  std::copy(Ii, Ee, vals);
  
  // ...
  
  // Return the temporary memory.
  std::return_temporary_buffer(vals);    
}

Implementation Details

A reasonable implementation of std::get_temporary_buffer() and std::copy() can result in code that behaves like the following example (with error-checking elided):.

Code Block
unsigned char *buffer = new (std::nothrow) unsigned char[sizeof(S) * object_count];
S *result = reinterpret_cast<S *>(buffer);
while (Ii != Ee) {
  *result = *Ii; // Undefined behavior
  ++result;
  ++Ii;
}

The act of dereferencing result is undefined behavior because the memory pointed to is not an object of type S within its lifetime.

Compliant Solution (std::uninitialized_copy())

In this compliant solution, std::uninitialized_copy() is used to perform the copy, instead of std::copy(), ensuring that the objects are initialized using placement new instead of dereferencing uninitialized memory. The compliant solution also shows an alternate solution using a std::raw_storage_iterator, with the same well-defined resultsIdentical code from the noncompliant code example has been elided for brevity.

Code Block
bgColor#ccccff
langcpp
#include <algorithm>
#include <cstddef>
#include <memory>
#include <type_traits>
 
class S {
  int i;
public:
  S() : i(0) {}
  S(int i) : i(i) {}
  S(const S&) = default;
  S& operator=(const S&) = default;
};

template <typename Iter>
void f(Iter I, Iter E) {
  static_assert(std::is_same<typename std::iterator_traits<Iter>::value_type, S>::value,
                "Expecting iterators over type S");
  ptrdiff_t count = std::distance(I, E);
  if (!count) {
    return;
  }
  
  // Get some temporary memory.
  auto p = std::get_temporary_buffer<S>(count);
  if (p.second < count) {
    // Handle error; memory wasn't allocated, or insufficient memory was allocated.
    return;
  }
  S *vals = p.first; 
  
  // Copy the values into the memory.
  std::uninitialized_copy(I, E, vals);
  // It is also acceptable to use a raw_storage_iterator in situations where an iterator
  // over uninitialized objects is required.
  //  std::copy(I, E, std::raw_storage_iterator<S*, S>(vals));
  
  // ...
  
  // Return the temporary memory.
  std::return_temporary_buffer(vals);    
}

Risk Assessment

Referencing an object outside of its lifetime can result in an attacker being able to run arbitrary code.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

EXP54-CPP

High

Probable

High

P6

L2

Automated Detection

//...
  // Copy the values into the memory.
  std::uninitialized_copy(i, e, vals);
// ...

Compliant Solution (std::raw_storage_iterator)

This compliant solution uses std::copy() with a std::raw_storage_iterator as the destination iterator with the same well-defined results as using std::uninitialized_copy(). As with the previous compliant solution, identical code from the noncompliant code example has been elided for brevity.

Code Block
bgColor#ccccff
langcpp
//...
  // Copy the values into the memory.
  std::copy(i, e, std::raw_storage_iterator<S*, S>(vals));
// ...

Risk Assessment

Referencing an object outside of its lifetime can result in an attacker being able to run arbitrary code.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

EXP54-CPP

High

Probable

High

P6

L2

Automated Detection

Tool

Version

Checker

Description

Astrée

Include Page
Astrée_V
Astrée_V

return-reference-local
dangling_pointer_use
Partially checked
Clang
Include Page
Clang_V
Clang_V

-Wdangling-initializer-list

Catches some lifetime issues related to incorrect use of std::initializer_list<>
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V

IO.UAC
ALLOC.UAF

Use after close
Use after free
Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C++4003, C++4026

DF2812, DF2813, DF2814, DF2930, DF2931, DF2932, DF2933, DF2934,


Klocwork
Include Page
Klocwork_V
Klocwork_V
CL.FFM.ASSIGN
CL.FFM.COPY
LOCRET.ARG
LOCRET.GLOB
LOCRET.RET
UFM.DEREF.MIGHT
UFM.DEREF.MUST
UFM.FFM.MIGHT
UFM.FFM.MUST
UFM.RETURN.MIGHT
UFM.RETURN.MUST
UFM.USE.MIGHT
UFM.USE.MUST
UNINIT.HEAP.MIGHT
UNINIT.HEAP.MUST
UNINIT.STACK.ARRAY.MIGHT
UNINIT.STACK.ARRAY.MUST
UNINIT.STACK.ARRAY.PARTIAL.MUST
UNINIT.STACK.MIGHT
UNINIT.STACK.MUST

LDRA tool suite
Include Page
LDRA_V
LDRA_V

42 D, 53 D, 77 D, 1 J, 71 S, 565 S

Partially implemented

Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V

CERT_CPP-EXP54-a
CERT_CPP-EXP54-b
CERT_CPP-EXP54-c

Do not use resources that have been freed
The address of an object with automatic storage shall not be returned from a function
The address of an object with automatic storage shall not be assigned to another object that may persist after the first object has ceased to exist

Parasoft Insure++

Runtime detection
Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: EXP54-CPP

Checks for:

  • Non-initialized variable or pointer
  • Use of previously freed pointer
  • Pointer or reference to stack variable leaving scope
  • Accessing object with temporary lifetime

Rule partially covered.

PVS-Studio

Include Page
PVS-Studio_V
PVS-Studio_V

V758V1041, V1099

RuleChecker
Include Page
RuleChecker_V
RuleChecker_V
return-reference-localPartially checked

Tool

Version

Checker

Description

Clang Include PageClang_VClang_V

-Wdangling-initializer-list

Catches some lifetime issues related to incorrect use of std::initializer_list<>

Related Vulnerabilities

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

Related Guidelines

Bibliography

[Coverity 2007]
 

[ISO/IEC 14882-2014]Subclause 3.8, "Object Lifetime"
Subclause 8.5.4, "List-Initialization"
 
 


...

Image Modified Image Modified Image Modified