https://gcc.gnu.org/g:2fd8866591b5aea7098835f80ab7e176382175ae
commit r17-5739-g2fd8866591b5aea7098835f80ab7e176382175ae Author: Jonathan Wakely <[email protected]> Date: Mon Oct 26 17:21:01 2026 +0000 libstdc++: Fix overflow in std::vector<bool> constructors (CVE-2026-114691) Some std::vector<bool> constructors pass a user-provided size (or a distance derived from user-provided range) to the _S_nword function which does addition to find the number of words (of type unsigned long) that are needed for that number of bits. If the size is close to SIZE_MAX the addition wraps to zero and the number of words needed is incorrect. This adds a new function for initializing from user-provided sizes which checks against max_size() so that out-of-range values are rejected before any call to _S_nword. The new function rejects negative values as well as large ones. For an invalid range the result of ranges::distance or std::distance could be a very large negative value that would become smaller than max_size() when converted to size_t. This detects some invalid inputs (last before first) at minimal cost. libstdc++-v3/ChangeLog: * include/bits/stl_bvector.h (_M_check_and_initialize): New function. (vector(size_type, const bool&, const allocator_type&)) (vector(from_range_t, R&&, const Alloc&)) (_M_initialize_dispatch, _M_initialize_range): Use new function instead of _M_initialize. (_M_initialize): Add debug assertion. * testsuite/23_containers/vector/bool/prX.cc: New test. Reviewed-by: Tomasz KamiĆski <[email protected]> Diff: --- libstdc++-v3/include/bits/stl_bvector.h | 24 +++- .../testsuite/23_containers/vector/bool/prX.cc | 160 +++++++++++++++++++++ 2 files changed, 179 insertions(+), 5 deletions(-) diff --git a/libstdc++-v3/include/bits/stl_bvector.h b/libstdc++-v3/include/bits/stl_bvector.h index 3cdb48f3983c..2092c98cadcc 100644 --- a/libstdc++-v3/include/bits/stl_bvector.h +++ b/libstdc++-v3/include/bits/stl_bvector.h @@ -718,6 +718,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER { _M_impl._M_move_data(std::move(__x._M_impl)); } #endif + // Caller must ensure the addition won't overflow. _GLIBCXX_CONSTEXPR static size_t _S_nword(size_t __n) @@ -814,7 +815,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER #endif : _Base(__a) { - _M_initialize(__n); + _M_check_and_initialize(__n); _M_initialize_value(__value); } @@ -912,7 +913,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER { if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>) { - _M_initialize(size_type(ranges::distance(__rg))); + _M_check_and_initialize(ranges::distance(__rg)); ranges::copy(__rg, begin()); } else @@ -1557,6 +1558,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER void _M_initialize(size_type __n) { + // Caller must ensure this, to avoid overflow in _S_nword. + _GLIBCXX_DEBUG_ASSERT(__n <= (size_t)-_S_word_bit); + if (__n) { _Bit_pointer __q = this->_M_allocate(__n); @@ -1567,6 +1571,17 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER } } + template<typename _SizeT> + _GLIBCXX20_CONSTEXPR + void + _M_check_and_initialize(_SizeT __n) + { + if (__n > max_size() || __n < 0) + __throw_length_error( + __N("Cannot create std::vector<bool> larger than max_size()")); + _M_initialize(static_cast<size_type>(__n)); + } + _GLIBCXX20_CONSTEXPR void _M_initialize_value(bool __x) _GLIBCXX_NOEXCEPT @@ -1592,7 +1607,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER void _M_initialize_dispatch(_Integer __n, _Integer __x, __true_type) { - _M_initialize(static_cast<size_type>(__n)); + _M_check_and_initialize(static_cast<size_type>(__n)); _M_initialize_value(__x); } @@ -1620,8 +1635,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER _M_initialize_range(_ForwardIterator __first, _ForwardIterator __last, std::forward_iterator_tag) { - const size_type __n = std::distance(__first, __last); - _M_initialize(__n); + _M_check_and_initialize(std::distance(__first, __last)); std::copy(__first, __last, begin()); } diff --git a/libstdc++-v3/testsuite/23_containers/vector/bool/prX.cc b/libstdc++-v3/testsuite/23_containers/vector/bool/prX.cc new file mode 100644 index 000000000000..bc97b9bfde5f --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/vector/bool/prX.cc @@ -0,0 +1,160 @@ +// { dg-do run } + +#include <vector> +#include <ranges> +#include <stdexcept> +#include <testsuite_hooks.h> + +static void +test_n_element_ctor() +{ + try { + std::vector<bool> v(-15, true); + VERIFY(false); + } catch (const std::length_error&) { + } +} + +static void +test_n_element_ctor_dispatch() +{ + try { + std::vector<bool> v(-15, 1); // in C++98 this calls vector(Iter, Iter) ctor + VERIFY(false); + } catch (const std::length_error&) { + } +} + +// A Cpp17RandomAccessIterator for which std::distance always returns an +// impossibly large distance. This implements the Cpp17RandomAccessIterator +// API but doesn't meet the semantic requirements. +// For the test below, all that matters is that the huge value from +// std::distance(first, last) causes std::length_error to be thrown. +struct FakeIterator +{ + typedef std::random_access_iterator_tag iterator_category; + typedef bool value_type; + typedef std::size_t difference_type; // should be a signed type + typedef const bool* pointer; + typedef const bool& reference; + + FakeIterator() : val(true) { } + + reference operator*() const { return val; } + pointer operator->() const { return &val; } + reference operator[](difference_type) const { return val; } + + FakeIterator& operator++() { return *this; } + FakeIterator operator++(int) { return *this; } + FakeIterator& operator--() { return *this; } + FakeIterator operator--(int) { return *this; } + + FakeIterator& operator+=(difference_type) { return *this; } + FakeIterator& operator-=(difference_type) { return *this; } + + FakeIterator operator+(difference_type) const { return *this; } + FakeIterator operator-(difference_type) const { return *this; } + + friend FakeIterator operator+(difference_type, FakeIterator i) { return i; } + + // Return an impossibly large distance + difference_type operator-(FakeIterator) const { return (std::size_t)-8; } + + // These are meaningless results but not actually used by this test. + bool operator==(const FakeIterator&) const { return false; } + bool operator!=(const FakeIterator&) const { return true; } + bool operator<(const FakeIterator&) const { return true; } + bool operator>(const FakeIterator&) const { return false; } + bool operator<=(const FakeIterator&) const { return true; } + bool operator>=(const FakeIterator&) const { return false; } + +private: + bool val; +}; + +#ifdef __cpp_lib_ranges +// Wraps a std::random_access_iterator as a Cpp17RandomAccessIterator +// except for having an integer-like class type as difference_type. +// This one is _almost_ a real Cpp17RandomAccessIterator, except for the +// difference_type. Again, we only care that the huge distance is +// detected and std::length_error is thrown. +class Iterator +{ + using View = std::ranges::iota_view<std::size_t, std::size_t>; + using ViewIter = std::ranges::iterator_t<View>; + +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = bool; + using difference_type = std::iter_difference_t<ViewIter>; + + Iterator() = default; + + explicit Iterator(ViewIter iter) : iter(iter) { } + + const bool& operator*() const { return val; } + const bool* operator->() const { return &val; } + const bool& operator[](difference_type n) const { return *(*this + n); } + + Iterator& operator++() { ++iter; return *this; } + Iterator operator++(int) { return Iterator(iter++); } + Iterator& operator--() { --iter; return *this; } + Iterator operator--(int) { return Iterator(iter--); } + + Iterator& operator+=(difference_type n) { iter += n; return *this; } + Iterator& operator-=(difference_type n) { iter -= n; return *this; } + + Iterator operator+(difference_type n) const { return Iterator(iter + n); } + Iterator operator-(difference_type n) const { return Iterator(iter - n); } + + friend Iterator operator+(difference_type n, Iterator i) { return i + n; } + difference_type operator-(Iterator i) const { return iter - i.iter; } + + bool operator==(const Iterator& i) const { return iter == i.iter; } + auto operator<=>(const Iterator& i) const { return iter <=> i.iter; } + +private: + ViewIter iter = ViewIter(); + bool val = true; +}; + +static_assert( std::random_access_iterator<Iterator> ); +#endif + +static void +test_range_ctors() +{ + try { + FakeIterator x; + std::vector<bool> v(x, x); + VERIFY(false); + } catch (const std::length_error&) { + } + +#ifdef __cpp_lib_ranges + const std::size_t min = 0, max = -8; + auto i = std::views::iota(min, max); + try { + Iterator first(std::ranges::begin(i)); + Iterator last(std::ranges::end(i)); + std::vector<bool> v(first, last); + VERIFY(false); + } catch (const std::length_error&) { + } + +#ifdef __cpp_lib_containers_ranges + try { + std::vector<bool> v(std::from_range, i); + VERIFY(false); + } catch (const std::length_error&) { + } +#endif +#endif +} + +int main() +{ + test_n_element_ctor(); + test_n_element_ctor_dispatch(); + test_range_ctors(); +}
