Skip to content

[libc++] Fix constructing bitset from non-null-terminated arrays #143691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 12, 2025

Conversation

frederick-vs-ja
Copy link
Contributor

Unconditional evaluation of char_traits<_CharT>::length(__str) is problematic, because it causes UB when __str points to a non-null-terminated array. We should only call length (currently, in basic_string_view's constructor) when __n == npos per [bitset.cons]/8.

Drive-by change: Reduction of conditional compilation, given that

  • both basic_string_view<_CharT>::size_type and basic_string<_CharT>::size_type must be size_t, and thus
  • both basic_string_view<_CharT>::npos and basic_string<_CharT>::npos must be size_t(-1).

For the type sameness in the standard wording, see:

  • [string.view.template.general]
  • [basic.string.general]
  • [allocator.traits.types]/6
  • [default.allocator.general]/1

Fixes #143684.

Unconditional evaluation of `char_traits<_CharT>::length(__str)` is
problematic, because it causes UB when `__str` points to a
non-null-terminated array. We should only call `length` (currently, in
`basic_string_view`'s constructor) when `__n == npos` per
[bitset.cons]/8.

Drive-by change: Reduction of conditional compilation, given that
- both `basic_string_view<_CharT>::size_type` and
`basic_string<_CharT>::size_type` must be `size_t`, and thus
- both `basic_string_view<_CharT>::npos` and
`basic_string<_CharT>::npos` must be `size_t(-1)`.

For the type sameness in the standard wording, see:
- [string.view.template.general]
- [basic.string.general]
- [allocator.traits.types]/6
- [default.allocator.general]/1
@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner June 11, 2025 12:19
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jun 11, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 11, 2025

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

Changes

Unconditional evaluation of char_traits&lt;_CharT&gt;::length(__str) is problematic, because it causes UB when __str points to a non-null-terminated array. We should only call length (currently, in basic_string_view's constructor) when __n == npos per [bitset.cons]/8.

Drive-by change: Reduction of conditional compilation, given that

  • both basic_string_view&lt;_CharT&gt;::size_type and basic_string&lt;_CharT&gt;::size_type must be size_t, and thus
  • both basic_string_view&lt;_CharT&gt;::npos and basic_string&lt;_CharT&gt;::npos must be size_t(-1).

For the type sameness in the standard wording, see:

  • [string.view.template.general]
  • [basic.string.general]
  • [allocator.traits.types]/6
  • [default.allocator.general]/1

Fixes #143684.


Full diff: https://github.com/llvm/llvm-project/pull/143691.diff

2 Files Affected:

  • (modified) libcxx/include/bitset (+5-8)
  • (modified) libcxx/test/std/utilities/template.bitset/bitset.cons/char_ptr_ctor.pass.cpp (+28)
diff --git a/libcxx/include/bitset b/libcxx/include/bitset
index 88dc0e08c995d..8f2c0611b6436 100644
--- a/libcxx/include/bitset
+++ b/libcxx/include/bitset
@@ -645,16 +645,13 @@ public:
   template <class _CharT, __enable_if_t<_IsCharLikeType<_CharT>::value, int> = 0>
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 explicit bitset(
       const _CharT* __str,
-#  if _LIBCPP_STD_VER >= 26
-      typename basic_string_view<_CharT>::size_type __n = basic_string_view<_CharT>::npos,
-#  else
-      typename basic_string<_CharT>::size_type __n = basic_string<_CharT>::npos,
-#  endif
+      size_t __n    = static_cast<size_t>(-1),
       _CharT __zero = _CharT('0'),
       _CharT __one  = _CharT('1')) {
-
-    size_t __rlen = std::min(__n, char_traits<_CharT>::length(__str));
-    __init_from_string_view(basic_string_view<_CharT>(__str, __rlen), __zero, __one);
+    if (__n == static_cast<size_t>(-1))
+      __init_from_string_view(basic_string_view<_CharT>(__str), __zero, __one);
+    else
+      __init_from_string_view(basic_string_view<_CharT>(__str, __n), __zero, __one);
   }
 #  if _LIBCPP_STD_VER >= 26
   template <class _CharT, class _Traits>
diff --git a/libcxx/test/std/utilities/template.bitset/bitset.cons/char_ptr_ctor.pass.cpp b/libcxx/test/std/utilities/template.bitset/bitset.cons/char_ptr_ctor.pass.cpp
index 86b144ed87b70..8ad7d513ea7c5 100644
--- a/libcxx/test/std/utilities/template.bitset/bitset.cons/char_ptr_ctor.pass.cpp
+++ b/libcxx/test/std/utilities/template.bitset/bitset.cons/char_ptr_ctor.pass.cpp
@@ -72,6 +72,34 @@ TEST_CONSTEXPR_CXX23 void test_char_pointer_ctor()
     for (std::size_t i = 10; i < v.size(); ++i)
         assert(v[i] == false);
   }
+  // See https://github.com/llvm/llvm-project/issues/143684
+  {
+    const char not_null_terminated[] = {'1', '0', '1', '0', '1', '0', '1', '0', '1', '0'};
+    std::bitset<N> v(not_null_terminated, 10);
+    std::size_t M = std::min<std::size_t>(v.size(), 10);
+    for (std::size_t i = 0; i < M; ++i)
+      assert(v[i] == (not_null_terminated[M - 1 - i] == '1'));
+    for (std::size_t i = 10; i < v.size(); ++i)
+      assert(v[i] == false);
+  }
+  {
+    const char not_null_terminated[] = {'1', 'a', '1', 'a', '1', 'a', '1', 'a', '1', 'a'};
+    std::bitset<N> v(not_null_terminated, 10, 'a');
+    std::size_t M = std::min<std::size_t>(v.size(), 10);
+    for (std::size_t i = 0; i < M; ++i)
+      assert(v[i] == (not_null_terminated[M - 1 - i] == '1'));
+    for (std::size_t i = 10; i < v.size(); ++i)
+      assert(v[i] == false);
+  }
+  {
+    const char not_null_terminated[] = {'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a'};
+    std::bitset<N> v(not_null_terminated, 10, 'a', 'b');
+    std::size_t M = std::min<std::size_t>(v.size(), 10);
+    for (std::size_t i = 0; i < M; ++i)
+      assert(v[i] == (not_null_terminated[M - 1 - i] == 'b'));
+    for (std::size_t i = 10; i < v.size(); ++i)
+      assert(v[i] == false);
+  }
 }
 
 TEST_CONSTEXPR_CXX23 bool test() {

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM % nit assuming the CI is green.

@ldionne
Copy link
Member

ldionne commented Jun 12, 2025

CI seems to be as green as it'll ever be in the current conditions. Merging.

@ldionne ldionne merged commit bba4ded into llvm:main Jun 12, 2025
170 of 180 checks passed
@frederick-vs-ja frederick-vs-ja deleted the bitset-from-data-size branch June 12, 2025 23:18
tomtor pushed a commit to tomtor/llvm-project that referenced this pull request Jun 14, 2025
…lvm#143691)

Unconditional evaluation of `char_traits<_CharT>::length(__str)` is problematic, because it causes
UB when `__str` points to a non-null-terminated array. We should only call `length` (currently, in
`basic_string_view`'s constructor) when `__n == npos` per [bitset.cons]/8.

Drive-by change: Reduction of conditional compilation, given that
- both `basic_string_view<_CharT>::size_type` and `basic_string<_CharT>::size_type` must be 
  `size_t`, and thus
- both `basic_string_view<_CharT>::npos` and `basic_string<_CharT>::npos` must be `size_t(-1)`.

For the type sameness in the standard wording, see:
- [string.view.template.general]
- [basic.string.general]
- [allocator.traits.types]/6
- [default.allocator.general]/1

Fixes llvm#143684
akuhlens pushed a commit to akuhlens/llvm-project that referenced this pull request Jun 24, 2025
…lvm#143691)

Unconditional evaluation of `char_traits<_CharT>::length(__str)` is problematic, because it causes
UB when `__str` points to a non-null-terminated array. We should only call `length` (currently, in
`basic_string_view`'s constructor) when `__n == npos` per [bitset.cons]/8.

Drive-by change: Reduction of conditional compilation, given that
- both `basic_string_view<_CharT>::size_type` and `basic_string<_CharT>::size_type` must be 
  `size_t`, and thus
- both `basic_string_view<_CharT>::npos` and `basic_string<_CharT>::npos` must be `size_t(-1)`.

For the type sameness in the standard wording, see:
- [string.view.template.general]
- [basic.string.general]
- [allocator.traits.types]/6
- [default.allocator.general]/1

Fixes llvm#143684
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[libc++] Out-of-bounds read in std::bitset constructor from char*
4 participants