2 Commits

Author SHA1 Message Date
Simon Gene Gottlieb
c7aa78d294 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).
2025-12-28 17:33:09 -06:00
Simon Gene Gottlieb
a2826e8983 fix: unittests sets locale before testing
the convert_with_stringstream seems to be missing a
.imbue() call to set the locale independent of the global state.
Not setting this, can in some settings lead to errors, see Issue #1366.
2025-12-28 17:32:43 -06:00
2 changed files with 28 additions and 6 deletions

View File

@@ -129,9 +129,14 @@ std::string FpToString(T v, int precision = 0) {
}
}
std::array<char, 28> 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<char, 28> 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++) = '-';
@@ -178,12 +183,22 @@ std::string FpToString(T v, int precision = 0) {
// 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;
}
}

View File

@@ -10,6 +10,7 @@ namespace {
template <typename T>
static std::string convert_with_stringstream(T v, size_t precision = 0) {
std::stringstream ss;
ss.imbue(std::locale::classic());
if (precision > 0) {
ss << std::setprecision(precision);
}
@@ -238,5 +239,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