On Tue, 11 Jan 2022 at 20:03, Patrick Palka via Libstdc++ <
libstd...@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
>
>

Reply via email to