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. The
affected targets are the ones without posix_memalign (where we round
before calling C11 aligned_alloc) and without any aligned allocation
function (where we provide our own __gnu_cxx::aligned_alloc in terms of
malloc).

libstdc++-v3/ChangeLog:

        * libsupc++/new_opa.cc (__gnu_cxx::aligned_alloc): Check for
        size_t overflow.
        (operator new) [_GLIBCXX_HAVE_ALIGNED_ALLOC]: Likewise.
        * testsuite/18_support/new_aligned_wrap.cc: New test.
---

Tested x86_64-linux. This should be backported too.

This fixes https://access.redhat.com/security/cve/cve-2026-95619 so
distros should backport it to any supported releases that are affected.
The bug was introduced in GCC 7.1 and fixed for most targets in 12.4.0,
13.3.0, and 14.1.0 by PR113258. For *-*-mingw* all versions after 7.1.0
are affected.

 libstdc++-v3/libsupc++/new_opa.cc             | 11 +++++---
 .../testsuite/18_support/new_aligned_wrap.cc  | 25 +++++++++++++++++++
 2 files changed, 33 insertions(+), 3 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..ad9cf21dfcae 100644
--- a/libstdc++-v3/libsupc++/new_opa.cc
+++ b/libstdc++-v3/libsupc++/new_opa.cc
@@ -103,8 +103,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);
@@ -141,7 +143,10 @@ operator new (std::size_t sz, std::align_val_t al)
     align = sizeof(void*);
 # endif
   /* C11: the value of size shall be an integral multiple of alignment.  */
-  sz = (sz + align - 1) & ~(align - 1);
+  if (!__builtin_add_overflow(sz, align - 1, &sz))
+    sz &= ~(align - 1);
+  else
+    _GLIBCXX_THROW_OR_ABORT(bad_alloc());
 #endif
 
   void *p;
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..d17a97a5e712
--- /dev/null
+++ b/libstdc++-v3/testsuite/18_support/new_aligned_wrap.cc
@@ -0,0 +1,25 @@
+// { dg-do run }
+
+#include <new>
+#include <testsuite_hooks.h>
+
+#pragma GCC diagnostic ignored "-Walloc-size-larger-than="
+
+int main()
+{
+#if __cpp_aligned_new
+  std::size_t size = -9;
+  std::align_val_t align = (std::align_val_t)32;
+  try
+    {
+      (void*) ::operator new(size, align);
+      VERIFY(false);
+    }
+  catch (const std::bad_alloc&)
+    {
+    }
+
+  void* p = ::operator new(size, align, std::nothrow);
+  VERIFY(p == 0);
+#endif
+}
-- 
2.55.0

Reply via email to