Skip to content

Commit 7af271d

Browse files
author
Advenam Tacet
committed
[ASan][libc++] Annotating std::basic_string with all allocators
This commit turns on ASan annotations in `std::basic_string` for all allocators by default. Originally suggested here: https://reviews.llvm.org/D146214 This commit is part of our efforts to support container annotations with (almost) every allocator. Annotating `std::basic_string` with default allocator is implemented in llvm#72677. Support in ASan API exests since llvm@dd1b7b7. This patch removes the check in std::basic_string annotation member function (__annotate_contiguous_container) to support different allocators. 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]
1 parent 31fd6d1 commit 7af271d

File tree

4 files changed

+134
-3
lines changed

4 files changed

+134
-3
lines changed

libcxx/include/string

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,8 +1891,7 @@ private:
18911891
#if !defined(_LIBCPP_HAS_NO_ASAN) && defined(_LIBCPP_INSTRUMENTED_WITH_ASAN)
18921892
const void* __begin = data();
18931893
const void* __end = data() + capacity() + 1;
1894-
if (!__libcpp_is_constant_evaluated() && __begin != nullptr &&
1895-
is_same<allocator_type, __default_allocator_type>::value)
1894+
if (!__libcpp_is_constant_evaluated() && __asan_annotate_container_with_allocator<allocator_type>::value)
18961895
__sanitizer_annotate_contiguous_container(__begin, __end, __old_mid, __new_mid);
18971896
#endif
18981897
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// REQUIRES: asan
10+
// UNSUPPORTED: c++03
11+
12+
// Basic test if ASan annotations work for basic_string.
13+
14+
#include <string>
15+
#include <cassert>
16+
#include <cstdlib>
17+
18+
#include "asan_testing.h"
19+
#include "min_allocator.h"
20+
#include "test_iterators.h"
21+
#include "test_macros.h"
22+
23+
extern "C" void __sanitizer_set_death_callback(void (*callback)(void));
24+
25+
void do_exit() { exit(0); }
26+
27+
int main(int, char**) {
28+
{
29+
typedef cpp17_input_iterator<char*> MyInputIter;
30+
// Should not trigger ASan.
31+
std::basic_string<char, std::char_traits<char>, safe_allocator<char>> v;
32+
char i[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'a', 'b', 'c', 'd', 'e',
33+
'f', 'g', 'h', 'i', 'j', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
34+
35+
v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 29));
36+
assert(v[0] == 'a');
37+
assert(is_string_asan_correct(v));
38+
}
39+
40+
__sanitizer_set_death_callback(do_exit);
41+
{
42+
using T = char;
43+
using C = std::basic_string<T, std::char_traits<T>, safe_allocator<T>>;
44+
const T t[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'a', 'b', 'c', 'd', 'e',
45+
'f', 'g', 'h', 'i', 'j', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
46+
C c(std::begin(t), std::end(t));
47+
assert(is_string_asan_correct(c));
48+
assert(__sanitizer_verify_contiguous_container(c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) !=
49+
0);
50+
volatile T foo = c[c.size() + 1]; // should trigger ASAN. Use volatile to prevent being optimized away.
51+
assert(false); // if we got here, ASAN didn't trigger
52+
((void)foo);
53+
}
54+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// REQUIRES: asan
10+
// UNSUPPORTED: c++03
11+
12+
// <string>
13+
14+
// Test based on: https://bugs.chromium.org/p/chromium/issues/detail?id=1419798#c5
15+
// Some allocators during deallocation may not call destructors and just reuse memory.
16+
// In those situations, one may want to deactivate annotations for a specific allocator.
17+
// It's possible with __asan_annotate_container_with_allocator template class.
18+
// This test confirms that those allocators work after turning off annotations.
19+
20+
#include <assert.h>
21+
#include <stdlib.h>
22+
#include <string>
23+
#include <new>
24+
25+
struct reuse_allocator {
26+
static size_t const N = 100;
27+
reuse_allocator() {
28+
for (size_t i = 0; i < N; ++i)
29+
__buffers[i] = malloc(8 * 1024);
30+
}
31+
~reuse_allocator() {
32+
for (size_t i = 0; i < N; ++i)
33+
free(__buffers[i]);
34+
}
35+
void* alloc() {
36+
assert(__next_id < N);
37+
return __buffers[__next_id++];
38+
}
39+
void reset() { __next_id = 0; }
40+
void* __buffers[N];
41+
size_t __next_id = 0;
42+
} reuse_buffers;
43+
44+
template <typename T>
45+
struct user_allocator {
46+
using value_type = T;
47+
user_allocator() = default;
48+
template <class U>
49+
user_allocator(user_allocator<U>) {}
50+
friend bool operator==(user_allocator, user_allocator) { return true; }
51+
friend bool operator!=(user_allocator x, user_allocator y) { return !(x == y); }
52+
53+
T* allocate(size_t) { return (T*)reuse_buffers.alloc(); }
54+
void deallocate(T*, size_t) noexcept {}
55+
};
56+
57+
template <class T>
58+
struct std::__asan_annotate_container_with_allocator<user_allocator<T>> {
59+
static bool const value = false;
60+
};
61+
62+
int main() {
63+
using S = std::basic_string<char, std::char_traits<char>, user_allocator<char>>;
64+
65+
{
66+
S* s = new (reuse_buffers.alloc()) S();
67+
for (int i = 0; i < 100; i++)
68+
s->push_back('a');
69+
}
70+
reuse_buffers.reset();
71+
{
72+
S s;
73+
for (int i = 0; i < 1000; i++)
74+
s.push_back('b');
75+
}
76+
77+
return 0;
78+
}

libcxx/test/support/asan_testing.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ TEST_CONSTEXPR bool is_string_asan_correct(const std::basic_string<ChrT, TraitsT
7575
return true;
7676

7777
if (!is_string_short(c) || _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED) {
78-
if (std::is_same<Alloc, std::allocator<ChrT>>::value)
78+
if (std::__asan_annotate_container_with_allocator<Alloc>::value)
7979
return __sanitizer_verify_contiguous_container(c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) !=
8080
0;
8181
else

0 commit comments

Comments
 (0)