From 6bdd58f73a33cceba4da5966c17c356ecc22b0c6 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Tue, 13 Nov 2018 22:57:44 +0000 Subject: [PATCH] Fix overflows in std::pmr::unsynchonized_pool_resource * 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 | 17 ++++ libstdc++-v3/src/c++17/memory_resource.cc | 60 +++++++++---- .../allocate-max-chunks.cc | 88 +++++++++++++++++++ 3 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index d904b45f7df..973470dfb76 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,20 @@ +2018-11-13 Jonathan Wakely + + * 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 * libsupc++/new (std::destroying_delete_t): New. diff --git a/libstdc++-v3/src/c++17/memory_resource.cc b/libstdc++-v3/src/c++17/memory_resource.cc index 3595e255889..fdbbc914f2e 100644 --- a/libstdc++-v3/src/c++17/memory_resource.cc +++ b/libstdc++-v3/src/c++17/memory_resource.cc @@ -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(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::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(__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 index 00000000000..f995fe9a47b --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc @@ -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 +// . + +// { dg-options "-std=gnu++17" } +// { dg-do run { target c++17 } } + +#include +#include + +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(); +}