[PATCH] libstdc++: Avoid overflow in bounds checks [PR103955]
Jonathan Wakely
jwakely@redhat.com
Tue Jan 11 21:07:24 GMT 2022
On Tue, 11 Jan 2022 at 20:03, Patrick Palka via Libstdc++ <
libstdc++@gcc.gnu.org> wrote:
> We currently crash when the floating-point to_chars overloads are passed
> a precision value near INT_MAX, ultimately due to overflow in the bounds
> checks that verify the output range is large enough.
>
> The most portable fix seems to be to replace bounds checks of the form
> A >= B + C (where B + C may overflow) with the otherwise equivalent check
> A >= B && A - B >= C, which is what this patch implements.
>
> Before we could do this in __floating_to_chars_hex, there we first need
> to track the unbounded "excess" precision (i.e. the number of trailing
> digits in the output that are guaranteed to be '0') separately from the
> bounded "effective" precision (i.e. the number of significant fractional
> digits in the output), like we do in __floating_to_chars_precision.
>
> Tested on x86_64 and i686, does this look OK for trunk/11?
>
OK for both, thanks.
> PR libstdc++/103955
>
> libstdc++-v3/ChangeLog:
>
> * src/c++17/floating_to_chars.cc (__floating_to_chars_hex):
> Track the excess precision separately from the effective
> precision. Avoid overflow in bounds check by splitting it into
> two checks.
> (__floating_to_chars_precision): Avoid overlflow in bounds
> checks similarly.
> * testsuite/20_util/to_chars/103955.cc: New test.
> ---
> libstdc++-v3/src/c++17/floating_to_chars.cc | 46 +++++++++++++------
> .../testsuite/20_util/to_chars/103955.cc | 31 +++++++++++++
> 2 files changed, 64 insertions(+), 13 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/20_util/to_chars/103955.cc
>
> diff --git a/libstdc++-v3/src/c++17/floating_to_chars.cc
> b/libstdc++-v3/src/c++17/floating_to_chars.cc
> index c825a3c8b24..6cd92553d05 100644
> --- a/libstdc++-v3/src/c++17/floating_to_chars.cc
> +++ b/libstdc++-v3/src/c++17/floating_to_chars.cc
> @@ -747,7 +747,8 @@ template<typename T>
> __glibcxx_assert(shortest_full_precision >= 0);
>
> int written_exponent = unbiased_exponent;
> - const int effective_precision =
> precision.value_or(shortest_full_precision);
> + int effective_precision = precision.value_or(shortest_full_precision);
> + int excess_precision = 0;
> if (effective_precision < shortest_full_precision)
> {
> // When limiting the precision, we need to determine how to round
> the
> @@ -794,6 +795,11 @@ template<typename T>
> }
> }
> }
> + else
> + {
> + excess_precision = effective_precision - shortest_full_precision;
> + effective_precision = shortest_full_precision;
> + }
>
> // Compute the leading hexit and mask it out from the mantissa.
> char leading_hexit;
> @@ -816,26 +822,30 @@ template<typename T>
> // Now before we start writing the string, determine the total length
> of
> // the output string and perform a single bounds check.
> int expected_output_length = sign + 1;
> - if (effective_precision != 0)
> - expected_output_length += strlen(".") + effective_precision;
> + if (effective_precision + excess_precision > 0)
> + expected_output_length += strlen(".");
> + expected_output_length += effective_precision;
> const int abs_written_exponent = abs(written_exponent);
> expected_output_length += (abs_written_exponent >= 10000 ?
> strlen("p+ddddd")
> : abs_written_exponent >= 1000 ?
> strlen("p+dddd")
> : abs_written_exponent >= 100 ?
> strlen("p+ddd")
> : abs_written_exponent >= 10 ?
> strlen("p+dd")
> : strlen("p+d"));
> - if (last - first < expected_output_length)
> + if (last - first < expected_output_length
> + || last - first - expected_output_length < excess_precision)
> return {last, errc::value_too_large};
> + char* const expected_last = first + expected_output_length +
> excess_precision;
>
> - const auto saved_first = first;
> // Write the negative sign and the leading hexit.
> if (sign)
> *first++ = '-';
> *first++ = leading_hexit;
>
> + if (effective_precision + excess_precision > 0)
> + *first++ = '.';
> +
> if (effective_precision > 0)
> {
> - *first++ = '.';
> int written_hexits = 0;
> // Extract and mask out the leading nibble after the decimal point,
> // write its corresponding hexit, and repeat until the mantissa is
> @@ -863,13 +873,18 @@ template<typename T>
> }
> }
>
> + if (excess_precision > 0)
> + {
> + memset(first, '0', excess_precision);
> + first += excess_precision;
> + }
> +
> // Finally, write the exponent.
> *first++ = 'p';
> if (written_exponent >= 0)
> *first++ = '+';
> const to_chars_result result = to_chars(first, last,
> written_exponent);
> - __glibcxx_assert(result.ec == errc{}
> - && result.ptr == saved_first +
> expected_output_length);
> + __glibcxx_assert(result.ec == errc{} && result.ptr == expected_last);
> return result;
> }
>
> @@ -1250,7 +1265,8 @@ template<typename T>
> }
>
> // Copy the string from the buffer over to the output range.
> - if (last - first < output_length + excess_precision)
> + if (last - first < output_length
> + || last - first - output_length < excess_precision)
> return {last, errc::value_too_large};
> memcpy(first, buffer, output_length);
> first += output_length;
> @@ -1304,7 +1320,8 @@ template<typename T>
> output_length_upper_bound += strlen("e+dd");
>
> int output_length;
> - if (last - first >= output_length_upper_bound + excess_precision)
> + if (last - first >= output_length_upper_bound
> + && last - first - output_length_upper_bound >=
> excess_precision)
> {
> // The result will definitely fit into the output range, so we
> can
> // write directly into it.
> @@ -1325,7 +1342,8 @@ template<typename T>
> buffer, nullptr);
> __glibcxx_assert(output_length == output_length_upper_bound - 1
> || output_length ==
> output_length_upper_bound);
> - if (last - first < output_length + excess_precision)
> + if (last - first < output_length
> + || last - first - output_length < excess_precision)
> return {last, errc::value_too_large};
> memcpy(first, buffer, output_length);
> }
> @@ -1365,7 +1383,8 @@ template<typename T>
> output_length_upper_bound += strlen(".") + effective_precision;
>
> int output_length;
> - if (last - first >= output_length_upper_bound + excess_precision)
> + if (last - first >= output_length_upper_bound
> + && last - first - output_length_upper_bound >=
> excess_precision)
> {
> // The result will definitely fit into the output range, so we
> can
> // write directly into it.
> @@ -1382,7 +1401,8 @@ template<typename T>
> output_length = ryu::d2fixed_buffered_n(value,
> effective_precision,
> buffer);
> __glibcxx_assert(output_length <= output_length_upper_bound);
> - if (last - first < output_length + excess_precision)
> + if (last - first < output_length
> + || last - first - output_length < excess_precision)
> return {last, errc::value_too_large};
> memcpy(first, buffer, output_length);
> }
> diff --git a/libstdc++-v3/testsuite/20_util/to_chars/103955.cc
> b/libstdc++-v3/testsuite/20_util/to_chars/103955.cc
> new file mode 100644
> index 00000000000..a47a0a5be8a
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/to_chars/103955.cc
> @@ -0,0 +1,31 @@
> +// PR libstdc++/103955
> +// Verify we don't crash when the floating-point to_chars overloads are
> passed
> +// a large precision argument.
> +
> +#include <charconv>
> +
> +#include <climits>
> +#include <initializer_list>
> +#include <testsuite_hooks.h>
> +
> +void
> +test01()
> +{
> + constexpr int size = 12;
> + char result[size];
> +
> + std::to_chars_result tcr;
> + for (auto fmt : { std::chars_format::fixed, std::chars_format::general,
> + std::chars_format::hex })
> + for (int precision : { INT_MAX, INT_MAX-1 })
> + {
> + tcr = std::to_chars(result, result+size, 1.337, fmt, precision);
> + VERIFY( tcr.ptr == result+size && tcr.ec ==
> std::errc::value_too_large );
> + }
> +}
> +
> +int
> +main()
> +{
> + test01();
> +}
> --
> 2.35.0.rc0
>
>
More information about the Libstdc++
mailing list