Using a huge width in a formatted output operation results in stack
overflow due to no restriction on the size passed to alloca. This patch
causes the formatting functions to use the heap instead of the stack
when a large buffer is needed.

        PR libstdc++/87228
        * include/bits/locale_facets.tcc (num_put:_M_insert_int): Use heap
        for large buffers instead of alloca.
        (num_put:_M_insert_float): Likewise.
        * testsuite/22_locale/num_put/put/char/87228.cc: New test.
        * testsuite/22_locale/num_put/put/wchar_t/87228.cc: New test.

Tested x86_64-linux.

Even with this patch we can still put 3kb on the stack, but that's
much better than trying (and failing) to use alloca for huge values.

An alternative would be to just check for silly values and throw
std::length_error, but I think this is the right fix.

I'll wait a day or two for any comments or better ideas.

commit 5b61805c1ccd106d2271dbf22663b898d2f03ab2
Author: Jonathan Wakely <jwak...@redhat.com>
Date:   Wed Sep 5 14:30:28 2018 +0100

    PR libstdc++/87228 Use heap for large buffers instead of alloca
    
    Using a huge width in a formatted output operation results in stack
    overflow due to no restriction on the size passed to alloca. This patch
    causes the formatting functions to use the heap instead of the stack
    when a large buffer is needed.
    
            PR libstdc++/87228
            * include/bits/locale_facets.tcc (num_put:_M_insert_int): Use heap
            for large buffers instead of alloca.
            (num_put:_M_insert_float): Likewise.
            * testsuite/22_locale/num_put/put/char/87228.cc: New test.
            * testsuite/22_locale/num_put/put/wchar_t/87228.cc: New test.

diff --git a/libstdc++-v3/include/bits/locale_facets.h 
b/libstdc++-v3/include/bits/locale_facets.h
index f6e0283fec9..b9a87aa38d5 100644
--- a/libstdc++-v3/include/bits/locale_facets.h
+++ b/libstdc++-v3/include/bits/locale_facets.h
@@ -2543,6 +2543,17 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
       do_put(iter_type, ios_base&, char_type, long double) const;
 #endif
       //@}
+
+    private:
+      template<typename _Tp>
+       struct _Char_buf {
+         _Char_buf() : _M_p(0) { }
+         _Char_buf(_Char_buf& __b) : _M_p(__b._M_p) { __b._M_p = 0; }
+         ~_Char_buf() { delete[] _M_p; }
+         _Tp* allocate(size_t __n) { return _M_p = new _Tp[__n]; }
+       private:
+         _Tp* _M_p;
+       };
     };
 
   template <typename _CharT, typename _OutIter>
diff --git a/libstdc++-v3/include/bits/locale_facets.tcc 
b/libstdc++-v3/include/bits/locale_facets.tcc
index 39da5766075..86c43d78d76 100644
--- a/libstdc++-v3/include/bits/locale_facets.tcc
+++ b/libstdc++-v3/include/bits/locale_facets.tcc
@@ -917,12 +917,17 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
              }
          }
 
+       _Char_buf<_CharT> __wbuf;
        // Pad.
        const streamsize __w = __io.width();
        if (__w > static_cast<streamsize>(__len))
          {
-           _CharT* __cs3 = static_cast<_CharT*>(__builtin_alloca(sizeof(_CharT)
-                                                                 * __w));
+           _CharT* __cs3;
+           const size_t __wlen = __w * sizeof(_CharT);
+           if (__wlen > 1024)
+             __cs3 = __wbuf.allocate(__w);
+           else
+             __cs3 = static_cast<_CharT*>(__builtin_alloca(__wlen));
            _M_pad(__fill, __w, __io, __cs3, __cs, __len);
            __cs = __cs3;
          }
@@ -992,6 +997,10 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
        char __fbuf[16];
        __num_base::_S_format_float(__io, __fbuf, __mod);
 
+       const size_t __max_alloca = 1024;
+       _Char_buf<char> __cbuf;
+       _Char_buf<_CharT> __wbuf;
+
 #if _GLIBCXX_USE_C99_STDIO
        // Precision is always used except for hexfloat format.
        const bool __use_prec =
