Skip to content

Commit

Permalink
[ASan][libc++] Annotating std::deque with all allocators
Browse files Browse the repository at this point in the history
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
  • Loading branch information
AdvenamTacet committed Jul 20, 2023
1 parent d16115d commit 0a35ac6
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 5 deletions.
5 changes: 1 addition & 4 deletions libcxx/include/deque
Expand Up @@ -458,9 +458,6 @@ const _DiffType __deque_iterator<_ValueType, _Pointer, _Reference, _MapPointer,
template <class _Tp, class _Allocator /*= allocator<_Tp>*/>
class _LIBCPP_TEMPLATE_VIS deque
{
private:
using __default_allocator_type = allocator<_Tp>;

public:
// types:

Expand Down Expand Up @@ -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<allocator_type, __default_allocator_type>::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);
}
Expand Down
17 changes: 16 additions & 1 deletion libcxx/test/libcxx/containers/sequences/deque/asan.pass.cpp
Expand Up @@ -31,13 +31,28 @@ int main(int, char**)
{
{
typedef cpp17_input_iterator<int*> MyInputIter;
// Sould not trigger ASan.
// Should not trigger ASan.
std::deque<int> 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<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));
assert(is_double_ended_contiguous_container_asan_correct(c));
}
{
typedef char T;
typedef std::deque<T, safe_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.pop_front();
assert(is_double_ended_contiguous_container_asan_correct(c));
}
__sanitizer_set_death_callback(do_exit);
{
typedef int T;
Expand Down
@@ -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

// <deque>

// 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 <cassert>
#include <deque>
#include <new>

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 <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 D = std::deque<int, user_allocator<int>>;

{
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;
}

0 comments on commit 0a35ac6

Please sign in to comment.