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();
+}

Reply via email to