Depending on the implementation used for aligned new, we might need to
round the size argument up to a multiple of the alignment. If either the
size or the alignment is a huge value then the rounded result could
wraparound to a small value which would then be successfully allocated
by the underlying libc allocation function. This means that we return
a non-null pointer but not of the requested size. Instead we should
throw std::bad_alloc because the allocation cannot succeed.

This doesn't affect most modern targets because they use the
posix_memalign implementation, which doesn't do that rounding.

On targets without posix_memalign we use C11
aligned_alloc and round before calling it. That rounding needs to be
checked for overflow. The using-directive for ::aligned_alloc is
replaced with a new __gnu_cxx::aligned_alloc function and the existing
fix-ups for that path are moved from operator new into the new function,
with added check for the overflow we need to fix.

On *-*-mingw* we use the Windows _aligned_malloc function which
correctly checks for overflow on Windows. But the Wine version of that
function does not and has the same overflow problem so we need to do our
own checks before calling it. We include a conservative 2*sizeof(void*)
adjustment in our own overflow check.

On targets without any aligned allocation function we provide our
own __gnu_cxx::aligned_alloc in terms of malloc. This also needs an
overflow check.

libstdc++-v3/ChangeLog:

        * libsupc++/new_opa.cc
        [_GLIBCXX_HAVE_ALIGNED_ALLOC] (__gnu_cxx::aligned_alloc):
        Replace using-directive with new function. Check for size_t
        overflow.
        [_GLIBCXX_HAVE__ALIGNED_MALLOC] (__gnu_cxx::aligned_alloc):
        Check for size_t overflow.
        (__gnu_cxx::aligned_alloc): Likewise in fallback definition.
        (operator new) [_GLIBCXX_HAVE_ALIGNED_ALLOC]: Move parameter
        adjustments to new __gnu_cxx::aligned_alloc function.
        * testsuite/18_support/new_aligned_wrap.cc: New test.

Reviewed-by: Tomasz KamiƄski <[email protected]>
---

v2: Move the precondition fix-ups for ::aligned_alloc into a new
__gnu_cxx::aligned_alloc function for the HAVE_ALIGNED_ALLOC case. Also
fix the mingw* implementation that uses _aligned_malloc. Test additional
(size, alignment) values.

Tested x86_64-linux and x86_64-w64-mingw32.

I've also reported https://bugs.winehq.org/show_bug.cgi?id=60378 and
https://sourceforge.net/p/mingw-w64/bugs/1039/ for the bugs in
_aligned_malloc which we workaround here.

 libstdc++-v3/libsupc++/new_opa.cc             | 50 ++++++++++++-------
 .../testsuite/18_support/new_aligned_wrap.cc  | 39 +++++++++++++++
 2 files changed, 71 insertions(+), 18 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/18_support/new_aligned_wrap.cc

diff --git a/libstdc++-v3/libsupc++/new_opa.cc 
b/libstdc++-v3/libsupc++/new_opa.cc
index 7bda847a9257..a509f9062e84 100644
--- a/libstdc++-v3/libsupc++/new_opa.cc
+++ b/libstdc++-v3/libsupc++/new_opa.cc
@@ -81,12 +81,37 @@ aligned_alloc (std::size_t al, std::size_t sz)
   return nullptr;
 }
 #elif _GLIBCXX_HAVE_ALIGNED_ALLOC
-using ::aligned_alloc;
-#elif _GLIBCXX_HAVE__ALIGNED_MALLOC
+// We have C11 aligned_alloc but we need to satisfy its preconditions.
 static inline void*
 aligned_alloc (std::size_t al, std::size_t sz)
