Skip to content
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

[ASan][libc++] Turn on ASan annotations for short strings #79536

Merged
merged 2 commits into from
May 7, 2024

Conversation

AdvenamTacet
Copy link
Member

@AdvenamTacet AdvenamTacet commented Jan 26, 2024

This pull request is the third iteration aiming to integrate short string annotations. This commit includes:

  • Enabling basic_string annotations for short strings.
  • Setting a value of __trivially_relocatable in std::basic_string to false_type when compiling with ASan (nothing changes when compiling without ASan). Short string annotations make std::basic_string to not be trivially relocatable, because memory has to be unpoisoned.
  • Adding a _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS modifier to two functions.
  • Creating a macro _LIBCPP_ASAN_VOLATILE_WRAPPER to prevent problematic stack optimizations (the macro modifies code behavior only when compiling with ASan).

Previously we had issues with compiler optimization, which we understand thanks to @vitalybuka. This commit also addresses smaller changes in short string, since previous upstream attempts.

Problematic optimization was loading two values in code similar to:

__is_long() ? __get_long_size() : __get_short_size();

We aim to resolve it with the volatile wrapper.

This commit is built on top of two previous attempts which descriptions are below.

Additionally, in the meantime, annotations were updated (but it shouldn't have any impact on anything):


Previous PR: #79049
Reverted: a16f81f

Previous description:

Originally merged here: #75882
Reverted here: #78627

Reverted due to failing buildbots. The problem was not caused by the annotations code, but by code in the UniqueFunctionBase class and in the JSON.h file. That code caused the program to write to memory that was already being used by string objects, which resulted in an ASan error.

Fixes are implemented in:

Problematic code from UniqueFunctionBase for example:

    // In debug builds, we also scribble across the rest of the storage.
    memset(RHS.getInlineStorage(), 0xAD, InlineStorageSize);

Original description:

This commit turns on ASan annotations in std::basic_string for short stings (SSO case).

Originally suggested here: https://reviews.llvm.org/D147680

String annotations added here: #72677

Requires to pass CI without fails:

Annotating std::basic_string with default allocator is implemented in #72677 but annotations for short strings (SSO - Short String Optimization) are turned off there. This commit turns them on. This also removes _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED, because we do not plan to support turning on and off short string annotations.

Support in ASan API exists since dd1b7b7. You can turn off annotations for a specific allocator based on changes from 2fa1bec.

This PR is a part of a series of patches extending AddressSanitizer C++ container overflow detection capabilities by adding annotations, similar to those existing in std::vector and std::deque collections. These enhancements empower ASan to effectively detect instances where the instrumented program attempts to access memory within a collection's internal allocation that remains unused. This includes cases where access occurs before or after the stored elements in std::deque, or between the std::basic_string's size (including the null terminator) and capacity bounds.

The introduction of these annotations was spurred by a real-world software bug discovered by Trail of Bits, involving an out-of-bounds memory access during the comparison of two strings using the std::equals function. This function was taking iterators (iter1_begin, iter1_end, iter2_begin) to perform the comparison, using a custom comparison function. When the iter1 object exceeded the length of iter2, an out-of-bounds read could occur on the iter2 object. Container sanitization, upon enabling these annotations, would effectively identify and flag this potential vulnerability.

If you have any questions, please email:

@AdvenamTacet AdvenamTacet added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jan 26, 2024
@AdvenamTacet AdvenamTacet requested a review from a team as a code owner January 26, 2024 01:43
@llvmbot
Copy link
Collaborator

llvmbot commented Jan 26, 2024

@llvm/pr-subscribers-libcxx

Author: Tacet (AdvenamTacet)

Changes

This is 3rd attempt to upstream short string annotations, it's the same as the previous one, but other PRs fixed issues withing LLVM:

Now, as far as I know, all buildbots should work without problems. Both previous reverts were not related to issues with string annotations, but with issues in LLVM/clang. Read PRs above and below for details.

Additionaly annotations were updated (but it shouldn't have any impact on anything):


Previous description:

Originally merged here: #75882
Reverted here: #78627

Reverted due to failing buildbots. The problem was not caused by the annotations code, but by code in the UniqueFunctionBase class and in the JSON.h file. That code caused the program to write to memory that was already being used by string objects, which resulted in an ASan error.

Fixes are implemented in:

Problematic code from UniqueFunctionBase for example:

    // In debug builds, we also scribble across the rest of the storage.
    memset(RHS.getInlineStorage(), 0xAD, InlineStorageSize);

Original description:

This commit turns on ASan annotations in std::basic_string for short stings (SSO case).

Originally suggested here: https://reviews.llvm.org/D147680

String annotations added here: #72677

Requires to pass CI without fails:

Annotating std::basic_string with default allocator is implemented in #72677 but annotations for short strings (SSO - Short String Optimization) are turned off there. This commit turns them on. This also removes _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED, because we do not plan to support turning on and off short string annotations.

Support in ASan API exists since dd1b7b7. You can turn off annotations for a specific allocator based on changes from 2fa1bec.

This PR is a part of a series of patches extending AddressSanitizer C++ container overflow detection capabilities by adding annotations, similar to those existing in std::vector and std::deque collections. These enhancements empower ASan to effectively detect instances where the instrumented program attempts to access memory within a collection's internal allocation that remains unused. This includes cases where access occurs before or after the stored elements in std::deque, or between the std::basic_string's size (including the null terminator) and capacity bounds.

The introduction of these annotations was spurred by a real-world software bug discovered by Trail of Bits, involving an out-of-bounds memory access during the comparison of two strings using the std::equals function. This function was taking iterators (iter1_begin, iter1_end, iter2_begin) to perform the comparison, using a custom comparison function. When the iter1 object exceeded the length of iter2, an out-of-bounds read could occur on the iter2 object. Container sanitization, upon enabling these annotations, would effectively identify and flag this potential vulnerability.

If you have any questions, please email:


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

5 Files Affected:

  • (modified) libcxx/include/string (+4-10)
  • (added) libcxx/test/libcxx/containers/strings/basic.string/asan_deque_integration.pass.cpp (+182)
  • (added) libcxx/test/libcxx/containers/strings/basic.string/asan_short.pass.cpp (+56)
  • (added) libcxx/test/libcxx/containers/strings/basic.string/asan_vector_integration.pass.cpp (+182)
  • (modified) libcxx/test/support/asan_testing.h (+5-24)
diff --git a/libcxx/include/string b/libcxx/include/string
index c5c245fa297d353..200ac207969a5cd 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -659,7 +659,6 @@ _LIBCPP_PUSH_MACROS
 #else
 #  define _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS
 #endif
-#define _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED false
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
@@ -1896,22 +1895,17 @@ private:
 #endif
   }
 
-  // ASan: short string is poisoned if and only if this function returns true.
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __asan_short_string_is_annotated() const _NOEXCEPT {
-    return _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED && !__libcpp_is_constant_evaluated();
-  }
-
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_new(size_type __current_size) const _NOEXCEPT {
     (void) __current_size;
 #if !defined(_LIBCPP_HAS_NO_ASAN) && defined(_LIBCPP_INSTRUMENTED_WITH_ASAN)
-    if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long()))
+    if (!__libcpp_is_constant_evaluated())
       __annotate_contiguous_container(data() + capacity() + 1, data() + __current_size + 1);
 #endif
   }
 
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_delete() const _NOEXCEPT {
 #if !defined(_LIBCPP_HAS_NO_ASAN) && defined(_LIBCPP_INSTRUMENTED_WITH_ASAN)
-    if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long()))
+    if (!__libcpp_is_constant_evaluated())
       __annotate_contiguous_container(data() + size() + 1, data() + capacity() + 1);
 #endif
   }
