[libcxx-commits] [libcxx] 4905550 - [ASan][libcxx] Annotating std::vector with all allocators

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jan 25 10:04:26 PST 2023


Author: Advenam Tacet
Date: 2023-01-25T19:04:15+01:00
New Revision: 490555026821db47d1cf4bf08c219b3e56ec6b45

URL: https://github.com/llvm/llvm-project/commit/490555026821db47d1cf4bf08c219b3e56ec6b45
DIFF: https://github.com/llvm/llvm-project/commit/490555026821db47d1cf4bf08c219b3e56ec6b45.diff

LOG: [ASan][libcxx] Annotating std::vector with all allocators

This revision 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, to std::string and std::deque collections.
These changes allow ASan to detect cases when the instrumented
program accesses memory which is internally allocated by
the collection but is still not in-use (accesses before or
after the stored elements for std::deque, or between the size and
capacity bounds for std::string).

The motivation for the research and those changes was a bug,
found by Trail of Bits, in a real code where an out-of-bounds read
could happen as two strings were compared via a std::equals function
that took iter1_begin, iter1_end, iter2_begin iterators
(with a custom comparison function).
When object iter1 was longer than iter2, read out-of-bounds on iter2
could happen. Container sanitization would detect it.

In revision D132522, support for non-aligned memory buffers (sharing
first/last granule with other objects) was added, therefore the
check for standard allocator is not necessary anymore.
This patch removes the check in std::vector annotation member
function (__annotate_contiguous_container) to support
different allocators.

If you have any questions, please email:
 - advenam.tacet at trailofbits.com
 - disconnect3d at trailofbits.com

Reviewed By: #libc, #sanitizers, philnik, vitalybuka

Spies: EricWF, philnik, #sanitizers, libcxx-commits

Differential Revision: https://reviews.llvm.org/D136765

Added: 
    

Modified: 
    libcxx/include/vector
    libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp
    libcxx/test/support/min_allocator.h

Removed: 
    


################################################################################
diff  --git a/libcxx/include/vector b/libcxx/include/vector
index 4b7ae130a7bc4..e9019a8ae3033 100644
--- a/libcxx/include/vector
+++ b/libcxx/include/vector
@@ -743,8 +743,25 @@ private:
                                          const void *__old_mid,
                                          const void *__new_mid) const
     {
-
+#  if _LIBCPP_CLANG_VER >= 1600
+      if (!__libcpp_is_constant_evaluated() && __beg)
+      // Implementation of __sanitizer_annotate_contiguous_container function changed with commit
+      // rGdd1b7b797a116eed588fd752fbe61d34deeb24e4 and supports:
+      //  - unaligned beginnings of buffers,
+      //  - shared first/last granule (if memory granule is shared with a 
diff erent object
+      //    just after the end of unaligned end/before the unaligned beginning, memory of that object won't be poisoned).
+      //
+      // Therefore, check for standard allocator is not necessary.
+#  else
+      // TODO LLVM18: Remove the special-casing
+      //
+      // Vectors annotations rely on __sanitizer_annotate_contiguous_container function,
+      // its implementation from LLVM15 (and earlier) requires that
+      // beginning of a containers data buffer is aligned to shadow granularity and
+      // memory just after is not shared with another object.
+      // Default allocator satisfies that.
       if (!__libcpp_is_constant_evaluated() && __beg && is_same<allocator_type, __default_allocator_type>::value)
+#  endif
         __sanitizer_annotate_contiguous_container(__beg, __end, __old_mid, __new_mid);
     }
 #else