-{ return _aligned_malloc(sz, al); }
+{
+# if defined _AIX || defined __APPLE__
+  /* AIX 7.2.0.0 aligned_alloc incorrectly has posix_memalign's requirement
+   * that alignment is a multiple of sizeof(void*).
+   * OS X 10.15 has the same requirement.  */
+  if (al < sizeof(void*))
+    al = sizeof(void*);
+# endif
+  /* C11: the value of size shall be an integral multiple of alignment.  */
+  if (__builtin_add_overflow(sz, al - 1, &sz)) [[unlikely]]
+    return nullptr;
+  sz &= ~(al - 1);
+  return ::aligned_alloc(al, sz);
+}
+#elif _GLIBCXX_HAVE__ALIGNED_MALLOC
+// Use Windows _aligned_malloc
+static inline void*
+aligned_alloc (std::size_t al, std::size_t sz)
+{
+  // The Wine msvcrt.dll version of _aligned_malloc doesn't detect overflow
+  // so do it ourselves.
+  std::size_t check;
+  if (__builtin_add_overflow(sz, al + 2 * sizeof(void*), &check)) [[unlikely]]
+    return nullptr;
+  return _aligned_malloc(sz, al);
+}
 #elif _GLIBCXX_HAVE_MEMALIGN
+// Use memalign (and assume that what it returns can be passed to free).
 static inline void*
 aligned_alloc (std::size_t al, std::size_t sz)
 {
@@ -103,8 +128,10 @@ aligned_alloc (std::size_t al, std::size_t sz)
   // We need extra bytes to store the original value returned by malloc.
   if (al < sizeof(void*))
     al = sizeof(void*);
-  void* const malloc_ptr = malloc(sz + al);
-  if (!malloc_ptr)
+  if (__builtin_add_overflow(sz, al, &sz)) [[unlikely]]
+    return nullptr;
+  void* const malloc_ptr = malloc(sz);
+  if (!malloc_ptr) [[unlikely]]
     return nullptr;
   // Align to the requested value, leaving room for the original malloc value.
   void* const aligned_ptr = (void *) (((uintptr_t) malloc_ptr + al) & -al);
@@ -131,19 +158,6 @@ operator new (std::size_t sz, std::align_val_t al)
   if (__builtin_expect (sz == 0, false))
     sz = 1;
 
-#if _GLIBCXX_HAVE_POSIX_MEMALIGN
-#elif _GLIBCXX_HAVE_ALIGNED_ALLOC
-# if defined _AIX || defined __APPLE__
-  /* AIX 7.2.0.0 aligned_alloc incorrectly has posix_memalign's requirement
-   * that alignment is a multiple of sizeof(void*).
-   * OS X 10.15 has the same requirement.  */
-  if (align < sizeof(void*))
-    align = sizeof(void*);
-# endif
-  /* C11: the value of size shall be an integral multiple of alignment.  */
-  sz = (sz + align - 1) & ~(align - 1);
-#endif
-
   void *p;
 
   while ((p = __gnu_cxx::aligned_alloc (align, sz)) == nullptr)
diff --git a/libstdc++-v3/testsuite/18_support/new_aligned_wrap.cc 
b/libstdc++-v3/testsuite/18_support/new_aligned_wrap.cc
new file mode 100644
index 000000000000..dbd642d6ba58
--- /dev/null
+++ b/libstdc++-v3/testsuite/18_support/new_aligned_wrap.cc
@@ -0,0 +1,39 @@
+// { dg-do run }
+
+#include <new>
+#include <stdint.h>
+#include <testsuite_hooks.h>
+
+#pragma GCC diagnostic ignored "-Walloc-size-larger-than="
+
+void
+check_aligned_new_overflow(std::size_t size, std::size_t align)
+{
+#if __cpp_aligned_new
+  std::align_val_t alignv = (std::align_val_t)align;
+  try
+    {
+      (void) ::operator new(size, alignv);
+      VERIFY(false);
+    }
+  catch (const std::bad_alloc&)
+    {
+    }
+
+  void* p = ::operator new(size, alignv, std::nothrow);
+  VERIFY(p == 0);
+#endif
+}
+
+int main()
+{
+  check_aligned_new_overflow(-9, 32);
+  check_aligned_new_overflow(SIZE_MAX, 8);
+  check_aligned_new_overflow(SIZE_MAX, 32);
+  check_aligned_new_overflow(SIZE_MAX, 1024);
+  check_aligned_new_overflow(SIZE_MAX, 65536);
+  check_aligned_new_overflow(SIZE_MAX -     1,    16);
+  check_aligned_new_overflow(SIZE_MAX -  1025,  1024);
+  check_aligned_new_overflow(SIZE_MAX -  1024,  1024);
+  check_aligned_new_overflow(SIZE_MAX - 65536, 65536);
+}
-- 
2.55.0

Reply via email to