From ccaf6ca775129dc9d9becd5838b8805f14ff7195 Mon Sep 17 00:00:00 2001 From: Simon Gene Gottlieb Date: Wed, 10 Dec 2025 03:22:22 +0100 Subject: [PATCH] fix: overflow buffer with large precision values Issue #1385 demonstrates how a large 'precision' value can cause buffer overflows. Originally, the buffer was designed to fit any scientific notation. But the precision changes at which point large floating point numbers are displayed in scientific notation or default notation. In case of the default notation many extra zeros have to be printed, this was not reflected in the output_buffer and an overflow could occur. This PR computes the number of zero that do not fit into the static buffer and appends them at the end of the function triggering potential a second dynamic allocation. (The first allocation is the std::string allocation). --- src/fptostring.cpp | 29 +++++++++++++++++++++++------ test/fptostring_test.cpp | 4 ++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/fptostring.cpp b/src/fptostring.cpp index 3026cbd08..e1722aeed 100644 --- a/src/fptostring.cpp +++ b/src/fptostring.cpp @@ -129,9 +129,14 @@ std::string FpToString(T v, int precision = 0) { } } - std::array output_buffer; // max digits of size_t plus sign, a dot and 2 letters for 'e+' or 'e-' and 4 letters for the exponent + // Case 1 - scientific notation: max digits of size_t plus sign, a dot and 2 letters for 'e+' or 'e-' and 4 letters for the exponent + // Case 2 - default notation: require up to precision number of digits and one for a potential sign + std::array output_buffer; auto output_ptr = &output_buffer[0]; + // Helper variable that in Case 2 counts the overflowing number of zeros that do not fit into the buffer. + int overflow_zeros = 0; + // print '-' symbol for negative numbers if (r.is_negative) { *(output_ptr++) = '-'; @@ -172,18 +177,28 @@ std::string FpToString(T v, int precision = 0) { // case 2: default notation } else { - auto const digits_end = digits.begin() + digits_ct; - auto digits_iter = digits.begin(); + auto const digits_end = digits.begin() + digits_ct; + auto digits_iter = digits.begin(); // print digits before point int const before_decimal_digits = digits_ct + r.exponent; if (before_decimal_digits > 0) { - // print digits before point + // print non-zero digits before point for (int i{0}; i < std::min(before_decimal_digits, digits_ct); ++i) { *(output_ptr++) = *(digits_iter++); } + + // number of digits that have to be zero + int const zero_digits_ct = before_decimal_digits - digits_ct; + + // space left in the output_buffer (-1 because we need it for null-termination) + int const buffer_empty_space = output_buffer.data() + output_buffer.size() - output_ptr - 1; + + // print all zeros not fitting into the buffer at the end of the function + overflow_zeros = std::max(0, zero_digits_ct - buffer_empty_space); + // print trailing zeros before point - for (int i{0}; i < before_decimal_digits - digits_ct; ++i) { + for (int i{0}; i < zero_digits_ct - overflow_zeros; ++i) { *(output_ptr++) = '0'; } @@ -207,7 +222,9 @@ std::string FpToString(T v, int precision = 0) { } } *output_ptr = '\0'; - return std::string{&output_buffer[0], output_ptr}; + auto ret_value = std::string{&output_buffer[0], output_ptr}; + ret_value.resize(ret_value.size() + overflow_zeros, '0'); + return ret_value; } } diff --git a/test/fptostring_test.cpp b/test/fptostring_test.cpp index 3f377abf2..8ba750e7e 100644 --- a/test/fptostring_test.cpp +++ b/test/fptostring_test.cpp @@ -238,5 +238,9 @@ TEST(FpToStringTest, conversion_float) { EXPECT_EQ("-1.3e-05", FpToString(-1.299e-5f, 2)); } +TEST(FpToStringTest, vulnerability_stack_buffer_overflow) { + EXPECT_EQ(FpToString(1.0e100, 200), "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + } // namespace } // namespace YAML