Fix allocation size overflow in detail::allocator

allocator<T>::allocate(n) computed n * sizeof(T) with only an FMT_ASSERT
guarding against overflow, which is a no-op under NDEBUG. On 32-bit targets
a width of 2^30 or more with a 4-byte character type, e.g.
fmt::format(L"{:>{}}", s, 1100000000), wraps the byte count around, and
basic_memory_buffer records a capacity much larger than the block it got,
so padding is written past the end of the allocation.

Make the check unconditional and throw std::bad_alloc, as std::allocator
did before it was replaced in 11.1.0. Move <new> back to format.h since the
header now throws std::bad_alloc.
This commit is contained in:
Victor Zverovich 2026-10-24 09:31:02 -07:00
parent 011e5a911f
commit 6b528261ec
3 changed files with 9 additions and 2 deletions

View File

@ -18,7 +18,6 @@ extern "C" void __tsan_release(void*);
# include <algorithm>
# include <cerrno> // errno
# include <new> // std::bad_alloc
#endif
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)

View File

@ -53,6 +53,7 @@
# include <cmath> // std::signbit
# include <limits> // std::numeric_limits
# include <new> // std::bad_alloc
# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)
// Workaround for pre gcc 5 libstdc++.
# include <memory> // std::allocator_traits
@ -905,7 +906,7 @@ template <typename T> struct allocator : private std::decay<void> {
using value_type = T;
FMT_CONSTEXPR20 auto allocate(size_t n) -> T* {
FMT_ASSERT(n <= max_value<size_t>() / sizeof(T), "");
if (n > max_value<size_t>() / sizeof(T)) FMT_THROW(std::bad_alloc());
#if FMT_USE_CONSTEVAL
// Use the builtin directly to avoid C++ runtime dependencies at -O0.
if (__builtin_is_constant_evaluated()) return new T[n];

View File

@ -23,6 +23,7 @@
#include <iterator> // std::back_inserter
#include <list> // std::list
#include <mutex> // std::mutex
#include <new> // std::bad_alloc
#include <string> // std::string
#include <thread> // std::thread
#include <type_traits> // std::is_default_constructible
@ -581,6 +582,12 @@ TEST(memory_buffer_test, max_size_allocator_overflow) {
EXPECT_THROW(buffer.resize(161), std::exception);
}
TEST(memory_buffer_test, default_allocator_overflow) {
// n * sizeof(char32_t) would wrap around to a small nonzero value.
size_t n = max_value<size_t>() / sizeof(char32_t) + 2;
EXPECT_THROW(fmt::detail::allocator<char32_t>().allocate(n), std::bad_alloc);
}
TEST(memory_buffer_test, back_insert_iterator) {
fmt::memory_buffer buf;
using iterator = decltype(std::back_inserter(buf));