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 callof operator new[](sizeof(T) * 5 + y, 2, f).
Here,
...
andy
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 byoperator new[]
. This overhead may be applied in all array new-expressions, including those referencing the library functionoperator 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.3 | 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 | Do not pass a pointer that has insufficient storage capacity or that is not suitably aligned for the object being constructed to placement 'new' |
Polyspace Bug Finder | R2024a | CERT C++: MEM54-CPP | Checks for placement new used with insufficient storage or misaligned pointers (rule fully covered) |
PVS-Studio | 7.33 | 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
Zhuo Chen
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;
}
David Svoboda
Agreed. In fact n will always be less than bufsize unless S was exactly 1 byte long and overhead was 0. Fixed.
Federico Zuccardi Merli
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:
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[]"
alignas(max_align_t)
- probably not in this case, as it contains no data members.alignof(size_t)
instead ofalignof(S)
falling into undefined behaviourNote 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 analignof() > alignof(size_t)
, e.g. long double, as the GCC compiler (while needing only asizeof(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.
David Svoboda
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.
Federico Zuccardi Merli
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.
So, with gcc, the actual situation is even worse: if length check is omitted, memory outside of the allocated region might be accessed!
David Svoboda
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.
Federico Zuccardi Merli
Thanks for confirming my doubts and swiftly taking care of this!
Joseph C. Sible
C++20, section 7.6.2.7 [expr.new], paragraph 15 says this:
And paragraph 19 says this:
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.