diff --git a/stl/inc/vector b/stl/inc/vector index 4a38d29e03..c616a02a10 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -482,81 +482,101 @@ public: private: #ifdef _INSERT_VECTOR_ANNOTATION - _CONSTEXPR20 void _Create_annotation() const noexcept { - // Annotates the shadow memory of the valid range - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Myend, _My_data._Mylast); + + _CONSTEXPR20 bool _Should_annotate() const noexcept { +#if _HAS_CXX20 + if (_STD is_constant_evaluated()) { + return false; + } +#endif // _HAS_CXX20 + + return _Asan_vector_should_annotate; } - _CONSTEXPR20 void _Remove_annotation() const noexcept { - // Removes the shadow memory annotation of the range - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Mylast, _My_data._Myend); + _CONSTEXPR20 const void* _Annotation_begin() const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Myfirst != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Myfirst); } - _CONSTEXPR20 void _Modify_annotation(const difference_type _Count) const noexcept { - // Extends/shrinks the annotated range by _Count - if (_Count == 0) { // nothing to do - // This also avoids calling _Apply_annotation() with null pointers - // when the vector has zero capacity, see GH-2464. - return; + _CONSTEXPR20 const void* _Annotation_at(const size_type _Index) const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Myfirst != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Myfirst + _Index); + } + + _CONSTEXPR20 const void* _Annotation_last() const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Mylast != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Mylast); + } + + _CONSTEXPR20 const void* _Annotation_last_with_difference(const difference_type _Count) const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Mylast != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Mylast + _Count); + } + + _CONSTEXPR20 const void* _Annotation_end() const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Myend != nullptr); + const void* const _End = _STD _Unfancy(_Mypair._Myval2._Myend); + + if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { + // If the allocator alignment is less than or equal to the ASan granularity, then we can + // realign the end forward to maximize the annotation coverage. + return _STD _Get_asan_aligned_after(_End); + } else { + // The allocation may not end on an ASan granularity boundary. In that case, annotating up to _End + // could affect shadow bytes for memory beyond this allocation, so restrict annotations to the largest + // aligned subrange inside the buffer. This may leave the tail of the buffer unannotated. + + return _End; // Using unaligned end will snap it to previous ASan granularity boundary. } + } - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Mylast, _My_data._Mylast + _Count); + _CONSTEXPR20 void _Create_annotation() const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { + return; + } + + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_end(), _Annotation_last()); + } } - static _CONSTEXPR20 void _Apply_annotation( - pointer _First_, pointer _End_, pointer _Old_last_, pointer _New_last_) noexcept { - _STL_INTERNAL_CHECK(_First_ != nullptr); - _STL_INTERNAL_CHECK(_End_ != nullptr); - _STL_INTERNAL_CHECK(_Old_last_ != nullptr); - _STL_INTERNAL_CHECK(_New_last_ != nullptr); + _CONSTEXPR20 void _Remove_annotation() const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { + return; + } + + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_end()); + } + } + _CONSTEXPR20 void _Modify_annotation(const difference_type _Count) const noexcept { if constexpr (!_Disable_ASan_container_annotations_for_allocator) { -#if _HAS_CXX20 - if (_STD is_constant_evaluated()) { + if (!_Should_annotate()) { return; } -#endif // _HAS_CXX20 - if (!_Asan_vector_should_annotate) { + if (_Count == 0) { // nothing to do + // This also avoids calling _Apply_annotation() with null pointers + // when the vector has zero capacity, see GH-2464. 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. - return; - } + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_last_with_difference(_Count)); + } + } - 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); + _CONSTEXPR20 void _Set_annotation(const size_type _Target_size) const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { + return; } + + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_at(_Target_size)); } } @@ -568,8 +588,15 @@ private: constexpr explicit _Asan_extend_guard(vector* _Myvec_, size_type _Target_size_) noexcept : _Myvec(_Myvec_), _Target_size(_Target_size_) { _STL_INTERNAL_CHECK(_Myvec != nullptr); - auto& _My_data = _Myvec->_Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Mylast, _My_data._Myfirst + _Target_size); + + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Myvec->_Should_annotate()) { + return; + } + + _CSTD __sanitizer_annotate_contiguous_container(_Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), + _Myvec->_Annotation_last(), _Myvec->_Annotation_at(_Target_size)); + } } _CONSTEXPR20 ~_Asan_extend_guard() { @@ -577,9 +604,15 @@ private: return; } - // Shrinks the shadow memory to the current size of the vector. - auto& _My_data = _Myvec->_Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Myfirst + _Target_size, _My_data._Mylast); + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Myvec->_Should_annotate()) { + return; + } + + // Shrinks the shadow memory to the current size of the vector. + _CSTD __sanitizer_annotate_contiguous_container(_Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), + _Myvec->_Annotation_at(_Target_size), _Myvec->_Annotation_last()); + } } _CONSTEXPR20 void _Release() noexcept { diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 1640e5dc61..9971b685c5 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -813,56 +813,29 @@ _INLINE_VAR constexpr size_t _Asan_granularity_mask = _Asan_granularity - 1; template constexpr bool _Disable_ASan_container_annotations_for_allocator = false; -struct _Asan_aligned_pointers { - const void* _First; - const void* _End; - - _NODISCARD constexpr const void* _Clamp_to_end(const void* _Mid) const noexcept { - _STL_INTERNAL_CHECK(_Mid >= _First); - if (_Mid > _End) { - return _End; - } else { - return _Mid; - } - } -}; - // The way that ASan shadow memory works, each eight byte block of memory ("shadow memory section") // has a single byte to mark it as either poison or valid. // Each section has 0 to 8 "valid" bytes followed by poison bytes, so: // ``` -// [ v v v p p p p p ] +// [ v v v p p p p p ] (encoded as 03 for 'first 3 bytes are valid') // ``` // or // ``` -// [ v v v v v v v v ] +// [ v v v v v v v v ] (encoded as 00 for 'all bytes valid') // ``` // are okay, but // ``` // [ p p p p v v v v ] // ``` -// is not. -// -// This function exists to fix up `first` and `end` pointers so that one can call -// `__sanitizer_annotate_contiguous_container`: -// -// - `__sanitizer_annotate_contiguous_container` checks that `first` is aligned to an 8-byte boundary -// - if `end` is not aligned to an 8-byte boundary, `__sanitizer_annotate_contiguous_container` still poisons the -// remaining bytes in the shadow memory section. -// -// Because of the second property, we can only mark poison up to the final aligned address before the true `last`. -// Otherwise, we'd poison the memory _after_ `last` as well. -// For the first property, we can assume that everything before `first` in the shadow memory section is valid -// (since otherwise we couldn't mark `first` valid), and so we just return back the first address in -// `first`'s shadow memory section. +// is not. There are no semantics for describing the first X bytes as being poisoned. // // ### Example // // ```cpp // struct alignas(8) cat { -// int meow; // bytes [0, 4) +// int meow; // bytes [0, 4) // char buffer[16]; // bytes [4, 20) -// int purr; // bytes [20, 24) +// int purr; // bytes [20, 24) // }; // ``` // @@ -883,41 +856,90 @@ struct _Asan_aligned_pointers { // | meow | buffer | purr | // [ v v v v p p p p ][ p p p p p p p p ][ v v v v v v v v ] // sm1 sm2 sm3 +// Shadow: 04 fc 00 // ``` // -// We call `aligned = _Get_asan_aligned_first_end(cat.buffer, cat.buffer + 16);`, and we get back +// For this, we can call `__sanitizer_annotate_contiguous_container`. +// Since container poisoning is a set of valid bytes followed by a set of poisoned bytes, we describe this fully to ASan +// by passing a pointer to the first valid byte, the middle pointer (pointing to the first poisoned byte), and the end +// pointer. For the unaligned beginning pointer, ASan will handle the unalignment assuming the bytes to the left are +// valid. For the unaligned end pointer, since there are no 'first X bytes are poisoned' semantics, ASan will just mark +// the entire shadow memory section as valid. +// +// To generate the above shadow memory state, we would call like this: // // ```cpp -// aligned = { -// ._First = &cat.meow, -// ._End = cat.buffer + 12, -// }; +// __sanitizer_annotate_contiguous_container( +// cat.buffer, // First +// cat.buffer + 16 // End +// cat.buffer + 16, // Old middle pointer +// cat.buffer); // New middle pointer // ``` // -// Then, we poison as much of buffer as we can via +// Correctly describing the old middle pointer is important since ASan will not check state before the existing shadow +// memory state performing the transformation. +// +// In cases where unalignment must be supported, we can miss coverage due to ASan's 8-byte alignment requirements. +// However, most allocators on Windows will only return 8-byte aligned pointers. This gives us an opportunity to provide +// extra coverage, because if everything is 8-byte aligned, we can extend, instead of reduce, the concept of the end of +// the buffer to the next 8-byte aligned boundary. _Get_asan_aligned_after is a helper function provided for this. +// +// ### Example // +// Let's say we took the above struct, aligned the members, and shortened the buffer to 12, and left our annotation code +// the same. // ```cpp +// struct alignas(8) cat { +// int alignas(8) meow; // bytes [0, 4) +// char alignas(8) buffer[12]; // bytes [8, 20) +// int alignas(8) purr; // bytes [24, 28) +// }; +// +// ... +// // __sanitizer_annotate_contiguous_container( -// aligned._First, -// aligned._End, -// cat.buffer, -// aligned._Clamp_to_end(cat.buffer + 16)); +// cat.buffer, // First +// cat.buffer + 12 // End +// cat.buffer + 12, // Old middle pointer +// cat.buffer // New middle pointer // ``` // -// We are allowed to assume that `&cat.meow` is valid, since otherwise `cat.buffer + [0, 4)` could not be valid. -// We cannot poison up to `cat.buffer + 16`, since then `&purr` could not be valid. -// Thus, this results in the shadow memory state from the second example. -_NODISCARD inline _Asan_aligned_pointers _Get_asan_aligned_first_end( - const void* const _First, const void* const _End) noexcept { - return { - reinterpret_cast(reinterpret_cast(_First) & ~_Asan_granularity_mask), - reinterpret_cast(reinterpret_cast(_End) & ~_Asan_granularity_mask), - }; -} - -// When we can assume that the allocator we are using will always align allocations to the 8-byte, -// we can simply push the `_End` pointer to the end of the shadow memory section. -// This is _not_ safe in general (see _Get_asan_aligned_first_end's comment for why). +// Under this, the shadow buffer would look like this: +// ``` +// | meow | pad | buffer | pad | purr | pad | +// [ v v v v v v v v ][ p p p p p p p p ][ v v v v v v v v ][ v v v v v v v v ] +// sm1 sm2 sm3 sm4 +// Shadow: 00 fc 00 00 +// ``` +// +// Currently, there is no coverage on the last 4 bytes of the buffer due to the previous rules, +// but when everything is 8-byte aligned, we can use the fact there is padding to extend the poisoning. +// +// ``` +// char *end = _Get_asan_aligned_after(cat.buffer + 12); +// __sanitizer_annotate_contiguous_container( +// cat.buffer, // First +// end, // End +// end, // Old middle pointer +// cat.buffer); // New middle pointer +// ``` +// +// With this adjustment, the shadow has full coverage of the buffer, and we are not poisoning any memory that will be +// accessed, just padding. +// ``` +// | meow | pad | buffer | pad | purr | pad | +// [ v v v v v v v v ][ p p p p p p p p ][ p p p p p p p p ][ v v v v v v v v ] +// sm1 sm2 sm3 sm4 +// Shadow: 00 fc fc 00 +// ``` +// +// This example is conceptually the same for why we can apply this technique in general to any allocator guaranteed to +// only return 8-byte aligned pointers. +// +// Note that caution is warranted with this technique, particularly when differentiating between clearing the +// annotation (e.g. during destruction when we want everything valid) and setting the annotation to be fully valid +// (which would leave poisoning bytes in the padding). +// _NODISCARD inline const void* _Get_asan_aligned_after(const void* const _End) noexcept { return reinterpret_cast( (reinterpret_cast(_End) + _Asan_granularity_mask) & ~_Asan_granularity_mask); diff --git a/stl/inc/xstring b/stl/inc/xstring index b371e31d6b..14e32c2b44 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -668,79 +668,84 @@ private: #endif // _HAS_CXX17 #ifdef _INSERT_STRING_ANNOTATION - _CONSTEXPR20 void _Create_annotation() const noexcept { - // Annotates the valid range with shadow memory - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myptr(), _My_data._Myres, _My_data._Myres, _My_data._Mysize); + + _CONSTEXPR20 bool _Should_annotate() const noexcept { +#if _HAS_CXX20 + if (_STD is_constant_evaluated()) { + return false; + } +#endif // _HAS_CXX20 + // Only annotate if the string is large enough to be worth it, and if the user hasn't opted out via + // _Asan_string_should_annotate. + return _Mypair._Myval2._Myres > _Small_string_capacity && _Asan_string_should_annotate; } - _CONSTEXPR20 void _Remove_annotation() const noexcept { - // Removes annotation of the range with shadow memory - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myptr(), _My_data._Myres, _My_data._Mysize, _My_data._Myres); + _CONSTEXPR20 const void* _Annotation_begin() const noexcept { + return _Mypair._Myval2._Myptr(); } - _CONSTEXPR20 void _Modify_annotation(const size_type _Old_size, const size_type _New_size) const noexcept { - if (_Old_size == _New_size) { - return; - } + _CONSTEXPR20 const void* _Annotation_at(const size_type _Size) const noexcept { + return _Mypair._Myval2._Myptr() + _Size + 1; // +1 to include null terminator in annotation + } - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myptr(), _My_data._Myres, _Old_size, _New_size); + _CONSTEXPR20 const void* _Annotation_last() const noexcept { + return _Mypair._Myval2._Myptr() + _Mypair._Myval2._Mysize + 1; // +1 to include null terminator in annotation } - 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 { + _CONSTEXPR20 const void* _Annotation_end() const noexcept { + auto& _My_data = _Mypair._Myval2; + const void* const _End = _My_data._Myptr() + _My_data._Myres + 1; // +1 to include null terminator in annotation + + constexpr bool _Large_string_always_asan_aligned = + (_Container_allocation_minimum_asan_alignment) >= _Asan_granularity; + + if constexpr (_Large_string_always_asan_aligned) { + // If the allocator alignment is less than or equal to the ASan granularity, then we can + // realign the end forward to maximize the annotation coverage. + return _STD _Get_asan_aligned_after(_End); + } else { + // The allocation may not end on an ASan granularity boundary. In that case, annotating up to _End + // could affect shadow bytes for memory beyond this allocation, so restrict annotations to the largest + // aligned subrange inside the buffer. This may leave the tail of the buffer unannotated. + + return _End; // Using unaligned end will snap it to previous ASan granularity boundary. + } + } + + _CONSTEXPR20 void _Create_annotation() const noexcept { if constexpr (!_Disable_ASan_container_annotations_for_allocator) { -#if _HAS_CXX20 - if (_STD is_constant_evaluated()) { + if (!_Should_annotate()) { return; } -#endif // _HAS_CXX20 - // Don't annotate small strings; only annotate on the heap. - if (_Capacity <= _Small_string_capacity || !_Asan_string_should_annotate) { + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_end(), _Annotation_last()); + } + } + + _CONSTEXPR20 void _Remove_annotation() const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_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; - - constexpr bool _Large_string_always_asan_aligned = - (_Container_allocation_minimum_asan_alignment) >= _Asan_granularity; + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_end()); + } + } - // 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. + _CONSTEXPR20 void _Modify_annotation(const size_type _Old_size, const size_type _New_size) const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { + return; + } - _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); + if (_Old_size == _New_size) { + return; } - 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); + + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_at(_Old_size), _Annotation_at(_New_size)); } } diff --git a/tests/std/include/test_asan_support.hpp b/tests/std/include/test_asan_support.hpp new file mode 100644 index 0000000000..b71d20c1d9 --- /dev/null +++ b/tests/std/include/test_asan_support.hpp @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wc++17-extensions" // constexpr if is a C++17 extension +#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) +#else // ^^^ clang / msvc vvv +#define NO_SANITIZE_ADDRESS __declspec(no_sanitize_address) +#endif // __clang__ + +#ifdef __SANITIZE_ADDRESS__ +#include +#include +#include +#include + +extern "C" uintptr_t __asan_shadow_memory_dynamic_address; +extern "C" void* __sanitizer_contiguous_container_find_bad_address( + const void* beg, const void* mid, const void* end) noexcept; +extern "C" void __asan_describe_address(void*) noexcept; + +namespace std_testing { + namespace asan { + constexpr uintptr_t shadow_granularity = 8; + + inline const char* round_down_to_shadow_granularity(const char* const addr) { + return reinterpret_cast(reinterpret_cast(addr) & ~(shadow_granularity - 1)); + } + + inline const char* round_up_to_shadow_granularity(const char* const addr) { + return reinterpret_cast( + (reinterpret_cast(addr) + shadow_granularity - 1) & ~(shadow_granularity - 1)); + } + + NO_SANITIZE_ADDRESS inline unsigned char* shadow_addr_of(const void* const addr) { + return reinterpret_cast( + (reinterpret_cast(addr) >> 3) + __asan_shadow_memory_dynamic_address); + } + + NO_SANITIZE_ADDRESS inline unsigned char shadow_byte_of(const void* addr) { + return *shadow_addr_of(addr); + } + + inline void print_shadow_bytes(const void* addr, size_t num_bytes, const void* error_addr = nullptr, + unsigned char expected_shadow_byte = 0xff /*unused shadow byte*/) { + constexpr size_t shadow_bytes_per_line = 16; + constexpr uintptr_t bytes_per_line_mask = (shadow_bytes_per_line * shadow_granularity) - 1; + + const char* begin = reinterpret_cast( + reinterpret_cast(addr) & ~(shadow_granularity - 1)); // align down to shadow boundary + const char* end = reinterpret_cast(addr) + num_bytes; + + if (error_addr) { + assert(error_addr >= begin && error_addr <= end); + fprintf(stderr, "ERROR: Expected %02x shadow byte at %p, but got %02x.\n", expected_shadow_byte, + error_addr, shadow_byte_of(error_addr)); + } + + fprintf(stderr, "Shadow for addresses %p -> %p (%p -> %p):", begin, end, shadow_addr_of(begin), + shadow_addr_of(end)); + + const char* const print_begin = reinterpret_cast( + reinterpret_cast(begin) - bytes_per_line_mask & ~bytes_per_line_mask); + const char* const print_end = reinterpret_cast( + (reinterpret_cast(end) + bytes_per_line_mask) & ~bytes_per_line_mask); + for (const char* p = print_begin; p < print_end; p += 8) { + if ((reinterpret_cast(p) & bytes_per_line_mask) == 0) { + fprintf(stderr, "\n %p: ", p); + } + if (reinterpret_cast(p) + == (reinterpret_cast(error_addr) & ~(shadow_granularity - 1))) { + fprintf(stderr, "\b[%02x]", shadow_byte_of(p)); + } else { + fprintf(stderr, "%02x ", shadow_byte_of(p)); + } + } + fprintf(stderr, "\n"); + } + + inline bool verify_poisoning_cleared(void* ptr, size_t num_bytes) { + const char* const begin = round_down_to_shadow_granularity(reinterpret_cast(ptr)); + const char* end = reinterpret_cast(ptr) + num_bytes; + + void* bad_addr = __sanitizer_contiguous_container_find_bad_address(begin, end, end); + if (bad_addr) { + print_shadow_bytes(ptr, num_bytes, bad_addr, 0); + __asan_describe_address(bad_addr); + return false; + } + return true; + } + + bool verify_container_poisoning( + const void* const addr, const size_t valid_size, const size_t total_capacity, const bool is_overpoisoned) { + const char* const begin = round_down_to_shadow_granularity(reinterpret_cast(addr)); + const char* const mid = reinterpret_cast(addr) + valid_size; + const char* end = reinterpret_cast(addr) + total_capacity; + + if (is_overpoisoned) { + end = round_up_to_shadow_granularity(end); + } + + void* bad_addr = __sanitizer_contiguous_container_find_bad_address(begin, mid, end); + if (bad_addr) { + const char* const b1 = round_down_to_shadow_granularity(mid); + const char* const b2 = round_up_to_shadow_granularity(mid); + + const unsigned char expected_byte = + static_cast((bad_addr >= b1 && bad_addr < b2) ? static_cast(mid - b1) + : (bad_addr < mid) ? 0 + : 0xfc); + + print_shadow_bytes(addr, total_capacity, bad_addr, expected_byte); + __asan_describe_address(bad_addr); + return false; + } + return true; + } + } // namespace asan +} // namespace std_testing +#endif // __SANITIZER_ADDRESS__ 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 d91b5774b3..617ea23d59 100644 --- a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp @@ -32,16 +32,13 @@ int main() {} #include #endif // _HAS_CXX17 +#include #include using namespace std; #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) -#ifdef __SANITIZE_ADDRESS__ -extern "C" int __sanitizer_verify_contiguous_container(const void* beg, const void* mid, const void* end) noexcept; -#endif // ASan instrumentation enabled - constexpr auto literal_input = "Hello fluffy kittens"; #ifdef __cpp_char8_t constexpr auto literal_input_u8 = u8"Hello fluffy kittens"; @@ -178,6 +175,17 @@ class input_iterator_tester { } }; +template +bool verify_poisoning_cleared(CharType* ptr, size_t capacity) { +#ifdef __SANITIZE_ADDRESS__ + return std_testing::asan::verify_poisoning_cleared(ptr, capacity * sizeof(CharType)); +#else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv + (void) ptr; + (void) capacity; + return true; +#endif // ASan instrumentation disabled +} + template bool verify_string(const basic_string, Alloc>& str) { #ifdef __SANITIZE_ADDRESS__ @@ -185,24 +193,9 @@ bool verify_string(const basic_string, Alloc>& s return true; } - const void* const buffer = str.data(); - const void* const buf_end = str.data() + str.capacity() + 1; - - constexpr bool _Large_string_always_aligned = - (_Container_allocation_minimum_asan_alignment>) >= 8; - - _Asan_aligned_pointers aligned; - if constexpr (_Large_string_always_aligned) { - aligned = {buffer, _Get_asan_aligned_after(buf_end)}; - } else { - aligned = _Get_asan_aligned_first_end(buffer, buf_end); - } - assert(aligned._First != aligned._End); - - const void* const mid = str.data() + str.size() + 1; - const void* const fixed_mid = aligned._Clamp_to_end(mid); - - return __sanitizer_verify_contiguous_container(aligned._First, fixed_mid, aligned._End) != 0; + return std_testing::asan::verify_container_poisoning(str.data(), (str.size() + 1) * sizeof(CharType), + (str.capacity() + 1) * sizeof(CharType), + (_Container_allocation_minimum_asan_alignment>) >= 8); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv (void) str; return true; @@ -241,7 +234,8 @@ struct aligned_allocator : public custom_test_allocator, aligned_allocator>> == 8); +template +struct extra_space_aligned_allocator : public custom_test_allocator { + static constexpr size_t _Minimum_asan_allocation_alignment = 8; + static constexpr size_t extra_space_per_side = 64 / sizeof(CharType); + + extra_space_aligned_allocator() = default; + template + constexpr extra_space_aligned_allocator(const extra_space_aligned_allocator&) noexcept {} + + CharType* allocate(size_t n) { + CharType* mem = new CharType[n + 2 * extra_space_per_side]; + return mem + extra_space_per_side; + } + + void deallocate(CharType* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - extra_space_per_side, n + 2 * extra_space_per_side)); + delete[] (p - extra_space_per_side); + } +}; +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_aligned_allocator>> + == 8); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_aligned_allocator>> + == 8); + template struct explicit_allocator : public custom_test_allocator { static constexpr size_t _Minimum_asan_allocation_alignment = alignof(CharType); @@ -264,7 +284,8 @@ struct explicit_allocator : public custom_test_allocator, implicit_allocator>> == 2); +template +struct extra_space_unaligned_allocator : public custom_test_allocator { + static constexpr size_t extra_space_per_side = 64 / sizeof(CharType); + + extra_space_unaligned_allocator() = default; + template + constexpr extra_space_unaligned_allocator(const extra_space_unaligned_allocator&) noexcept {} + + CharType* allocate(size_t n) { + CharType* mem = new CharType[n + 1 + 2 * extra_space_per_side]; + return mem + extra_space_per_side + 1; + } + + void deallocate(CharType* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2 * extra_space_per_side)); + delete[] (p - 1 - extra_space_per_side); + } +}; +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_unaligned_allocator>> + == 1); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_unaligned_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 { @@ -910,6 +957,15 @@ void test_append() { str op_char_rstr_sso_growing = CharType{'!'} + str(max_sso_size, CharType{'b'}); assert(verify_string(op_char_rstr_sso_growing)); } + + { // stress test append, testing for correctness after triggering resize (gh-6276) + for (size_t i = 1; i < 1024; ++i) { + str stress_append{}; + stress_append.assign(i, CharType{'a'}); + stress_append.append({CharType{'a'}}); + assert(verify_string(stress_append)); + } + } } template @@ -1542,6 +1598,15 @@ void test_removal() { assert(verify_string(pop_back_shrinking)); } + { // stress test pop_back - testing for correctness after triggering resize (gh-6276) + for (size_t i = 1; i < 1024; ++i) { + str stress_pop_back{}; + stress_pop_back.assign(i, CharType{'a'}); + stress_pop_back.pop_back(); + assert(verify_string(stress_pop_back)); + } + } + { // shrink_to_fit str shrink_to_fit(32, CharType{'a'}); shrink_to_fit.resize(min_large_size); @@ -1918,8 +1983,10 @@ template void run_allocator_matrix() { run_tests>(); run_custom_allocator_matrix(); + run_custom_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, @@ -1975,6 +2042,45 @@ void test_gh_3955() { assert(s == t); } + +void test_gh_6276() { + { + basic_string, extra_space_unaligned_allocator> unaligned{ + L"1234567890123456789012"}; + assert(verify_string(unaligned)); + + unaligned.append(L"3"); + assert(verify_string(unaligned)); + } + + { + basic_string, extra_space_unaligned_allocator> unaligned{ + L"1234567890123456789012"}; + assert(verify_string(unaligned)); + + unaligned.pop_back(); + assert(verify_string(unaligned)); + } + + { + basic_string, extra_space_aligned_allocator> aligned{ + L"12345678901234567890123"}; + assert(verify_string(aligned)); + + aligned.append(L"4"); + assert(verify_string(aligned)); + } + + { + basic_string, extra_space_aligned_allocator> aligned{ + L"12345678901234567890123"}; + assert(verify_string(aligned)); + + aligned.pop_back(); + assert(verify_string(aligned)); + } +} + int main(int argc, char* argv[]) { std_testing::death_test_executive exec([] { run_allocator_matrix(); @@ -1989,6 +2095,7 @@ int main(int argc, char* argv[]) { test_DevCom_10109507(); test_gh_3883(); test_gh_3955(); + test_gh_6276(); }); #ifdef __SANITIZE_ADDRESS__ exec.add_death_tests({ 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 589fa132cf..675964a7c5 100644 --- a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp @@ -17,6 +17,7 @@ int main() {} #include #include +#include #include #pragma warning(disable : 4984) // 'if constexpr' is a C++17 language extension @@ -37,13 +38,6 @@ using namespace std; #endif #endif -#ifdef __SANITIZE_ADDRESS__ -extern "C" { -void* __sanitizer_contiguous_container_find_bad_address(const void* beg, const void* mid, const void* end) noexcept; -void __asan_describe_address(void*) noexcept; -} -#endif // ASan instrumentation enabled - struct non_trivial_can_throw { non_trivial_can_throw() { ++i; @@ -164,45 +158,22 @@ class input_iterator_tester { } }; +template +bool verify_poisoning_cleared(CharType* ptr, size_t capacity) { +#ifdef __SANITIZE_ADDRESS__ + return std_testing::asan::verify_poisoning_cleared(ptr, capacity * sizeof(CharType)); +#else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv + (void) ptr; + (void) capacity; + return true; +#endif // ASan instrumentation disabled +} + template bool verify_vector(vector& vec) { #ifdef __SANITIZE_ADDRESS__ - const void* buffer = vec.data(); - const void* buf_end = vec.data() + vec.capacity(); - _Asan_aligned_pointers aligned; - - if constexpr ((_Container_allocation_minimum_asan_alignment>) >= 8) { - aligned = {buffer, buf_end}; - } else { - aligned = _Get_asan_aligned_first_end(buffer, buf_end); - if (aligned._First == aligned._End) { - return true; - } - } - - const void* const mid = vec.data() + vec.size(); - const void* const fixed_mid = aligned._Clamp_to_end(mid); - - void* bad_address = __sanitizer_contiguous_container_find_bad_address(aligned._First, fixed_mid, aligned._End); - if (bad_address == nullptr) { - return true; - } - - if (bad_address < mid) { - cout << bad_address << " was marked as poisoned when it should not be." << endl; - } else { - cout << bad_address << " was not marked as poisoned when it should be." << endl; - } - cout << "Vector State:" << endl; - cout << " begin: " << buffer << endl; - cout << " aligned begin: " << aligned._First << endl; - cout << " last: " << mid << endl; - cout << " aligned_last: " << fixed_mid << endl; - cout << " end: " << buf_end << endl; - cout << " aligned_end: " << aligned._End << endl; - __asan_describe_address(bad_address); - - return false; + return std_testing::asan::verify_container_poisoning(vec.data(), vec.size() * sizeof(T), vec.capacity() * sizeof(T), + _Container_allocation_minimum_asan_alignment> >= 8); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv (void) vec; return true; @@ -241,12 +212,34 @@ struct aligned_allocator : custom_test_allocator { return new T[n]; } - void deallocate(T* p, size_t) noexcept { + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p, n)); delete[] p; } }; STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 8); +template +struct extra_space_aligned_allocator : public custom_test_allocator { + static constexpr size_t _Minimum_asan_allocation_alignment = 8; + static constexpr size_t extra_space_per_side = 64 / sizeof(T); + + extra_space_aligned_allocator() = default; + template + constexpr extra_space_aligned_allocator(const extra_space_aligned_allocator&) noexcept {} + + T* allocate(size_t n) { + T* mem = new T[n + 2 * extra_space_per_side]; + return mem + extra_space_per_side; + } + + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - extra_space_per_side, n + 2 * extra_space_per_side)); + delete[] (p - extra_space_per_side); + } +}; +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 8); + template struct explicit_allocator : custom_test_allocator { static constexpr size_t _Minimum_asan_allocation_alignment = alignof(T); @@ -260,7 +253,8 @@ struct explicit_allocator : custom_test_allocator { return mem + 1; } - void deallocate(T* p, size_t) noexcept { + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1, n + 1)); delete[] (p - 1); } }; @@ -278,13 +272,36 @@ struct implicit_allocator : custom_test_allocator { return mem + 1; } - void deallocate(T* p, size_t) noexcept { + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1, n + 1)); delete[] (p - 1); } }; STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 1); STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 2); +template +struct extra_space_unaligned_allocator : public custom_test_allocator { + static constexpr size_t extra_space_per_side = 64 / sizeof(T); + + extra_space_unaligned_allocator() = default; + template + constexpr extra_space_unaligned_allocator(const extra_space_unaligned_allocator&) noexcept {} + + T* allocate(size_t n) { + T* mem = new T[n + 1 + 2 * extra_space_per_side]; + return mem + extra_space_per_side + 1; + } + + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2 * extra_space_per_side)); + delete[] (p - 1 - extra_space_per_side); + } +}; +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 { @@ -322,6 +339,24 @@ void test_push_pop() { assert(verify_vector(v)); } +template +void test_push_pop_resize() { + using T = typename Alloc::value_type; + + // Try push/pop at various sizes to cover resize code path (gh-6276) + for (size_t i = 1; i < Size; ++i) { + vector v(i, T()); + v.push_back(T()); + assert(verify_vector(v)); + } + + for (size_t i = 1; i < Size; ++i) { + vector v(i, T()); + v.pop_back(); + assert(verify_vector(v)); + } +} + template void test_reserve_shrink() { using T = typename Alloc::value_type; @@ -1015,6 +1050,7 @@ void test_empty() { template void run_tests() { test_push_pop(); + test_push_pop_resize(); test_reserve_shrink(); test_emplace_pop(); test_move_assign(); @@ -1060,8 +1096,10 @@ template void run_allocator_matrix() { run_tests>(); run_custom_allocator_matrix(); + run_custom_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, @@ -1069,6 +1107,40 @@ void run_allocator_matrix() { run_asan_annotations_disablement_test(); } +void test_gh_6276() { + { + vector> unaligned{22, L'a'}; + assert(verify_vector(unaligned)); + + unaligned.push_back(L'b'); + assert(verify_vector(unaligned)); + } + + { + vector> unaligned{22, L'a'}; + assert(verify_vector(unaligned)); + + unaligned.pop_back(); + assert(verify_vector(unaligned)); + } + + { + vector> aligned{23, L'a'}; + assert(verify_vector(aligned)); + + aligned.push_back(L'a'); + assert(verify_vector(aligned)); + } + + { + vector> aligned{23, L'a'}; + assert(verify_vector(aligned)); + + aligned.pop_back(); + assert(verify_vector(aligned)); + } +} + int main(int argc, char* argv[]) { std_testing::death_test_executive exec([] { // Do some work even when we aren't instrumented @@ -1089,6 +1161,8 @@ int main(int argc, char* argv[]) { test_insert_n_throw(); #endif // ^^^ no workaround ^^^ #endif // ASan instrumentation enabled + + test_gh_6276(); }); #ifdef __SANITIZE_ADDRESS__