https://gcc.gnu.org/g:d6f4bc858383e071b1a1e2b303f6622b614c37a1
commit r16-9701-gd6f4bc858383e071b1a1e2b303f6622b614c37a1 Author: Jonathan Wakely <[email protected]> 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 <[email protected]> (cherry picked from commit 59d235ffa5a69231eb42e5290d52dc8c90d28b7a) 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..897b3d792e29 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); +}
