The -Wplacement-new option warns for buffer overflow in placement new expressions with objects of constant sizes, but because it's implemented completely in the C++ front end it misses the more interesting non-constant sizes.
The attached patch instruments both forms of operator placement new to emit a trap when __builtin_object_size() determines that the pointer points to an object less than the specified number of bytes. This is done only when _FORTIFY_SOURCE is defined to a non-zero value. This makes it possible to prevent buffer overflow in most of the same cases as in built-ins like strcpy, though without warnings when the size is nor a C++ constant integer expression. On x86_64-linux it passes testing with no apparent regressions. Can anyone think of problems with this solution? If not, given its simplicity, would it be appropriate even at this stage? Martin PS I added the tests to the G++ test suite rather than to libstdc++ because the latter doesn't understand the DejaGnu scan-tree-dump directive (looks like it's missing an import for the .exp file that defines it).
libstdc++-v3/ChangeLog: * libsupc++/new (placement new)[_FORTIFY_SOURCE]: Emit __builtin_trap. gcc/ChangeLog: * g++.dg/init/new49.C: New test. * g++.dg/init/new50.C: New test. diff --git a/gcc/testsuite/g++.dg/init/new49.C b/gcc/testsuite/g++.dg/init/new49.C new file mode 100644 index 0000000..5713aef --- /dev/null +++ b/gcc/testsuite/g++.dg/init/new49.C @@ -0,0 +1,57 @@ +// { dg-do compile } +// { dg-options "-O2 -Wno-placement-new -fdump-tree-optimized" } + +#define _FORTIFY_SOURCE 1 +#include <new> + +void sink (void*); + +void test_placement_new_single_auto () +{ + char buf[2]; + long *p = new (buf) long; + sink (p); +} + +void* test_placement_new_signle_allocated () +{ + char *buf = (char*)::operator new (2); + long *p = new (buf) long; + return p; +} + +void test_placement_new_array_auto_const () +{ + char buf[sizeof (int)]; + int *p = new (buf) int[2]; + sink (p); +} + +void* test_placement_new_array_allocated_const () +{ + char *buf = (char*)::operator new (sizeof (int)); + int *p = new (buf) int[2]; + return p; +} + +void test_placement_new_array_auto_range (unsigned n) +{ + if (n < 2) + n = 2; + + char buf[sizeof (int)]; + int *p = new (buf) int[n]; + sink (p); +} + +void test_placement_new_array_allocated_range (unsigned n) +{ + if (n < 2) + n = 2; + + char *buf = (char*)::operator new (sizeof (int)); + int *p = new (buf) int[n]; + sink (p); +} + +// { dg-final { scan-tree-dump-times "__builtin_trap" 6 "optimized" } } diff --git a/gcc/testsuite/g++.dg/init/new50.C b/gcc/testsuite/g++.dg/init/new50.C new file mode 100644 index 0000000..1490992 --- /dev/null +++ b/gcc/testsuite/g++.dg/init/new50.C @@ -0,0 +1,22 @@ +// { dg-do compile } +// { dg-options "-O2 -fdump-tree-optimized" } + +#include <new> + +void sink (void*); + +void test_placement_new_single () +{ + char buf[2]; + long *p = new (buf) long; // { dg-warning "placement new" } + sink (p); +} + +void test_placement_new_array () +{ + char buf[2]; + int *p = new (buf) int[2]; // { dg-warning "placement new" } + sink (p); +} + +// { dg-final { scan-tree-dump-not "__builtin_trap" "optimized" } } diff --git a/libstdc++-v3/libsupc++/new b/libstdc++-v3/libsupc++/new index 0e408b1..ef7ea6d 100644 --- a/libstdc++-v3/libsupc++/new +++ b/libstdc++-v3/libsupc++/new @@ -165,10 +165,25 @@ void operator delete[](void*, std::size_t, std::align_val_t) #endif // __cpp_aligned_new // Default placement versions of operator new. -inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT -{ return __p; } -inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT -{ return __p; } +inline void* operator new(std::size_t __n, void* __p) _GLIBCXX_USE_NOEXCEPT +{ +#ifdef _FORTIFY_SOURCE && _FORTIFY_SOURCE > 0 + if (__builtin_object_size (__p, 0) < __n) + __builtin_trap (); +#endif + + return __p; +} + +inline void* operator new[](std::size_t __n, void* __p) _GLIBCXX_USE_NOEXCEPT +{ +#ifdef _FORTIFY_SOURCE && _FORTIFY_SOURCE > 0 + if (__builtin_object_size (__p, 0) < __n) + __builtin_trap (); +#endif + + return __p; +} // Default placement versions of operator delete. inline void operator delete (void*, void*) _GLIBCXX_USE_NOEXCEPT { }