From 0a35ac6c2e0cb0160ca2e6cc11644c263692a46d Mon Sep 17 00:00:00 2001 From: Advenam Tacet Date: Tue, 18 Jul 2023 21:15:13 +0200 Subject: [PATCH] [ASan][libc++] Annotating std::deque with all allocators This patch is part of our efforts to support container annotations with (almost) every allocator. Annotating std::deque with default allocator is implemented in D132092. Support in ASan API exests since rG1c5ad6d2c01294a0decde43a88e9c27d7437d157. The motivation for a 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. If you have any questions, please email: - advenam.tacet@trailofbits.com - disconnect3d@trailofbits.com Reviewed By: #libc, ldionne Differential Revision: https://reviews.llvm.org/D146815 --- libcxx/include/deque | 5 +- .../containers/sequences/deque/asan.pass.cpp | 17 ++++- .../sequences/deque/asan_turning_off.pass.cpp | 76 +++++++++++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 libcxx/test/libcxx/containers/sequences/deque/asan_turning_off.pass.cpp diff --git a/libcxx/include/deque b/libcxx/include/deque index 230bae75e6619..b063680500cce 100644 --- a/libcxx/include/deque +++ b/libcxx/include/deque @@ -458,9 +458,6 @@ const _DiffType __deque_iterator<_ValueType, _Pointer, _Reference, _MapPointer, template */> class _LIBCPP_TEMPLATE_VIS deque { -private: - using __default_allocator_type = allocator<_Tp>; - public: // types: @@ -981,7 +978,7 @@ public: const void* __old_con_end, const void* __new_con_beg, const void* __new_con_end) const { - if (__beg && is_same::value) + if (__beg != nullptr && __asan_annotate_container_with_allocator<_Allocator>::value) __sanitizer_annotate_double_ended_contiguous_container( __beg, __end, __old_con_beg, __old_con_end, __new_con_beg, __new_con_end); } diff --git a/libcxx/test/libcxx/containers/sequences/deque/asan.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/asan.pass.cpp index 6067974f3a7e9..e8091acefc368 100644 --- a/libcxx/test/libcxx/containers/sequences/deque/asan.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/deque/asan.pass.cpp @@ -31,13 +31,28 @@ int main(int, char**) { { typedef cpp17_input_iterator MyInputIter; - // Sould not trigger ASan. + // Should not trigger ASan. std::deque v; int i[] = {42}; v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 1)); assert(v[0] == 42); assert(is_double_ended_contiguous_container_asan_correct(v)); } + { + typedef int T; + typedef std::deque > C; + const T t[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + C c(std::begin(t), std::end(t)); + assert(is_double_ended_contiguous_container_asan_correct(c)); + } + { + typedef char T; + typedef std::deque > C; + const T t[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + C c(std::begin(t), std::end(t)); + c.pop_front(); + assert(is_double_ended_contiguous_container_asan_correct(c)); + } __sanitizer_set_death_callback(do_exit); { typedef int T; diff --git a/libcxx/test/libcxx/containers/sequences/deque/asan_turning_off.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/asan_turning_off.pass.cpp new file mode 100644 index 0000000000000..e9b9cde64ee91 --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/deque/asan_turning_off.pass.cpp @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// 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 +#include +#include + +struct reuse_allocator { + static size_t const N = 100; + reuse_allocator() { + for (size_t i = 0; i < N; ++i) + __buffers[i] = new char[8 * 1024]; + } + ~reuse_allocator() { + for (size_t i = 0; i < N; ++i) + delete[] (char*)__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 +struct user_allocator { + using value_type = T; + user_allocator() = default; + template + user_allocator(user_allocator) {} + 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 +struct std::__asan_annotate_container_with_allocator> : false_type {}; +#endif + +int main(int, char**) { + using D = std::deque>; + + { + D* d = new (reuse_buffers.alloc()) D(); + for (int i = 0; i < 100; i++) + d->push_back(i); + } + reuse_buffers.reset(); + { + D d; + for (int i = 0; i < 1000; i++) + d.push_back(i); + } + + return 0; +}