Fix overflows in std::pmr::unsynchonized_pool_resource
authorJonathan Wakely <jwakely@redhat.com>
Tue, 13 Nov 2018 22:57:44 +0000 (22:57 +0000)
committerJonathan Wakely <redi@gcc.gnu.org>
Tue, 13 Nov 2018 22:57:44 +0000 (22:57 +0000)
* src/c++17/memory_resource.cc (bitset::full()): Handle edge case
for _M_next_word maximum value.
(bitset::get_first_unset(), bitset::set(size_type)): Use
update_next_word() to update _M_next_word.
(bitset::update_next_word()): New function, avoiding wraparound of
unsigned _M_next_word member.
(bitset::max_word_index()): New function.
(chunk::chunk(void*, uint32_t, void*, size_t)): Add assertion.
(chunk::max_bytes_per_chunk()): New function.
(pool::replenish(memory_resource*, const pool_options&)): Prevent
_M_blocks_per_chunk from exceeding max_blocks_per_chunk or from
causing chunk::max_bytes_per_chunk() to be exceeded.
* testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc:
New test.

From-SVN: r266087

libstdc++-v3/ChangeLog
libstdc++-v3/src/c++17/memory_resource.cc
libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc [new file with mode: 0644]

index d904b45f7dfcf4fb82e75dc4845f1ea48947710a..973470dfb76e35f16931e5ace06070663a2bfd3d 100644 (file)
@@ -1,3 +1,20 @@
+2018-11-13  Jonathan Wakely  <jwakely@redhat.com>
+
+       * src/c++17/memory_resource.cc (bitset::full()): Handle edge case
+       for _M_next_word maximum value.
+       (bitset::get_first_unset(), bitset::set(size_type)): Use
+       update_next_word() to update _M_next_word.
+       (bitset::update_next_word()): New function, avoiding wraparound of
+       unsigned _M_next_word member.
+       (bitset::max_word_index()): New function.
+       (chunk::chunk(void*, uint32_t, void*, size_t)): Add assertion.
+       (chunk::max_bytes_per_chunk()): New function.
+       (pool::replenish(memory_resource*, const pool_options&)): Prevent
+       _M_blocks_per_chunk from exceeding max_blocks_per_chunk or from
+       causing chunk::max_bytes_per_chunk() to be exceeded.
+       * testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc:
+       New test.
+
 2018-11-12  Jason Merrill  <jason@redhat.com>
 
        * libsupc++/new (std::destroying_delete_t): New.
index 3595e255889c54445123d94dd9ffabff1151cf37..fdbbc914f2e21284b56cf7a90061199715093e9e 100644 (file)
@@ -290,7 +290,18 @@ namespace pmr
     }
 
     // True if all bits are set
-    bool full() const noexcept { return _M_next_word >= nwords(); }
+    bool full() const noexcept
+    {
+      if (_M_next_word >= nwords())
+       return true;
+      // For a bitset with size() > (max_blocks_per_chunk() - 64) we will
+      // have nwords() == (max_word_index() + 1) and so _M_next_word will
+      // never be equal to nwords().
+      // In that case, check if the last word is full:
+      if (_M_next_word == max_word_index())
+       return _M_words[_M_next_word] == word(-1);
+      return false;
+    }
 
     // True if size() != 0 and no bits are set.
     bool empty() const noexcept
@@ -343,11 +354,7 @@ namespace pmr
              const word bit = word(1) << n;
              _M_words[i] |= bit;
              if (i == _M_next_word)
-               {
-                 while (_M_words[_M_next_word] == word(-1)
-                     && ++_M_next_word != nwords())
-                   { }
-               }
+               update_next_word();
              return (i * bits_per_word) + n;
            }
        }
@@ -361,11 +368,7 @@ namespace pmr
       const word bit = word(1) << (n % bits_per_word);
       _M_words[wd] |= bit;
       if (wd == _M_next_word)
-       {
-         while (_M_words[_M_next_word] == word(-1)
-             && ++_M_next_word != nwords())
-           { }
-       }
+       update_next_word();
     }
 
     void clear(size_type n) noexcept
@@ -378,6 +381,18 @@ namespace pmr
        _M_next_word = wd;
     }
 
