[gcc r17-4611] libstdc++: Ensure that aligned new detects size_t overflow (CVE-2026-95619)
Jonathan Wakely
redi@gcc.gnu.org
Wed Sep 23 15:06:04 GMT 2026
https://gcc.gnu.org/g:59d235ffa5a69231eb42e5290d52dc8c90d28b7a
commit r17-4611-g59d235ffa5a69231eb42e5290d52dc8c90d28b7a
Author: Jonathan Wakely <jwakely@redhat.com>
Date: Wed Sep 23 13:15:18 2026 +0100
libstdc++: Ensure that aligned new detects size_t overflow (CVE-2026-95619)
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 <tkaminsk@redhat.com>
Diff:
---
libstdc++-v3/libsupc++/new_opa.cc | 49 ++++++++++++++--------
.../testsuite/18_support/new_aligned_wrap.cc | 39 +++++++++++++++++
2 files changed, 71 insertions(+), 17 deletions(-)
diff --git a/libstdc++-v3/libsupc++/new_opa.cc b/libstdc++-v3/libsupc++/new_opa.cc
index 7bda847a9257..bd0098ac09e4 100644
--- a/libstdc++-v3/libsupc++/new_opa.cc
+++ b/libstdc++-v3/libsupc++/new_opa.cc
@@ -81,12 +81,38 @@ aligned_alloc (std::size_t al, std::size_t sz)
return nullptr;
}
#elif _GLIBCXX_HAVE_ALIGNED_ALLOC
-using ::aligned_alloc;
+// 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)
+{
+# 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)
-{ return _aligned_malloc(sz, al); }
+{
+ // The Wine msvcrt.dll version of _aligned_malloc doesn't detect overflow
+ // so do it ourselves. We know al + 2 * sizeof(void*) won't overflow because
+ // has_single_bit(al) was already checked by the caller.
+ 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 +129,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 +159,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);
+}
More information about the Libstdc++-cvs
mailing list