When invoked by a new expression for a given type, the default global non-placement forms of operator new attempt to allocate sufficient storage for an object of the type and, if successful, return a pointer with alignment suitable for any object with a fundamental alignment requirement. However, the default placement new operator simply returns the given pointer back to the caller without guaranteeing that there is sufficient space in which to construct the object or ensuring that the pointer meets the proper alignment requirements. The C++ Standard, [expr.new], paragraph 16 [ISO/IEC 14882-2014], nonnormatively states the following:

[Note: when the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned and of the requested size. The address of the created object will not necessarily be the same as that of the block if the object is an array. —end note]

(This note is a reminder of the general requirements specified by the C++ Standard, [basic.stc.dynamic.allocation], paragraph 1, which apply to placement new operators by virtue of [basic.stc.dynamic], paragraph 3.)

In addition, the standard provides the following example later in the same section:

new(2, f) T[5] results in a call of operator new[](sizeof(T) * 5 + y, 2, f).

Here, ... and y are non-negative unspecified values representing array allocation overhead; the result of the new-expression will be offset by this amount from the value returned by operator new[]. This overhead may be applied in all array new-expressions, including those referencing the library function operator new[](std::size_t, void*) and other placement allocation functions. The amount of overhead may vary from one invocation of new to another.

Do not pass a pointer that is not suitably aligned for the object being constructed to placement new. Doing so results in an object being constructed at a misaligned location, which results in undefined behavior. Do not pass a pointer that has insufficient storage capacity for the object being constructed, including the overhead required for arrays. Doing so may result in initialization of memory outside of the bounds of the object being constructed, which results in undefined behavior. 

Finally, do not use placement new[] on any platform that does not specify a limit for the overhead it requires.

Noncompliant Code Example

In this noncompliant code example, a pointer to a short is passed to placement new, which is attempting to initialize a long. On architectures where sizeof(short) < sizeof(long), this results in undefined behavior. This example, and subsequent ones, all assume the pointer returned by placement new will not be used after the lifetime of its underlying storage has ended. For instance, the pointer will not be stored in a static global variable and dereferenced after the call to f() has ended. This assumption is in conformance with MEM50-CPP. Do not access freed memory.

#include <new>
 
void f() {
  short s;
  long *lp = ::new (&s) long;
}

Noncompliant Code Example

This noncompliant code example ensures that the long is constructed into a buffer of sufficient size. However, it does not ensure that the alignment requirements are met for the pointer passed into placement new. To make this example clearer, an additional local variable c has also been declared.

#include <new>
 
void f() {
  char c; // Used elsewhere in the function
  unsigned char buffer[sizeof(long)];
  long *lp = ::new (buffer) long;
 
  // ...
}

Compliant Solution (alignas)

In this compliant solution, the alignas declaration specifier is used to ensure the buffer is appropriately aligned for a long.

#include <new>
 
void f() {
  char c; // Used elsewhere in the function
  alignas(long) unsigned char buffer[sizeof(long)];
  long *lp = ::new (buffer) long;
 
  // ...
}

Compliant Solution (std::aligned_storage)

This compliant solution ensures that the long is constructed into a buffer of sufficient size and with suitable alignment.

#include <new>
 
void f() {
  char c; // Used elsewhere in the function
  std::aligned_storage<sizeof(long), alignof(long)>::type buffer;
  long *lp = ::new (&buffer) long;
 
  // ...
}

Noncompliant Code Example (Failure to Account for Array Overhead)

This noncompliant code example attempts to allocate sufficient storage of the appropriate alignment for the array of objects of S.  However, it fails to account for the overhead an implementation may add to the amount of storage for array objects.  The overhead (commonly referred to as a cookie) is necessary to store the number of elements in the array so that the array delete expression or the exception unwinding mechanism can invoke the type's destructor on each successfully constructed element of the array.  While some implementations are able to avoid allocating space for the cookie in some situations, assuming they do in all cases is unsafe.

#include <new>