+    // Update _M_next_word to refer to the next word with an unset bit.
+    // The size of the _M_next_word bit-field means it cannot represent
+    // the maximum possible nwords() value. To avoid wraparound to zero
+    // this function saturates _M_next_word at max_word_index().
+    void update_next_word() noexcept
+    {
+      size_t next = _M_next_word;
+      while (_M_words[next] == word(-1) && ++next < nwords())
+       { }
+      _M_next_word = std::min(next, max_word_index());
+    }
+
     void swap(bitset& b) noexcept
     {
       std::swap(_M_words, b._M_words);
@@ -396,6 +411,10 @@ namespace pmr
     static constexpr size_t max_blocks_per_chunk() noexcept
     { return (1ull << _S_size_digits) - 1; }
 
+    // Maximum value that can be stored in bitset::_M_next_word member (8191).
+    static constexpr size_t max_word_index() noexcept
+    { return (max_blocks_per_chunk() + bits_per_word - 1) / bits_per_word; }
+
     word* data() const noexcept { return _M_words; }
 
   private:
@@ -425,7 +444,7 @@ namespace pmr
     : bitset(words, n),
       _M_bytes(bytes),
       _M_p(static_cast<std::byte*>(p))
-    { }
+    { __glibcxx_assert(bytes <= chunk::max_bytes_per_chunk()); }
 
     chunk(chunk&& c) noexcept
     : bitset(std::move(c)), _M_bytes(c._M_bytes), _M_p(c._M_p)
@@ -451,6 +470,9 @@ namespace pmr
     // Number of blocks in this chunk
     using bitset::size;
 
+    static constexpr uint32_t max_bytes_per_chunk() noexcept
+    { return numeric_limits<decltype(_M_bytes)>::max(); }
+
     // Determine if block with address p and size block_size
     // is contained within this chunk.
     bool owns(void* p, size_t block_size)
@@ -639,8 +661,7 @@ namespace pmr
     void replenish(memory_resource* __r, const pool_options& __opts)
     {
       using word = chunk::word;
-      const size_t __blocks
-       = std::min<size_t>(__opts.max_blocks_per_chunk, _M_blocks_per_chunk);
+      const size_t __blocks = _M_blocks_per_chunk;
       const auto __bits = chunk::bits_per_word;
       const size_t __words = (__blocks + __bits - 1) / __bits;
       const size_t __block_size = block_size();
@@ -658,7 +679,16 @@ namespace pmr
          __r->deallocate(__p, __bytes, __alignment);
        }
       if (_M_blocks_per_chunk < __opts.max_blocks_per_chunk)
-       _M_blocks_per_chunk *= 2;
+       {
+         const size_t max_blocks
+           = (chunk::max_bytes_per_chunk() - sizeof(word))
+           / (__block_size + 0.125);
+         _M_blocks_per_chunk = std::min({
+             max_blocks,
+             __opts.max_blocks_per_chunk,
+             (size_t)_M_blocks_per_chunk * 2
+         });
+       }
     }
 
     void release(memory_resource* __r)
diff --git a/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc
new file mode 100644 (file)
index 0000000..f995fe9
--- /dev/null
@@ -0,0 +1,88 @@
+// 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/>.
+
+// { dg-options "-std=gnu++17" }
+// { dg-do run { target c++17 } }
+
+#include <memory_resource>
+#include <testsuite_hooks.h>
+
+struct custom_mr : std::pmr::memory_resource
+{
+  custom_mr(std::size_t max) : max(max) { }
+
+  bool reached_max = false;
+
+private:
+  std::size_t max;
+  std::size_t count = 0;
+
+  void* do_allocate(std::size_t b, std::size_t a)
+  {
+    if (b >= max)
+      reached_max = true;
+    count += b;
+    if (count > (18 * 1024 * 1024))
+      // Something went wrong, should not need to allocate this much.
+      throw std::bad_alloc();
+    return std::pmr::new_delete_resource()->allocate(b, a);
+  }
+
+  void do_deallocate(void* p, std::size_t b, std::size_t a)
+  { std::pmr::new_delete_resource()->deallocate(p, b, a); }
+
+  bool do_is_equal(const memory_resource& r) const noexcept
+  { return false; }
+};
+
+void
+test01()
+{
+  // Only going to allocate blocks of this size:
+  const std::size_t block_size = 8;
+  std::pmr::pool_options opts{};
+  // Use maximum allowed number of blocks per chunk:
+  opts.max_blocks_per_chunk = (std::size_t)-1;
+  opts.largest_required_pool_block = block_size;
+  {
+    std::pmr::unsynchronized_pool_resource r(opts);
+    // Get the real max_blocks_per_chunk that will be used:
+    opts = r.options();
+    // Sanity test in case chunk::max_blocks_per_chunk() changes,
+    // as that could make this test take much longer to run:
+    VERIFY( opts.max_blocks_per_chunk <= (1 << 19) );
+  }
+  custom_mr c(block_size * opts.max_blocks_per_chunk);
+  std::pmr::unsynchronized_pool_resource r(opts, &c);
+  // Keep allocating from the pool until reaching the maximum chunk size:
+  while (!c.reached_max)
+    (void) r.allocate(block_size, 1);
+  c.reached_max = false;
+  // Now fill that maximally-sized chunk
+  // (this used to go into an infinite loop ):
+  for (std::size_t i = 0; i < opts.max_blocks_per_chunk; ++i)
+    (void) r.allocate(block_size, 1);
+  // Should have filled the maximally-sized chunk and allocated another
+  // maximally-sized chunk from upstream:
+  VERIFY( c.reached_max );
+}
+
+int
+main()
+{
+  test01();
+}