From 6b528261ec6c8e856c30483ea900ce438f9ca4d6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 24 Oct 2026 09:31:02 -0700 Subject: [PATCH] Fix allocation size overflow in detail::allocator allocator::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 back to format.h since the header now throws std::bad_alloc. --- include/fmt/format-inl.h | 1 - include/fmt/format.h | 3 ++- test/format-test.cc | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 87e002d7..9e539ec7 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -18,7 +18,6 @@ extern "C" void __tsan_release(void*); # include # include // errno -# include // std::bad_alloc #endif #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) diff --git a/include/fmt/format.h b/include/fmt/format.h index ebaac1a1..2fbe206a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -53,6 +53,7 @@ # include // std::signbit # include // std::numeric_limits +# include // std::bad_alloc # if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) // Workaround for pre gcc 5 libstdc++. # include // std::allocator_traits @@ -905,7 +906,7 @@ template struct allocator : private std::decay { using value_type = T; FMT_CONSTEXPR20 auto allocate(size_t n) -> T* { - FMT_ASSERT(n <= max_value() / sizeof(T), ""); + if (n > max_value() / 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]; diff --git a/test/format-test.cc b/test/format-test.cc index e86b8adb..f4b4b830 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -23,6 +23,7 @@ #include // std::back_inserter #include // std::list #include // std::mutex +#include // std::bad_alloc #include // std::string #include // std::thread #include // 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() / sizeof(char32_t) + 2; + EXPECT_THROW(fmt::detail::allocator().allocate(n), std::bad_alloc); +} + TEST(memory_buffer_test, back_insert_iterator) { fmt::memory_buffer buf; using iterator = decltype(std::back_inserter(buf));