struct S {
  S ();
  ~S ();
};

void f() {
  const unsigned N = 32;
  alignas(S) unsigned char buffer[sizeof(S) * N];
  S *sp = ::new (buffer) S[N];
 
  // ...
  // Destroy elements of the array.
  for (size_t i = 0; i != n; ++i) {
    sp[i].~S();
  }
}

Compliant Solution (Clang/GCC)

The amount of overhead required by array new expressions is unspecified but ideally would be documented by quality implementations. The following compliant solution is specifically for the Clang and GNU GCC compilers, which guarantee that the overhead for dynamic array allocations is a single value of type size_t. (Note that this value is often treated as a "-1th" element in the array, so the actual space used may be larger.) To verify that the assumption is, in fact, safe, the compliant solution also overloads the placement new[] operator to accept the buffer size as a third argument and verifies that it is not smaller than the total amount of storage required.

#include <cstddef>
#include <new>

#if defined(__clang__) || defined(__GNUG__)
  const size_t overhead = sizeof(size_t);
#else
  static_assert(false, "you need to determine the size of your implementation's array overhead");
  const size_t overhead = 0; // Declaration prevents additional diagnostics about overhead being undefined; the value used does not matter.
#endif

struct S {
  S();
  ~S();
};

void *operator new[](size_t n, void *p, size_t bufsize) {
  if (n > bufsize) {
    throw std::bad_array_new_length();
  }
  return p;
}

void f() {
  const size_t n = 32;
  alignas(S) unsigned char buffer[sizeof(S) * n + std::max(overhead, alignof(S))];
  S *sp = ::new (buffer, sizeof(buffer)) S[n];
 
  // ...
  // Destroy elements of the array.
  for (size_t i = 0; i != n; ++i) {
    sp[i].~S();
  }
}

Porting this compliant solution to other implementations requires adding similar conditional definitions of the overhead constant, depending on the constraints of the platform.

Risk Assessment

Passing improperly aligned pointers or pointers to insufficient storage to placement new expressions can result in undefined behavior, including buffer overflow and abnormal termination.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MEM54-CPP

High

Likely

Medium

P18

L1

Automated Detection

Tool

Version

Checker

Description

Axivion Bauhaus Suite

7.2.0

CertC++-MEM54
CodeSonar
8.1p0

LANG.MEM.BO

Buffer Overrun

Helix QAC

2024.1

C++3119, C++3128, DF3520, DF3521, DF3522, DF3523


LDRA tool suite
9.7.1

 

597 S

Enhanced Enforcement

Parasoft C/C++test

2023.1

CERT_CPP-MEM54-a
CERT_CPP-MEM54-b

Do not pass a pointer that has insufficient storage capacity or that is not suitably aligned for the object being constructed to placement 'new'
An overhead should be used when an array of objects is passed to the placement 'new' allocation function

Polyspace Bug Finder

R2024a

CERT C++: MEM54-CPPChecks for placement new used with insufficient storage or misaligned pointers (rule fully covered)
PVS-Studio

7.31

V752

Related Vulnerabilities

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

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]Subclause 3.7.4, "Dynamic Storage Duration"
Subclause 5.3.4, "New" 



