diff options
author | Advenam Tacet <advenam.tacet@trailofbits.com> | 2023-05-04 17:43:51 -0700 |
---|---|---|
committer | Nikolas Klauser <n_klauser@apple.com> | 2023-05-04 17:44:06 -0700 |
commit | c08d4ad25cf3f335e9b2e7b1b149eb1b486868f1 (patch) | |
tree | 9257cff309db19fbac6cd0b26e06b8215709e2f6 /libcxx/test | |
parent | 901266dad313c114e12c181651249e30e5902e26 (diff) | |
download | llvm-c08d4ad25cf3f335e9b2e7b1b149eb1b486868f1.tar.gz |
[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.
Additionally, this revision fixes unpoisoning in std::vector.
It guarantees that __alloc_traits::deallocate may access returned memory.
Originally suggested in D144155 revision.
If you have any questions, please email:
- advenam.tacet@trailofbits.com
- disconnect3d@trailofbits.com
Reviewed By: #libc, #sanitizers, philnik, vitalybuka, ldionne
Spies: mikhail.ramalho, manojgupta, ldionne, AntonBikineev, ayzhao, hans, EricWF, philnik, #sanitizers, libcxx-commits
Differential Revision: https://reviews.llvm.org/D136765
Diffstat (limited to 'libcxx/test')
-rw-r--r-- | libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp | 80 | ||||
-rw-r--r-- | libcxx/test/libcxx/containers/sequences/vector/asan_turning_off.pass.cpp | 77 |
2 files changed, 123 insertions, 34 deletions
diff --git a/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp index 40275f58cb32..5b58de0c86ac 100644 --- a/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/vector/asan.pass.cpp @@ -29,40 +29,52 @@ 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 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)); + } - { - 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/libcxx/containers/sequences/vector/asan_turning_off.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/asan_turning_off.pass.cpp new file mode 100644 index 000000000000..e2760271d60a --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/vector/asan_turning_off.pass.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 + +// <vector> + +// Test based on: https://bugs.chromium.org/p/chromium/issues/detail?id=1419798#c5 +// Some allocators during deallocation may not call destructors and just reuse memory. +// In those situations, one may want to deactivate annotations for a specific allocator. +// It's possible with __asan_annotate_container_with_allocator template class. +// This test confirms that those allocators work after turning off annotations. + +#include <assert.h> +#include <stdlib.h> +#include <vector> +#include <new> + +struct reuse_allocator { + static size_t const N = 100; + reuse_allocator() { + for (size_t i = 0; i < N; ++i) + __buffers[i] = malloc(8*1024); + } + ~reuse_allocator() { + for (size_t i = 0; i < N; ++i) + free(__buffers[i]); + } + void* alloc() { + assert(__next_id < N); + return __buffers[__next_id++]; + } + void reset() { __next_id = 0; } + void* __buffers[N]; + size_t __next_id = 0; +} reuse_buffers; + +template <typename T> +struct user_allocator { + using value_type = T; + user_allocator() = default; + template <class U> + user_allocator(user_allocator<U>) {} + friend bool operator==(user_allocator, user_allocator) {return true;} + friend bool operator!=(user_allocator x, user_allocator y) {return !(x == y);} + + T* allocate(size_t) { return (T*)reuse_buffers.alloc(); } + void deallocate(T*, size_t) noexcept {} +}; + +#ifdef _LIBCPP_HAS_ASAN_CONTAINER_ANNOTATIONS_FOR_ALL_ALLOCATORS +template <class T> +struct std::__asan_annotate_container_with_allocator<user_allocator<T>> : false_type {}; +#endif + +int main(int, char**) { + using V = std::vector<int, user_allocator<int>>; + + { + V* v = new (reuse_buffers.alloc()) V(); + for (int i = 0; i < 100; i++) + v->push_back(i); + } + reuse_buffers.reset(); + { + V v; + for (int i = 0; i < 1000; i++) + v.push_back(i); + } + + return 0; +} |