@@ -1919,7 +1913,7 @@ private:
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_increase(size_type __n) const _NOEXCEPT {
     (void) __n;
 #if !defined(_LIBCPP_HAS_NO_ASAN) && defined(_LIBCPP_INSTRUMENTED_WITH_ASAN)
-    if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long()))
+    if (!__libcpp_is_constant_evaluated())
       __annotate_contiguous_container(data() + size() + 1, data() + size() + 1 + __n);
 #endif
   }
@@ -1927,7 +1921,7 @@ private:
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_shrink(size_type __old_size) const _NOEXCEPT {
     (void) __old_size;
 #if !defined(_LIBCPP_HAS_NO_ASAN) && defined(_LIBCPP_INSTRUMENTED_WITH_ASAN)
-    if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long()))
+    if (!__libcpp_is_constant_evaluated())
       __annotate_contiguous_container(data() + __old_size + 1, data() + size() + 1);
 #endif
   }
diff --git a/libcxx/test/libcxx/containers/strings/basic.string/asan_deque_integration.pass.cpp b/libcxx/test/libcxx/containers/strings/basic.string/asan_deque_integration.pass.cpp
new file mode 100644
index 000000000000000..1205190b3a6e131
--- /dev/null
+++ b/libcxx/test/libcxx/containers/strings/basic.string/asan_deque_integration.pass.cpp
@@ -0,0 +1,182 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: asan
+// UNSUPPORTED: c++03
+
+#include <cassert>
+#include <string>
+#include <array>
+#include <deque>
+#include "test_macros.h"
+#include "asan_testing.h"
+#include "min_allocator.h"
+
+// This tests exists to check if strings work well with deque, as those
+// may be partialy annotated, we cannot simply call
+// is_double_ended_contiguous_container_asan_correct, as it assumes that
+// object memory inside is not annotated, so we check everything in a more careful way.
+
+template <typename D>
+void verify_inside(D const& d) {
+  for (size_t i = 0; i < d.size(); ++i) {
+    assert(is_string_asan_correct(d[i]));
+  }
+}
+
+template <typename S, size_t N>
+S get_s(char c) {
+  S s;
+  for (size_t i = 0; i < N; ++i)
+    s.push_back(c);
+
+  return s;
+}
+
+template <class C, class S>
+void test_string() {
+  size_t const N = sizeof(S) < 256 ? (4096 / sizeof(S)) : 16;
+
+  {
+    C d1a(1), d1b(N), d1c(N + 1), d1d(5 * N);
+    verify_inside(d1a);
+    verify_inside(d1b);
+    verify_inside(d1c);
+    verify_inside(d1d);
+  }
+  {
+    C d2;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      d2.push_back(get_s<S, 1>(i % 10 + 'a'));
+      verify_inside(d2);
+      d2.push_back(get_s<S, 22>(i % 10 + 'b'));
+      verify_inside(d2);
+
+      d2.pop_front();
+      verify_inside(d2);
+    }
+  }
+  {
+    C d3;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      d3.push_front(get_s<S, 1>(i % 10 + 'a'));
+      verify_inside(d3);
+      d3.push_front(get_s<S, 28>(i % 10 + 'b'));
+      verify_inside(d3);
+
+      d3.pop_back();
+      verify_inside(d3);
+    }
+  }
+  {
+    C d4;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      // When there is no SSO, all elements inside should not be poisoned,
+      // so we can verify deque poisoning.
+      d4.push_front(get_s<S, 33>(i % 10 + 'a'));
+      verify_inside(d4);
+      assert(is_double_ended_contiguous_container_asan_correct(d4));
+      d4.push_back(get_s<S, 28>(i % 10 + 'b'));
+      verify_inside(d4);
+      assert(is_double_ended_contiguous_container_asan_correct(d4));
+    }
+  }
+  {
+    C d5;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      // In d4 we never had poisoned memory inside deque.
+      // Here we start with SSO, so part of the inside of the container,
+      // will be poisoned.
+      d5.push_front(S());
+      verify_inside(d5);
+    }
+    for (size_t i = 0; i < d5.size(); ++i) {
+      // We change the size to have long string.
+      // Memory owne by deque should not be poisoned by string.
+      d5[i].resize(100);
+      verify_inside(d5);
+    }
+
+    assert(is_double_ended_contiguous_container_asan_correct(d5));
+
+    d5.erase(d5.begin() + 2);
+    verify_inside(d5);
+
+    d5.erase(d5.end() - 2);
+    verify_inside(d5);
+
+    assert(is_double_ended_contiguous_container_asan_correct(d5));
+  }
+  {
+    C d6a;
+    assert(is_double_ended_contiguous_container_asan_correct(d6a));
+
+    C d6b(N + 2, get_s<S, 100>('a'));
+    d6b.push_front(get_s<S, 101>('b'));
+    while (!d6b.empty()) {
+      d6b.pop_back();
+      assert(is_double_ended_contiguous_container_asan_correct(d6b));
+    }
+
+    C d6c(N + 2, get_s<S, 102>('c'));
+    while (!d6c.empty()) {
+      d6c.pop_back();
+      assert(is_double_ended_contiguous_container_asan_correct(d6c));
+    }
+  }
+  {
+    C d7(9 * N + 2);
+
+    d7.insert(d7.begin() + 1, S());
+    verify_inside(d7);
+
+    d7.insert(d7.end() - 3, S());
+    verify_inside(d7);
+
+    d7.insert(d7.begin() + 2 * N, get_s<S, 1>('a'));
+    verify_inside(d7);
+
+    d7.insert(d7.end() - 2 * N, get_s<S, 1>('b'));
+    verify_inside(d7);
+
+    d7.insert(d7.begin() + 2 * N, 3 * N, get_s<S, 1>('c'));
+    verify_inside(d7);
+
+    // It may not be short for big element types, but it will be checked correctly:
+    d7.insert(d7.end() - 2 * N, 3 * N, get_s<S, 2>('d'));
+    verify_inside(d7);
+
+    d7.erase(d7.begin() + 2);
+    verify_inside(d7);
+
+    d7.erase(d7.end() - 2);
+    verify_inside(d7);
+  }
+}
+
+template <class S>
+void test_container() {
+  test_string<std::deque<S, std::allocator<S>>, S>();
+  test_string<std::deque<S, min_allocator<S>>, S>();
+  test_string<std::deque<S, safe_allocator<S>>, S>();
+}
+
+int main(int, char**) {
+  // Those tests support only types based on std::basic_string.
+  test_container<std::string>();
+  test_container<std::wstring>();
+#if TEST_STD_VER >= 11
+  test_container<std::u16string>();
+  test_container<std::u32string>();
+#endif
+#if TEST_STD_VER >= 20
+  test_container<std::u8string>();
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/strings/basic.string/asan_short.pass.cpp b/libcxx/test/libcxx/containers/strings/basic.string/asan_short.pass.cpp
new file mode 100644
index 000000000000000..53c70bed189b5c1
--- /dev/null
+++ b/libcxx/test/libcxx/containers/strings/basic.string/asan_short.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: asan
+// UNSUPPORTED: c++03
+
+// <string>
+
+// Basic test if ASan annotations work for short strings.
+
+#include <string>
+#include <cassert>
+#include <cstdlib>
+
+#include "asan_testing.h"
+#include "min_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+extern "C" void __sanitizer_set_death_callback(void (*callback)(void));
+
+void do_exit() { exit(0); }
+
+int main(int, char**) {
+  {
+    typedef cpp17_input_iterator<char*> MyInputIter;
+    // Should not trigger ASan.
+    std::basic_string<char, std::char_traits<char>, safe_allocator<char>> v;
+    char i[] = {'a', 'b', 'c', 'd'};
+
+    v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 4));
+    assert(v[0] == 'a');
+    assert(is_string_asan_correct(v));
+  }
+
+  __sanitizer_set_death_callback(do_exit);
+  {
+    using T     = char;
+    using C     = std::basic_string<T, std::char_traits<T>, safe_allocator<T>>;
+    const T t[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
+    C c(std::begin(t), std::end(t));
+    assert(is_string_asan_correct(c));
+    assert(__sanitizer_verify_contiguous_container(c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) !=
+           0);
+    volatile T foo = c[c.size() + 1]; // should trigger ASAN. Use volatile to prevent being optimized away.
+    assert(false);                    // if we got here, ASAN didn't trigger
+    ((void)foo);
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/strings/basic.string/asan_vector_integration.pass.cpp b/libcxx/test/libcxx/containers/strings/basic.string/asan_vector_integration.pass.cpp
new file mode 100644
index 000000000000000..b7d95b7069083ae
--- /dev/null
+++ b/libcxx/test/libcxx/containers/strings/basic.string/asan_vector_integration.pass.cpp
@@ -0,0 +1,182 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: asan
+// UNSUPPORTED: c++03
+
+#include <cassert>
+#include <string>
+#include <vector>
+#include <array>
+#include "test_macros.h"
+#include "asan_testing.h"
+#include "min_allocator.h"
+
+// This tests exists to check if strings work well with vector, as those
+// may be partialy annotated, we cannot simply call
+// is_contiguous_container_asan_correct, as it assumes that
+// object memory inside is not annotated, so we check everything in a more careful way.
+
+template <typename D>
+void verify_inside(D const& d) {
+  for (size_t i = 0; i < d.size(); ++i) {
+    assert(is_string_asan_correct(d[i]));
+  }
+}
+
+template <typename S, size_t N>
+S get_s(char c) {
+  S s;
+  for (size_t i = 0; i < N; ++i)
+    s.push_back(c);
+
+  return s;
+}
+
+template <class C, class S>
+void test_string() {
+  size_t const N = sizeof(S) < 256 ? (4096 / sizeof(S)) : 16;
+
+  {
+    C d1a(1), d1b(N), d1c(N + 1), d1d(5 * N);
+    verify_inside(d1a);
+    verify_inside(d1b);
+    verify_inside(d1c);
+    verify_inside(d1d);
+  }
+  {
+    C d2;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      d2.push_back(get_s<S, 1>(i % 10 + 'a'));
+      verify_inside(d2);
+      d2.push_back(get_s<S, 28>(i % 10 + 'b'));
+      verify_inside(d2);
+
+      d2.erase(d2.cbegin());
+      verify_inside(d2);
+    }
+  }
+  {
+    C d3;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      d3.push_back(get_s<S, 1>(i % 10 + 'a'));
+      verify_inside(d3);
+      d3.push_back(get_s<S, 28>(i % 10 + 'b'));
+      verify_inside(d3);
+
+      d3.pop_back();
+      verify_inside(d3);
+    }
+  }
+  {
+    C d4;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      // When there is no SSO, all elements inside should not be poisoned,
+      // so we can verify vector poisoning.
+      d4.push_back(get_s<S, 33>(i % 10 + 'a'));
+      verify_inside(d4);
+      assert(is_contiguous_container_asan_correct(d4));
+      d4.push_back(get_s<S, 28>(i % 10 + 'b'));
+      verify_inside(d4);
+      assert(is_contiguous_container_asan_correct(d4));
+    }
+  }
+  {
+    C d5;
+    for (size_t i = 0; i < 3 * N + 2; ++i) {
+      // In d4 we never had poisoned memory inside vector.
+      // Here we start with SSO, so part of the inside of the container,
+      // will be poisoned.
+      d5.push_back(S());
+      verify_inside(d5);
+    }
+    for (size_t i = 0; i < d5.size(); ++i) {
+      // We change the size to have long string.
+      // Memory owne by vector should not be poisoned by string.
+      d5[i].resize(100);
+      verify_inside(d5);
+    }
+
+    assert(is_contiguous_container_asan_correct(d5));
+
+    d5.erase(d5.begin() + 2);
+    verify_inside(d5);
+
+    d5.erase(d5.end() - 2);
+    verify_inside(d5);
+
+    assert(is_contiguous_container_asan_correct(d5));
+  }
+  {
+    C d6a;
+    assert(is_contiguous_container_asan_correct(d6a));
+
+    C d6b(N + 2, get_s<S, 100>('a'));
+    d6b.push_back(get_s<S, 101>('b'));
+    while (!d6b.empty()) {
+      d6b.pop_back();
+      assert(is_contiguous_container_asan_correct(d6b));
+    }
+
+    C d6c(N + 2, get_s<S, 102>('c'));
+    while (!d6c.empty()) {
+      d6c.pop_back();
+      assert(is_contiguous_container_asan_correct(d6c));
+    }
+  }
+  {
+    C d7(9 * N + 2);
+
+    d7.insert(d7.begin() + 1, S());
+    verify_inside(d7);
+
+    d7.insert(d7.end() - 3, S());
+    verify_inside(d7);
+
+    d7.insert(d7.begin() + 2 * N, get_s<S, 1>('a'));
+    verify_inside(d7);
+
+    d7.insert(d7.end() - 2 * N, get_s<S, 1>('b'));
+    verify_inside(d7);
+
+    d7.insert(d7.begin() + 2 * N, 3 * N, get_s<S, 1>('c'));
+    verify_inside(d7);
+
+    // It may not be short for big element types, but it will be checked correctly:
+    d7.insert(d7.end() - 2 * N, 3 * N, get_s<S, 2>('d'));
+    verify_inside(d7);
+
+    d7.erase(d7.begin() + 2);
+    verify_inside(d7);
+
+    d7.erase(d7.end() - 2);
+    verify_inside(d7);
+  }
+}
+
+template <class S>
+void test_container() {
+  test_string<std::vector<S, std::allocator<S>>, S>();
+  test_string<std::vector<S, min_allocator<S>>, S>();
+  test_string<std::vector<S, safe_allocator<S>>, S>();
+}
+
+int main(int, char**) {
+  // Those tests support only types based on std::basic_string.
+  test_container<std::string>();
+  test_container<std::wstring>();
+#if TEST_STD_VER >= 11
+  test_container<std::u16string>();
+  test_container<std::u32string>();
+#endif
+#if TEST_STD_VER >= 20
+  test_container<std::u8string>();
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/support/asan_testing.h b/libcxx/test/support/asan_testing.h
index 6bfc8280a4ead30..3785c1f9c20dea1 100644
--- a/libcxx/test/support/asan_testing.h
+++ b/libcxx/test/support/asan_testing.h
@@ -56,35 +56,16 @@ TEST_CONSTEXPR bool is_double_ended_contiguous_container_asan_correct(const std:
 #endif
 
 #if TEST_HAS_FEATURE(address_sanitizer)
-template <typename S>
-bool is_string_short(S const& s) {
-  // We do not have access to __is_long(), but we can check if strings
-  // buffer is inside strings memory. If strings memory contains its content,
-  // SSO is in use. To check it, we can just confirm that the beginning is in
-  // the string object memory block.
-  // &s    - beginning of objects memory
-  // &s[0] - beginning of the buffer
-  // (&s+1) - end of objects memory
-  return (void*)std::addressof(s) <= (void*)std::addressof(s[0]) &&
-         (void*)std::addressof(s[0]) < (void*)(std::addressof(s) + 1);
-}
-
 template <typename ChrT, typename TraitsT, typename Alloc>
 TEST_CONSTEXPR bool is_string_asan_correct(const std::basic_string<ChrT, TraitsT, Alloc>& c) {
   if (TEST_IS_CONSTANT_EVALUATED)
     return true;
 
-  if (!is_string_short(c) || _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED) {
-    if (std::__asan_annotate_container_with_allocator<Alloc>::value)
-      return __sanitizer_verify_contiguous_container(c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) !=
-             0;
-    else
-      return __sanitizer_verify_contiguous_container(
-                 c.data(), c.data() + c.capacity() + 1, c.data() + c.capacity() + 1) != 0;
-  } else {
-    return __sanitizer_verify_contiguous_container(std::addressof(c), std::addressof(c) + 1, std::addressof(c) + 1) !=
-           0;
-  }
+  if (std::__asan_annotate_container_with_allocator<Alloc>::value)
+    return __sanitizer_verify_contiguous_container(c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) != 0;
+  else
+    return __sanitizer_verify_contiguous_container(
+               c.data(), c.data() + c.capacity() + 1, c.data() + c.capacity() + 1) != 0;
 }
 #else
 #  include <string>

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

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

If only we had pre-commit CI everywhere, this would have been easier to land in the first place :-)

@AdvenamTacet AdvenamTacet marked this pull request as draft January 31, 2024 18:42
AdvenamTacet pushed a commit to trail-of-forks/llvm-project that referenced this pull request Feb 6, 2024
This commit adds information that only long strings are annotated,
and with all allocators by default.

To read why short string annotations are not turned on yet, read
related PR: llvm#79536
@AdvenamTacet
Copy link
Member Author

@ldionne thanks for looking at it! For now I changed it to WIP to avoid merging by mistake, before we figure out and understand the real reason behind buildbot failures.

Pre-commit CI with buildbots would be really convenient!

@ldionne
Copy link
Member

ldionne commented Feb 6, 2024

@AdvenamTacet I am confused, are we still seeing issues on bots?

@AdvenamTacet
Copy link
Member Author

@ldionne with #79522 not merged, we still have issues on bots. And #79522 solves all issues on bots, but we didn't merge it because we don't really know why it solves those problems.

Copy link
Collaborator

@vitalybuka vitalybuka left a comment

Choose a reason for hiding this comment

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

I assume we are not landing as-is, as we need a workaround for asan?

AdvenamTacet pushed a commit that referenced this pull request Feb 20, 2024
This commit adds information that only long strings are annotated, and
with all allocators by default.

To read why short string annotations are not turned on yet, read comments in a related
PR: #79536

---------

Co-authored-by: Mark de Wever <[email protected]>
AdvenamTacet pushed a commit that referenced this pull request Feb 23, 2024
This commit adds information that only long strings are annotated, and
with all allocators by default.

To read why short string annotations are not turned on yet, read comments in a related
PR: #79536

---------

Co-authored-by: Mark de Wever <[email protected]>
tstellar pushed a commit that referenced this pull request Feb 23, 2024
This commit adds information that only long strings are annotated, and
with all allocators by default.

To read why short string annotations are not turned on yet, read
comments in a related PR:
#79536

Upstreamed in: 7661ade
Upstream PR: #80912

---------

Co-authored-by: Mark de Wever <[email protected]>

Co-authored-by: Mark de Wever <[email protected]>
@AdvenamTacet AdvenamTacet force-pushed the short-string-annotations-v3 branch 2 times, most recently from 8783125 to 69f4912 Compare May 4, 2024 16:59
@AdvenamTacet AdvenamTacet marked this pull request as ready for review May 4, 2024 17:01
@AdvenamTacet
Copy link
Member Author

AdvenamTacet commented May 4, 2024

Sorry for the delay, I had to focus on other projects and it took me a while to fix the issue and properly test it.

@vitalybuka I believe with those new changes it's ready to be upstreamed.
I have tested with sanitizers/buildbot_bootstrap_asan.sh, sanitizers/buildbot_fast.sh and with fuzzing a few projects. Didn't notice any problems.

New changes are in a separate commit [ASan][libc++] Turn on ASan annotations for short strings.
Changes in the commit:

  • Value of __trivially_relocatable is set to false when compiling with ASan (nothing changes when compiling without ASan). Short string annotations make std::basic_string to not be trivially relocatable, because memory has to be unpoisoned.
  • Added two _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS.
  • Added a macro _LIBCPP_ASAN_VOLATILE_WRAPPER, basically it's your idea of changing pointer into a volatile pointer for a moment. When compiling without ASan, macro is "transparent" as shown below.
#define _LIBCPP_ASAN_VOLATILE_WRAPPER(ptr) __asan_volatile_wrapper(ptr)
#else
#define _LIBCPP_ASAN_VOLATILE_WRAPPER(ptr) ptr
#endif

I was thinking about a different approach where we rely on compiler optimizations and always call __asan_volatile_wrapper instead of the macro. And without ASan volatile code doesn't exist. That would be consistent with other ASan helper functions, but we had problems with it in past, so I took safer approach.

I can't find a problem with that implementation, so I am happy to upstream it. Hope it won't cause a problem anymore.

blueboxd pushed a commit to blueboxd/libcxx that referenced this pull request May 4, 2024
This commit adds information that only long strings are annotated, and
with all allocators by default.

To read why short string annotations are not turned on yet, read comments in a related
PR: llvm/llvm-project#79536

---------

Co-authored-by: Mark de Wever <[email protected]>
NOKEYCHECK=True
GitOrigin-RevId: 7661ade5d1ac4fc8e1e2339b2476cb8e45c24641
@AdvenamTacet AdvenamTacet force-pushed the short-string-annotations-v3 branch 3 times, most recently from 35a0328 to 835c7aa Compare May 5, 2024 01:42
This is 3rd attempt to upstream short string annotations, it's the same as the previous one, but other PRs fixed issues withing LLVM:
- llvm#79489
- llvm#79522

Additionaly annotations were updated (but it shouldn't have any impact on anything):
- llvm#79292

Now, as far as I know, all buildbots should work without problems. Both previous reverts were not related to issues with string annotations, but with issues in LLVM/clang.
Read PRs above and below for details.

---

Previous description:

Originally merged here: llvm#75882
Reverted here: llvm#78627

Reverted due to failing buildbots. The problem was not caused by the annotations code, but by code in the `UniqueFunctionBase` class and in the `JSON.h` file. That code caused the program to write to memory that was already being used by string objects, which resulted in an ASan error.

Fixes are implemented in:
- llvm#79065
- llvm#79066

Problematic code from `UniqueFunctionBase` for example:
```cpp
    // In debug builds, we also scribble across the rest of the storage.
    memset(RHS.getInlineStorage(), 0xAD, InlineStorageSize);
```

---

Original description:

This commit turns on ASan annotations in `std::basic_string` for short stings (SSO case).

Originally suggested here: https://reviews.llvm.org/D147680

String annotations added here: llvm#72677

Requires to pass CI without fails:
- llvm#75845
- llvm#75858

Annotating `std::basic_string` with default allocator is implemented in llvm#72677 but annotations for short strings (SSO - Short String Optimization) are turned off there. This commit turns them on. This also removes `_LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED`, because we do not plan to support turning on and off short string annotations.

Support in ASan API exists since llvm@dd1b7b7. You can turn off annotations for a specific allocator based on changes from llvm@2fa1bec.

This PR is a part of a series of patches extending AddressSanitizer C++ container overflow detection capabilities by adding annotations, similar to those existing in `std::vector` and `std::deque` collections. These enhancements empower ASan to effectively detect instances where the instrumented program attempts to access memory within a collection's internal allocation that remains unused. This includes cases where access occurs before or after the stored elements in `std::deque`, or between the `std::basic_string`'s size (including the null terminator) and capacity bounds.

The introduction of these annotations was spurred by a real-world software bug discovered by Trail of Bits, involving an out-of-bounds memory access during the comparison of two strings using the `std::equals` function. This function was taking iterators (`iter1_begin`, `iter1_end`, `iter2_begin`) to perform the comparison, using a custom comparison function. When the `iter1` object exceeded the length of `iter2`, an out-of-bounds read could occur on the `iter2` object. Container sanitization, upon enabling these annotations, would effectively identify and flag this potential vulnerability.

If you have any questions, please email:

    [email protected]
    [email protected]
This commit stops some optimizations when compiling with ASan.
- Short strings make std::basic_string to not be trivially relocatable, because memory has to be unpoisoned. It changes value of `__trivially_relocatable` when compiling with ASan.
- It truns off compiler stack optimizations with `__asan_volatile_wrapper`, the function is not used when compiling without ASan.
- It turns off instrumentation in a few functions.
@AdvenamTacet
Copy link
Member Author

AdvenamTacet commented May 6, 2024

I've wrapped up the final changes and fixes, and everything is ready for final testing, which I'm starting right now.

  • Since my last message, I had to address a small issue related to min_allocator and const_cast.
  • I modifed a test for trivially relocatable classes and opt out basic_string from it whenever strings are annotated with ASan.

Let me know if you have any questions or suggestions before we upstream.

Edit:
I have tested with sanitizers/buildbot_bootstrap_asan.sh, sanitizers/buildbot_fast.sh and with fuzzing a few projects

@AdvenamTacet AdvenamTacet merged commit 1a96179 into llvm:main May 7, 2024
51 checks passed
@AdvenamTacet AdvenamTacet deleted the short-string-annotations-v3 branch May 7, 2024 16:35
// external memory. In such cases, the destructor is responsible for unpoisoning
// the memory to avoid triggering false positives.
// Therefore it's crucial to ensure the destructor is called
using __trivially_relocatable = false_type;
Copy link
Contributor

@philnik777 philnik777 May 8, 2024

Choose a reason for hiding this comment

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

Suggested change
using __trivially_relocatable = false_type;
using __trivially_relocatable = void;

IMO ASan shouldn't affect traits of the type. This change definitely needs more discussion.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe there needs to be some ASan handling when relocating objects? We're destroying objects after all, which should probably poison memory.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm happy to incrementally improve string annotations.
Without change of __trivially_relocatable and no other changes ASan error happens exactly here:

} else {
__builtin_memcpy(const_cast<__remove_const_t<_Tp>*>(__result), __first, sizeof(_Tp) * (__last - __first));
}

__builtin_memcpy accesses poisoned memory, when objects memory is poisoned.

We can unpoison memory there for every type. Then problematic code area would be:

 } else {
 #ifndef  _LIBCPP_HAS_NO_ASAN
   __asan_unpoison_memory_region(__first, sizeof(_Tp) * (__last - __first));
#endif
   __builtin_memcpy(const_cast<__remove_const_t<_Tp>*>(__result), __first, sizeof(_Tp) * (__last - __first)); 
 } 