diff  --git a/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp
index 40275f58cb32c..01da9d52e8f0d 100644
--- a/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp
@@ -29,40 +29,65 @@ void do_exit() {
 
 int main(int, char**)
 {
-#if TEST_STD_VER >= 11
-    {
-        typedef int T;
-        typedef std::vector<T, min_allocator<T>> C;
-        const T t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-        C c(std::begin(t), std::end(t));
-        c.reserve(2*c.size());
-        volatile T foo = c[c.size()];    // bad, but not caught by ASAN
-        ((void)foo);
-    }
+#if TEST_STD_VER >= 11 && TEST_CLANG_VER < 1600
+  // TODO LLVM18: Remove the special-casing
+  {
+    typedef int T;
+    typedef std::vector<T, min_allocator<T>> C;
+    const T t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+    C c(std::begin(t), std::end(t));
+    c.reserve(2 * c.size());
+    volatile T foo = c[c.size()]; // bad, but not caught by ASAN
+    ((void)foo);
+  }
 #endif
 
-    {
-        typedef cpp17_input_iterator<int*> MyInputIter;
-        // Sould not trigger ASan.
-        std::vector<int> v;
-        v.reserve(1);
-        int i[] = {42};
-        v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 1));
-        assert(v[0] == 42);
-        assert(is_contiguous_container_asan_correct(v));
-    }
+#if TEST_STD_VER >= 11 && TEST_CLANG_VER >= 1600
+  // TODO LLVM18: Remove the TEST_CLANG_VER check
+  {
+    typedef int T;
+    typedef cpp17_input_iterator<T*> MyInputIter;
+    std::vector<T, min_allocator<T>> v;
+    v.reserve(1);
+    int i[] = {42};
+    v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 1));
+    assert(v[0] == 42);
+    assert(is_contiguous_container_asan_correct(v));
+  }
+  {
+    typedef char T;
+    typedef cpp17_input_iterator<T*> MyInputIter;
+    std::vector<T, unaligned_allocator<T>> v;
+    v.reserve(1);
+    char i[] = {'a', 'b'};
+    v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 2));
+    assert(v[0] == 'a');
+    assert(v[1] == 'b');
+    assert(is_contiguous_container_asan_correct(v));
+  }
+#endif
+  {
+    typedef cpp17_input_iterator<int*> MyInputIter;
+    // Sould not trigger ASan.
+    std::vector<int> v;
+    v.reserve(1);
+    int i[] = {42};
+    v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 1));
+    assert(v[0] == 42);
+    assert(is_contiguous_container_asan_correct(v));
+  }
 
-    __sanitizer_set_death_callback(do_exit);
-    {
-        typedef int T;
-        typedef std::vector<T> C;
-        const T t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-        C c(std::begin(t), std::end(t));
-        c.reserve(2*c.size());
-        assert(is_contiguous_container_asan_correct(c));
-        assert(!__sanitizer_verify_contiguous_container( c.data(), c.data() + 1, c.data() + c.capacity()));
-        volatile T foo = c[c.size()]; // should trigger ASAN. Use volatile to prevent being optimized away.
-        assert(false);          // if we got here, ASAN didn't trigger
-        ((void)foo);
-    }
+  __sanitizer_set_death_callback(do_exit);
+  {
+    typedef int T;
+    typedef std::vector<T> C;
+    const T t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+    C c(std::begin(t), std::end(t));
+    c.reserve(2 * c.size());
+    assert(is_contiguous_container_asan_correct(c));
+    assert(!__sanitizer_verify_contiguous_container(c.data(), c.data() + 1, c.data() + c.capacity()));
+    volatile T foo = c[c.size()]; // should trigger ASAN. Use volatile to prevent being optimized away.
+    assert(false);                // if we got here, ASAN didn't trigger
+    ((void)foo);
+  }
 }

diff  --git a/libcxx/test/support/min_allocator.h b/libcxx/test/support/min_allocator.h
index f27ac80c04649..7d517518f0522 100644
--- a/libcxx/test/support/min_allocator.h
+++ b/libcxx/test/support/min_allocator.h
@@ -432,4 +432,23 @@ class explicit_allocator
     TEST_CONSTEXPR_CXX20 friend bool operator!=(explicit_allocator x, explicit_allocator y) {return !(x == y);}
 };
 
+template <class T>
+class unaligned_allocator {
+public:
+  static_assert(TEST_ALIGNOF(T) == 1, "Type T cannot be created on unaligned address (UB)");
+  typedef T value_type;
+
+  TEST_CONSTEXPR_CXX20 unaligned_allocator() TEST_NOEXCEPT {}
+
+  template <class U>
+  TEST_CONSTEXPR_CXX20 explicit unaligned_allocator(unaligned_allocator<U>) TEST_NOEXCEPT {}
+
+  TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return static_cast<T*>(std::allocator<T>().allocate(n + 1)) + 1; }
+
+  TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p - 1, n + 1); }
+
+  TEST_CONSTEXPR_CXX20 friend bool operator==(unaligned_allocator, unaligned_allocator) { return true; }
+  TEST_CONSTEXPR_CXX20 friend bool operator!=(unaligned_allocator x, unaligned_allocator y) { return !(x == y); }
+};
+
 #endif // MIN_ALLOCATOR_H


        


More information about the libcxx-commits mailing list