diff --git a/stl/inc/vector b/stl/inc/vector index 4f79f4986c..27f3ad281d 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -483,48 +483,50 @@ private: _STL_INTERNAL_CHECK(_Old_last_ != nullptr); _STL_INTERNAL_CHECK(_New_last_ != nullptr); + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { #if _HAS_CXX20 - if (_STD is_constant_evaluated()) { - return; - } + if (_STD is_constant_evaluated()) { + return; + } #endif // _HAS_CXX20 - if (!_Asan_vector_should_annotate) { - return; - } - - const void* const _First = _STD _Unfancy(_First_); - const void* const _End = _STD _Unfancy(_End_); - const void* const _Old_last = _STD _Unfancy(_Old_last_); - const void* const _New_last = _STD _Unfancy(_New_last_); - if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { - // old state: - // [_First, _Old_last) valid - // [_Old_last, _End) poison - // new state: - // [_First, _New_last) valid - // [_New_last, asan_aligned_after(_End)) poison - _CSTD __sanitizer_annotate_contiguous_container( - _First, _STD _Get_asan_aligned_after(_End), _Old_last, _New_last); - } else { - const auto _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); - if (_Aligned._First == _Aligned._End) { - // The buffer does not end at least one shadow memory section; nothing to do. + if (!_Asan_vector_should_annotate) { return; } - const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); - const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); - - // old state: - // [_Aligned._First, _Old_fixed) valid - // [_Old_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - // new state: - // [_Aligned._First, _New_fixed) valid - // [_New_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - _CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed); + const void* const _First = _STD _Unfancy(_First_); + const void* const _End = _STD _Unfancy(_End_); + const void* const _Old_last = _STD _Unfancy(_Old_last_); + const void* const _New_last = _STD _Unfancy(_New_last_); + if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { + // old state: + // [_First, _Old_last) valid + // [_Old_last, _End) poison + // new state: + // [_First, _New_last) valid + // [_New_last, asan_aligned_after(_End)) poison + _CSTD __sanitizer_annotate_contiguous_container( + _First, _STD _Get_asan_aligned_after(_End), _Old_last, _New_last); + } else { + const auto _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); + if (_Aligned._First == _Aligned._End) { + // The buffer does not end at least one shadow memory section; nothing to do. + return; + } + + const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); + const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); + + // old state: + // [_Aligned._First, _Old_fixed) valid + // [_Old_fixed, _Aligned._End) poison + // [_Aligned._End, _End) valid + // new state: + // [_Aligned._First, _New_fixed) valid + // [_New_fixed, _Aligned._End) poison + // [_Aligned._End, _End) valid + _CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed); + } } } diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 7567d415a1..31d0bed8c9 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -805,6 +805,10 @@ struct _Simple_types { // wraps types from allocators with simple addressing for _INLINE_VAR constexpr size_t _Asan_granularity = 8; _INLINE_VAR constexpr size_t _Asan_granularity_mask = _Asan_granularity - 1; +// Controls whether ASan `container-overflow` errors are reported for this allocator. +template +constexpr bool _Disable_ASan_container_annotations_for_allocator = false; + struct _Asan_aligned_pointers { const void* _First; const void* _End; diff --git a/stl/inc/xstring b/stl/inc/xstring index 6d5954ea93..8bf2a0a5c0 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -622,54 +622,57 @@ private: static _CONSTEXPR20 void _Apply_annotation(const value_type* const _First, const size_type _Capacity, const size_type _Old_size, const size_type _New_size) noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { #if _HAS_CXX20 - if (_STD is_constant_evaluated()) { - return; - } + if (_STD is_constant_evaluated()) { + return; + } #endif // _HAS_CXX20 - // Don't annotate small strings; only annotate on the heap. - if (_Capacity <= _Small_string_capacity || !_Asan_string_should_annotate) { - return; - } - // Note that `_Capacity`, `_Old_size`, and `_New_size` do not include the null terminator - const void* const _End = _First + _Capacity + 1; - const void* const _Old_last = _First + _Old_size + 1; - const void* const _New_last = _First + _New_size + 1; + // Don't annotate small strings; only annotate on the heap. + if (_Capacity <= _Small_string_capacity || !_Asan_string_should_annotate) { + return; + } - constexpr bool _Large_string_always_asan_aligned = - (_Container_allocation_minimum_asan_alignment) >= _Asan_granularity; + // Note that `_Capacity`, `_Old_size`, and `_New_size` do not include the null terminator + const void* const _End = _First + _Capacity + 1; + const void* const _Old_last = _First + _Old_size + 1; + const void* const _New_last = _First + _New_size + 1; - // for the non-aligned buffer options, the buffer must always have size >= 9 bytes, - // so it will always end at least one shadow memory section. + constexpr bool _Large_string_always_asan_aligned = + (_Container_allocation_minimum_asan_alignment) >= _Asan_granularity; - _Asan_aligned_pointers _Aligned; - if constexpr (_Large_string_always_asan_aligned) { - _Aligned = {_First, _STD _Get_asan_aligned_after(_End)}; - } else { - _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); - } - const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); - const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); - - // --- always aligned case --- - // old state: - // [_First, _Old_last) valid - // [_Old_last, asan_aligned_after(_End)) poison - // new state: - // [_First, _New_last) valid - // [_New_last, asan_aligned_after(_End)) poison - - // --- sometimes non-aligned case --- - // old state: - // [_Aligned._First, _Old_fixed) valid - // [_Old_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - // new state: - // [_Aligned._First, _New_fixed) valid - // [_New_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - _CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed); + // for the non-aligned buffer options, the buffer must always have size >= 9 bytes, + // so it will always end at least one shadow memory section. + + _Asan_aligned_pointers _Aligned; + if constexpr (_Large_string_always_asan_aligned) { + _Aligned = {_First, _STD _Get_asan_aligned_after(_End)}; + } else { + _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); + } + const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); + const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); + + // --- always aligned case --- + // old state: + // [_First, _Old_last) valid + // [_Old_last, asan_aligned_after(_End)) poison + // new state: + // [_First, _New_last) valid + // [_New_last, asan_aligned_after(_End)) poison + + // --- sometimes non-aligned case --- + // old state: + // [_Aligned._First, _Old_fixed) valid + // [_Old_fixed, _Aligned._End) poison + // [_Aligned._End, _End) valid + // new state: + // [_Aligned._First, _New_fixed) valid + // [_New_fixed, _Aligned._End) poison + // [_Aligned._End, _End) valid + _CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed); + } } #define _ASAN_STRING_REMOVE(_Str) (_Str)._Remove_annotation() diff --git a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp index e8bf4d30c3..ab35b14f36 100644 --- a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp @@ -289,6 +289,29 @@ STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< basic_string, implicit_allocator>> == 2); +// Simple allocator that opts out of ASan annotations (via `_Disable_ASan_container_annotations_for_allocator`) +template +struct implicit_allocator_no_asan_annotations : implicit_allocator { + implicit_allocator_no_asan_annotations() = default; + template + constexpr implicit_allocator_no_asan_annotations( + const implicit_allocator_no_asan_annotations&) noexcept {} + + T* allocate(size_t n) { + T* mem = new T[n + 1]; + return mem + 1; + } + + void deallocate(T* p, size_t) noexcept { + delete[] (p - 1); + } +}; + +template +constexpr bool + _Disable_ASan_container_annotations_for_allocator> = + true; + template void test_construction() { using CharType = typename Alloc::value_type; @@ -1855,6 +1878,28 @@ void run_tests() { #endif // ^^^ no workaround ^^^ } +// Test that writing to uninitialized memory in a string triggers an ASan container-overflow error. (See GH-5251.) +template > +void run_asan_container_overflow_death_test() { + + // We'll give the string capacity 100 (all uninitialized memory, except for the null terminator). + basic_string, Alloc> myString; + myString.reserve(100); + + // Write to the element at index 50 to trigger an ASan container-overflow check. + CharType* myData = &myString[0]; + myData[50] = CharType{'A'}; +} + +// Test that ASan `container-overflow` checks can be disabled for a custom allocator. +template +void run_asan_annotations_disablement_test() { + + // ASan annotations are disabled for the `implicit_allocator_no_asan_annotations` allocator, + // which should make the container-overflow 'death test' pass. + run_asan_container_overflow_death_test>(); +} + template class Alloc> void run_custom_allocator_matrix() { run_tests>(); @@ -1869,6 +1914,11 @@ void run_allocator_matrix() { run_custom_allocator_matrix(); run_custom_allocator_matrix(); run_custom_allocator_matrix(); + + // To test ASan annotation disablement, we use an ad-hoc allocator type to avoid disrupting other + // tests that depend on annotations being enabled. Therefore, unlike the prior tests, + // this test is not parameterized by the allocator type. + run_asan_annotations_disablement_test(); } void test_DevCom_10116361() { @@ -1919,15 +1969,6 @@ void test_gh_3955() { assert(s == t); } -void test_gh_5251() { - // GH-5251 : ASan annotations do not prevent writing to allocated - // but uninitialized basic_string memory - string myString; - myString.reserve(100); - char* myData = &myString[0]; - myData[50] = 'A'; // ASan should fire! -} - int main(int argc, char* argv[]) { std_testing::death_test_executive exec([] { run_allocator_matrix(); @@ -1944,7 +1985,15 @@ int main(int argc, char* argv[]) { test_gh_3955(); }); #ifdef __SANITIZE_ADDRESS__ - exec.add_death_tests({test_gh_5251}); + exec.add_death_tests({ + run_asan_container_overflow_death_test, +#ifdef __cpp_char8_t + run_asan_container_overflow_death_test, +#endif // __cpp_char8_t + run_asan_container_overflow_death_test, + run_asan_container_overflow_death_test, + run_asan_container_overflow_death_test, + }); #endif // ASan instrumentation enabled return exec.run(argc, argv); } diff --git a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp index 93ac83e275..300b7daf25 100644 --- a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp @@ -12,6 +12,8 @@ #include #include +#include + #pragma warning(disable : 4984) // 'if constexpr' is a C++17 language extension #ifdef __clang__ @@ -53,6 +55,10 @@ struct non_trivial_can_throw { } non_trivial_can_throw& operator=(const non_trivial_can_throw&) { + ++i; + if (i == 0) { + throw i; + } return *this; } @@ -274,6 +280,29 @@ struct implicit_allocator : custom_test_allocator { STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 1); STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 2); +// Simple allocator that opts out of ASan annotations (via `_Disable_ASan_container_annotations_for_allocator`) +template +struct implicit_allocator_no_asan_annotations : implicit_allocator { + implicit_allocator_no_asan_annotations() = default; + template + constexpr implicit_allocator_no_asan_annotations( + const implicit_allocator_no_asan_annotations&) noexcept {} + + T* allocate(size_t n) { + T* mem = new T[n + 1]; + return mem + 1; + } + + void deallocate(T* p, size_t) noexcept { + delete[] (p - 1); + } +}; + +template +constexpr bool + _Disable_ASan_container_annotations_for_allocator> = + true; + template void test_push_pop() { using T = typename Alloc::value_type; @@ -1002,31 +1031,69 @@ void run_custom_allocator_matrix() { run_tests>(); } +// Test that writing to uninitialized memory in a vector triggers an ASan container-overflow error. +template > +void run_asan_container_overflow_death_test() { + // We'll give the vector capacity 100 (all uninitialized memory). + vector v; + v.reserve(100); + + // Write to the element at index 50 to trigger an ASan container-overflow check. + v.data()[50] = T{}; +} + +// Test that ASan `container-overflow` checks can be disabled for a custom allocator. +template +void run_asan_annotations_disablement_test() { + + // ASan annotations are disabled for the `implicit_allocator_no_asan_annotations` allocator, + // which should make the container-overflow 'death test' pass. + run_asan_container_overflow_death_test>(); +} + template void run_allocator_matrix() { run_tests>(); run_custom_allocator_matrix(); run_custom_allocator_matrix(); run_custom_allocator_matrix(); + + // To test ASan annotation disablement, we use an ad-hoc allocator type to avoid disrupting other + // tests that depend on annotations being enabled. Therefore, unlike the prior tests, + // this test is not parameterized by the allocator type. + run_asan_annotations_disablement_test(); } -int main() { - // Do some work even when we aren't instrumented - run_allocator_matrix(); +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec([] { + // Do some work even when we aren't instrumented + run_allocator_matrix(); #ifdef __SANITIZE_ADDRESS__ - run_allocator_matrix(); - run_allocator_matrix(); - run_allocator_matrix(); - run_allocator_matrix(); + run_allocator_matrix(); + run_allocator_matrix(); + run_allocator_matrix(); + run_allocator_matrix(); #ifndef __clang__ // TRANSITION, LLVM-35365 - test_push_back_throw(); - test_emplace_back_throw(); - test_insert_range_throw(); - test_insert_throw(); - test_emplace_throw(); - test_resize_throw(); - test_insert_n_throw(); + test_push_back_throw(); + test_emplace_back_throw(); + test_insert_range_throw(); + test_insert_throw(); + test_emplace_throw(); + test_resize_throw(); + test_insert_n_throw(); #endif // ^^^ no workaround ^^^ #endif // ASan instrumentation enabled + }); + +#ifdef __SANITIZE_ADDRESS__ + exec.add_death_tests({ + run_asan_container_overflow_death_test, + run_asan_container_overflow_death_test, + run_asan_container_overflow_death_test, + run_asan_container_overflow_death_test, + }); +#endif // ASan instrumentation enabled + + return exec.run(argc, argv); }