Disadvantage of that over changing the value of __trivially_relocatable is the need of unpoisoning memory like that in every function which depends on __libcpp_is_trivially_relocatable. But it shouldn't be a big problem.

What I don't like more is fact that objects after that move won't be poisoned correctly (may result in false negatives).
My idea to fix it is creating a compiler-rt function __asan_move_annotations which may be used here instead of __asan_unpoison_memory_region, making old buffer unpoisoned and the new buffer poisoned in the same way as the old one.

If we try to optimize it as much as possible, creating something similar to __libcpp_is_trivially_relocatable just for ASan is possible. For example __libcpp_may_memory_be_poisoned, and calling __asan_unpoison_memory_region/__asan_move_annotations if and only if that value is true. However, __asan_unpoison_memory_region should be cheaper than __builtin_memcpy, so I think we can accept additional cost here as new ASan trait adds a lot of complexity.

We're destroying objects after all, which should probably poison memory.

Poisoning, if happens, happens in the allocator (during deallocation) and we cannot assume anything about it. For example, allocator cleaning memory before freeing it touches the whole memory before annotations are updated. Therefore __annotate_delete() in all classes is necessary.

But something like __asan_move_annotations is possible and I can create it. Then we can go back to one value of __trivially_relocatable.
To put my actions behind my words, I will open a PR with that change Today or Tomorrow.