8 Comments

  1. Hi,  i'm wondering there is an error in the test case of Compliant Solution (Clang/GCC):


    void *operator new[](size_t n, void *p, size_t bufsize) {
      if (n <= bufsize) {    =============> should it be `n > bufsize` here?
        throw std::bad_array_new_length();
      }
      return p;
    }



    1. Agreed. In fact n will always be less than bufsize unless S was exactly 1 byte long and overhead was 0. (smile)  Fixed.

  2. I have a doubt about the compliant solution for the array cookie case:

    The pointer returned by the new-expression might not be suitably aligned for a general object (a struct S in this case), please follow my reasoning:

    • The overloaded operator new is passed a pointer which is guaranteed to have alignof(S), in addition to n.
    • According to §12 (C++11) or §15 (C++17) of expr.new, when referring to the additional size passed to operator new in case of arrays:
      "... the result of the new-expression will be offset by this amount from the value returned by operator new[]"

    • A struct/class might in general be alignas(max_align_t) - probably not in this case, as it contains no data members.
    • In our case, this means that the new-expression return an address at best with an alignof(size_t)instead of alignof(S) falling into undefined behaviour

    Note that in reality on a x86_64 architecture, the compliant code will in fact fail, throwing bad_array_new_length, as soon as struct S contains any data member with an alignof() > alignof(size_t), e.g. long double, as the GCC compiler (while needing only a sizeof(size_t) sized cookie) will ask for a size that guarantees a correctly aligned pointer can be generated as result of the new-expression.

    A test with the Microsoft compiler (the cookie size appears to be sizeof(size_t)) generates instead a badly aligned pointer.

    As the compliant code should show good practice (as far as this very implementation dependent thing can even be considered good practice...) I think it should be amended, as it is, yes, working but only because of the triviality of struct S.

    Probably the best option for the general case would be reserving an additional size of max_align_t, as we can be sure that both sizeof() and alignof() of max_align_t are greater or equal to those of size_t.

    1. Federico Zuccardi Merli:

      There is one problem with your question: The compliant solution you cite is specific to Clang & GCC, as it relies on the fact that both Clang & GCC guarantee that the overhead for dynamic array allocations is a single value of type size_t. We make no guarantees of how MSVC will behave on this code, especially because this code explicitly sets the cookie size is 0 for any compilers except Clang & GCC. So the code complies with this rule only for Clang & GCC.

  3. David Svoboda

    Yes, I can agree on the MS compiler point, but that was only meant as an example where the size of the cookie is also known but not enough to guarantee compliant behaviour.

    In any case, the fact that reserving just enough space for the cookie is in  general not enough for any non-trivial structure should be highlighted somehow.

    The following code will in fact throw bad_array_new_length for gcc, x86_64, on Linux as a (e.g.) long double data member is added to struct S:
    EtA: This happens because gcc will ask for size + max(alignof(T), sizeof(size_t), as far as I could experiment.

    #include <cstddef>
    #include <new>
    
    struct S
    {
        long double v;
        S() { v = 0; };
        ~S() {};
    };
    
    void *operator new[](size_t n, void *p, size_t bufsize)
    {
        if (n > bufsize)
        {
            throw std::bad_array_new_length();
        }
        return p;
    }
    
    const size_t overhead = sizeof(size_t);
    
    int main(void)
    {
        const size_t n = 32;
        alignas(S) unsigned char buffer[sizeof(S) * n + overhead];
        S *sp = ::new (buffer, sizeof(buffer)) S[n];
        //...
        for (size_t i = 0; i != n; ++i)
        {
            sp[i].~S();
        }
    }
    
    

    So, with gcc, the actual situation is even worse: if length check is omitted, memory outside of the allocated region might be accessed!

    1. Federico Zuccardi Merli:

      You are correct, your change makes the final compliant solution fail for GCC and Clang (on both MacOS and Ubuntu).

      I have tweaked the final compliant solution to use the maximum of S's alignment requirements and GCC/Clang 's size_t size. This works for all the platforms we can test on.

      I have also added a warning in the intro not to use placement new[] on any platform that does not specify its overhead requirements.

  4. Thanks for confirming my doubts and swiftly taking care of this!

  5. C++20, section 7.6.2.7 [expr.new], paragraph 15 says this:

    That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array and the allocation function is not a non-allocating form (17.6.2.3).

    And paragraph 19 says this:

    This overhead may be applied in all array new-expressions, including those referencing a placement allocation function, except when referencing the library function operator new[](std::size_t, void*).

    This seems to be intended solely to make you not have to worry about the overhead part of this rule anymore. We should make it clear that this part of the rule only applies to old versions of C++, and update our compliant solution to not waste any space for it anymore if it detects C++20.