@@ -1012,7 +1021,10 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
        if (__len >= __cs_size)
          {
            __cs_size = __len + 1;
-           __cs = static_cast<char*>(__builtin_alloca(__cs_size));
+           if (__cs_size > __max_alloca)
+             __cs = __cbuf.allocate(__cs_size);
+           else
+             __cs = static_cast<char*>(__builtin_alloca(__cs_size));
            if (__use_prec)
              __len = std::__convert_from_v(_S_get_c_locale(), __cs, __cs_size,
                                            __fbuf, __prec, __v);
@@ -1034,7 +1046,11 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
        // largely sufficient.
        const int __cs_size = __fixed ? __max_exp + __prec + 4
                                      : __max_digits * 2 + __prec;
-       char* __cs = static_cast<char*>(__builtin_alloca(__cs_size));
+       char* __cs;
+       if (__cs_size > __max_alloca)
+         __cs = __cbuf.allocate(__cs_size);
+       else
+         __cs = static_cast<char*>(__builtin_alloca(__cs_size));
        __len = std::__convert_from_v(_S_get_c_locale(), __cs, 0, __fbuf, 
                                      __prec, __v);
 #endif
@@ -1043,8 +1059,12 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
        // numpunct.decimal_point() values for '.' and adding grouping.
        const ctype<_CharT>& __ctype = use_facet<ctype<_CharT> >(__loc);
        
-       _CharT* __ws = static_cast<_CharT*>(__builtin_alloca(sizeof(_CharT)
-                                                            * __len));
+       _CharT* __ws;
+       size_t __wlen = sizeof(_CharT) * __len;
+       if (__wlen > __max_alloca)
+         __ws = __wbuf.allocate(__len);
+       else
+         __ws = static_cast<_CharT*>(__builtin_alloca(__wlen));
        __ctype.widen(__cs, __cs + __len, __ws);
        
        // Replace decimal point.
@@ -1063,10 +1083,15 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
            && (__wp || __len < 3 || (__cs[1] <= '9' && __cs[2] <= '9'
                                      && __cs[1] >= '0' && __cs[2] >= '0')))
          {
+           _Char_buf<_CharT> __wbuf2(__wbuf);
            // Grouping can add (almost) as many separators as the
            // number of digits, but no more.
-           _CharT* __ws2 = static_cast<_CharT*>(__builtin_alloca(sizeof(_CharT)
-                                                                 * __len * 2));
+           const size_t __wlen2 = __wlen * 2;
+           _CharT* __ws2;
+           if (__wlen2 > __max_alloca)
+             __ws2 = __wbuf.allocate(__len * 2);
+           else
+             __ws2 = static_cast<_CharT*>(__builtin_alloca(__wlen2));
            
            streamsize __off = 0;
            if (__cs[0] == '-' || __cs[0] == '+')
@@ -1088,8 +1113,13 @@ _GLIBCXX_BEGIN_NAMESPACE_LDBL
        const streamsize __w = __io.width();
        if (__w > static_cast<streamsize>(__len))
          {
-           _CharT* __ws3 = static_cast<_CharT*>(__builtin_alloca(sizeof(_CharT)
-                                                                 * __w));
+           _Char_buf<_CharT> __wbuf3(__wbuf);
+           const size_t __wlen3 = sizeof(_CharT) * __w;
+           _CharT* __ws3;
+           if (__wlen3 > __max_alloca)
+             __ws3 = __wbuf.allocate(__w);
+           else
+             __ws3 = static_cast<_CharT*>(__builtin_alloca(__wlen3));
            _M_pad(__fill, __w, __io, __ws3, __ws, __len);
            __ws = __ws3;
          }
diff --git a/libstdc++-v3/testsuite/22_locale/num_put/put/char/87228.cc 
b/libstdc++-v3/testsuite/22_locale/num_put/put/char/87228.cc
new file mode 100644
index 00000000000..0b363d2d266
--- /dev/null
+++ b/libstdc++-v3/testsuite/22_locale/num_put/put/char/87228.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2018 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <locale>
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  const std::size_t width = 1024 * 1024 * 8 + 1;
+  std::ostringstream out;
+  out << std::setw(width) << std::fixed;
+  out << std::numeric_limits<long double>::max();
+  VERIFY( out.str().length() == width );
+}
+
+void
+test02()
+{
+  const std::size_t width = 1024 * 1024 * 8 + 1;
+  std::ostringstream out;
+  out << std::setw(width);
+  out << std::numeric_limits<long long>::max();
+  VERIFY( out.str().length() == width );
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/22_locale/num_put/put/wchar_t/87228.cc 
b/libstdc++-v3/testsuite/22_locale/num_put/put/wchar_t/87228.cc
new file mode 100644
index 00000000000..fed4d065589
--- /dev/null
+++ b/libstdc++-v3/testsuite/22_locale/num_put/put/wchar_t/87228.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2018 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <locale>
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  const std::size_t width = 1024 * 1024 * 4 + 1;
+  std::wostringstream out;
+  out << std::setw(width) << std::fixed;
+  out << std::numeric_limits<long double>::max();
+  VERIFY( out.str().length() == width );
+}
+
+void
+test02()
+{
+  const std::size_t width = 1024 * 1024 * 4 + 1;
+  std::wostringstream out;
+  out << std::setw(width);
+  out << std::numeric_limits<long long>::max();
+  VERIFY( out.str().length() == width );
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}

Reply via email to