Copy link
Member Author

Choose a reason for hiding this comment

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

I see that change as two or three PRs.

Last thing is trivial. __memcpy_with_asan I hope to be fairly easy, but I am not sure what place is the best to add it. Fist one I just drafted, but didn't test it yet. I should finish everything today, unless we have a discussion about changing direction of that work. Let me know what you think.

@nico
Copy link
Contributor

nico commented Jun 19, 2024

This breaks asan for most Objective-C++ programs, see #96099.

This seems fairly tricky to fix.

@nico
Copy link
Contributor

nico commented Jul 12, 2024

I'm now done debugging all issues encountered after merging this to Chromium. Here's some user feedback, do with it what you will.

I can't help but think that it might make sense to make this opt-in by default, in light of the below.

There were four distinct issues.

  1. It found a bug! In https://crbug.com/346356630, we had some test code using a short test string, and the code under test did an out-of-bounds read, and with this change, the test + asan found it. That's great!

  2. As mentioned above, the blocks runtime memcpy()s variables captured by blocks using memcpy() and then calls some function to rehydrate them. This is in a system library, we can't do anything about it, and it wasn't super easy to debug (libc++ asan annotations for short string inline storage is incompatible with apple blocks runtime #96099; upstream https://crbug.com/347379886).

  3. This patch is incompatible with compiling some of your code with asan enabled and some of it with asan disabled. For our fuzzers, we've been building some code that isn't "under fuzz" with asan disabled for performance reasons (since 5 years ago, so for a while). With this change, that no longer works. In short, the __annotate_new() / __annotate_delete (functions) etc have a preprocessor check for if asan enabled, and only call the asan runtime if it's enabled. If some targets that depend on libc++ build without asan, they will have definitions of these methods that don't call asan. Other targets with asan enabled have definitions that do call the runtime. So that's an ODR violation, and it depends on luck which definition the linker picks up. So you can end up in situations where one TU creates a string object that has poisoned short string storage, and then it passes it to a different TU that appends data to that string, trying to convert it to long form, but this TU isn't unpoisoning the short string storage area, and trying to write the long size field then traps. (Arguably, it already doesn't work for the vector and long string asan allocations either, but we didn't hit it in practice there.) https://issues.chromium.org/issues/347026228#comment25 has a detailed writeup.

  4. We had some user code that memcpy()d keys of a custom hash table type. While I don't understand this issue completely, I'm told that that code doesn't care about copying some uninitialized inline storage bytes. The fix here was to replace a std::string in a hash key with an std::optionalstd::string in asan builds (but not elsewhere, for perf reasons). It took a bunch of time to diagnose, the asan diagnostic was a bit hard to read, and in the end we had to come up with a somewhat boutique workaround (https://crbug.com/346174906). (This one is less interesting than the previous two.)

It was extremely helpful that there's a toggle for turning off asan annotations just for std::string. This allowed us to roll libc++ and just turn off this feature and then finish diagnosing issues asynchronously.

EDIT: Oh, and we ended up no longer defining _LIBCPP_INSTRUMENTED_WITH_ASAN. This loses coverage for long strings too, which is a bit unfortunate. Given issues 2 and 3, I don't currently see a path forward for us turning this back on again.

@nico
Copy link
Contributor

nico commented Jul 12, 2024

For 3, I suppose the problem is that we (libc++) use has_feature(asan) to check if asan is enabled. That'l checks if asan is enabled for the current TU, but we really want "is asan used in any TU". I.e. having a user-settable knob instead of has_feature (and maybe use has_feature as fallback if the knob isn't explicitly set).

@ldionne
Copy link
Member

ldionne commented Jul 19, 2024

@nico I am not certain whether you are arguing for the feature being enabled by default, or being disabled by default but opt-in. I think it's the latter but it's not 100% clear to me.

IMO, (2) is a bug in the Blocks runtime and #96269 will work around that issue until we fix the Blocks runtime. (3) is indeed a problem, but I think the real answer here is that we must begin shipping a sanitized version of libc++ and have the clang driver select the right version based on -fsanitize=<foo>. Even the long string markup suffers from this problem although it might be less of an actual issue in practice.

TLDR, I am still leaning towards keeping this enabled by default except on Apple where it is currently badly broken due to the blocks runtime. If it turns out that (3) is a big problem and is biting users quite a bit, perhaps we should revisit this decision and keep it disabled until we actually ship a different dylib based on -fsanitize=foo.

@AdvenamTacet
Copy link
Member Author

AdvenamTacet commented Jul 22, 2024

Hey,
first of all, thank you very much @nico for the feedback, it's really helpful to know experience of other users and I really appreciate that!
I'm back home and I'm starting to look what I can commit to improve experience. Bellow are my observations.


First of all, what you already pointed out, (3) is a known issue since creation of ASan vector annotations. Container annotations (for all containers) don't work when only part of the code is compiled with ASan and therefore it's possible to turn off container annotations in runtime with ASAN_OPTIONS=detect_container_overflow=0 environment variable.
However, I see why it may happen more often with strings than with other containers and it looks like it may be even more more visible with short strings based on your case.

We removed flag allowing to turn off short string annotations with reasoning that we don't want to force users to know that under the hood we have something like SSO. Adding it back is a five seconds work. We can add it and never mention it anywhere, therefore only people who read the file (and therefore are aware of SSO) know about it.

Alternatively, (and I consider it much better solution, at least at the moment of writing it) is adding something like ASAN_OPTIONS=detect_container_overflow=0 for short strings, maybe also strings in general. That would allow turning it off without recompiling everything and still work when we start to ship annotated libc++. I'm inclined to implement it.

If we have a consensus about implementing one or another, I'm happy to sit to it.

Complex solution to (3) would require big changes or additional code in non-ASan builds. I don't think anyone is willing to accept that and I don't support it.


Regarding the (2), I agree with @ldionne and have nothing to add. (4) is very similar in nature.


I think you have not-optimal workaround for (4). I will address that in #91702 but for the time being I see two better solutions.

  • If after memcpy purpose of the source memory area is changed, just unpoison whole source area before the call to memcpy. You can do it with ASAN_UNPOISON_MEMORY_REGION macro.

  • If after memcpy string objects in source memory area are still in use, just copy the memory with turned off instrumentation (you can turn off instrumentation with __attribute__((__no_sanitize__("address")))). At the moment it's probably best to create own asan_unsafe_memcpy with turned off instrumentation.

I hope to have a better solution soon, but #91702 turned out to be extremely time consuming and I was forced to postpone working on it for a while, but I hope to commit that whole week to open source and to finish it soon.

At the same time, I think ASan should detect that, we should only make it easier for developers to tell "I understand that this memory operation is not safe".


I'm really happy about (1), if you have more data about recent ASan container annotations changes (short string annotations/long string annotations/deque annotations/annotations of non-default allocators) and can share them, it would be extremely valuable for me and I would very appreciate that. I'm interested in any and every piece of data you can share.


In general, I'm very strongly in favor of enabling all annotations by default. But we can make disabling some annotations easier.
It's much easier to tell "Hey, you can turn off those annotations" when user hits error, than inform users that there is a possibility to enable more annotations.

All ASan errors mentioned here should be reported by ASan, imho. As mention before, I think we should only make it easier for developers to tell "I understand that this memory operation is not safe". And I plan to address it. I don't want to commit to finalize it this week, but I will try to do it asap.

I think, now the best thing we can do is adding runtime ASan options to turn off string annotations and (maybe) short string annotations.
I understand the argument "users shouldn't have to know about SSO", additionally same problems may happen with all containers including long string annotations as described in my comment to (3)... but if in practice only short string annotations are problematic, I think we should address it somehow.
I support creating a runtime option to turn off only short string annotations and string annotations in general, suggesting the latter for all users [who don't know about SSO]. Or just for short annotations, if long annotations are not reported to be problematic. I believe there is big enough group of people who understand SSO and use ASan that this additional option would be helpful. But I understand if we decide against that granularity.
If not, the option to turn off string annotations in general (and in runtime) seems like a good thing to me.

Thank you for your feedback again!

@AdvenamTacet
Copy link
Member Author

AdvenamTacet commented Jul 22, 2024

For 3, I suppose the problem is that we (libc++) use has_feature(asan) to check if asan is enabled. That'l checks if asan is enabled for the current TU, but we really want "is asan used in any TU". I.e. having a user-settable knob instead of has_feature (and maybe use has_feature as fallback if the knob isn't explicitly set).

I don't think it's feasible.

If it turns out that (3) is a big problem and is biting users quite a bit, perhaps we should revisit this decision and keep it disabled until we actually ship a different dylib based on -fsanitize=foo.

Right now, any string annotations are enabled only when libc++ is compiled with ASan and right now it has to be done by user. So it would help only in case everything is compiled with ASan, but some TUs are compiled with stock libc++ and some with self-compiled ASan libc++.

In this case, if I understand correctly, it's a conscious decision to disable ASan completely for the whole TU. And therefore it's against assumptions of container annotations, and shipping instrumented libc++ would not help in that case.

@nico
Copy link
Contributor

nico commented Jul 24, 2024

I don't think it's feasible.

How come?

To be clear, all I'm suggesting is something like this:

% git diff
diff --git a/libcxx/include/__config b/libcxx/include/__config
index 108f700823cb..aaab10129a6f 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -355,7 +355,15 @@ typedef __char32_t char32_t;
 #    define _LIBCPP_HAS_BLOCKS_RUNTIME
 #  endif
 
-#  if !__has_feature(address_sanitizer)
+#  if !defined(_LIBCPP_SOME_USER_CODE_IS_USING_ASAN)
+#    if _has_feature(address_sanitizer)
+#      define _LIBCPP_SOME_USER_CODE_IS_USING_ASAN 1
+#    else
+#      define _LIBCPP_SOME_USER_CODE_IS_USING_ASAN 0
+#    endif
+#  endif
+
+#  if !_LIBCPP_SOME_USER_CODE_IS_USING_ASAN
 #    define _LIBCPP_HAS_NO_ASAN
 #  endif

Then people who use asan in some TUs but not in others can make sure to pass -D_LIBCPP_SOME_USER_CODE_IS_USING_ASAN 1 in all their TUs, and they won't get ODR violations and everything should just work.

(Tweak name and delivery mechanism (cmake option that sets some toggle or what) of that define at will)

@AdvenamTacet
Copy link
Member Author

@nico if I understand correctly, you suggest a way to not have instrumentation in a TU, but still have container annotation functions (to update container annotations).

Thinking about it longer, I believe it should work. Not sure if we can expect some linking issues, but it shouldn't be a big problem. Maybe there is something else I don't consider.

If someone decides to implement it now, I'm happy to review it. If not, I can look into it in future.

@thesamesam
Copy link
Member

FYI: I've filed https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116243 for libstdc++ after @pinskia pointed me this way.

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.

7 participants