Skip to content

Commit fc15609

Browse files
davidmrdavidCaseyCarterStephanTLavavej
authored
Add the ability to opt-out of ASan container annotations on a per-allocator basis (#5241)
Co-authored-by: Casey Carter <[email protected]> Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent 2314e1a commit fc15609

File tree

5 files changed

+227
-102
lines changed

5 files changed

+227
-102
lines changed

stl/inc/vector

+38-36
Original file line numberDiff line numberDiff line change
@@ -483,48 +483,50 @@ private:
483483
_STL_INTERNAL_CHECK(_Old_last_ != nullptr);
484484
_STL_INTERNAL_CHECK(_New_last_ != nullptr);
485485

486+
if constexpr (!_Disable_ASan_container_annotations_for_allocator<allocator_type>) {
486487
#if _HAS_CXX20
487-
if (_STD is_constant_evaluated()) {
488-
return;
489-
}
488+
if (_STD is_constant_evaluated()) {
489+
return;
490+
}
490491
#endif // _HAS_CXX20
491492

492-
if (!_Asan_vector_should_annotate) {
493-
return;
494-
}
495-
496-
const void* const _First = _STD _Unfancy(_First_);
497-
const void* const _End = _STD _Unfancy(_End_);
498-
const void* const _Old_last = _STD _Unfancy(_Old_last_);
499-
const void* const _New_last = _STD _Unfancy(_New_last_);
500-
if constexpr ((_Container_allocation_minimum_asan_alignment<vector>) >= _Asan_granularity) {
501-
// old state:
502-
// [_First, _Old_last) valid
503-
// [_Old_last, _End) poison
504-
// new state:
505-
// [_First, _New_last) valid
506-
// [_New_last, asan_aligned_after(_End)) poison
507-
_CSTD __sanitizer_annotate_contiguous_container(
508-
_First, _STD _Get_asan_aligned_after(_End), _Old_last, _New_last);
509-
} else {
510-
const auto _Aligned = _STD _Get_asan_aligned_first_end(_First, _End);
511-
if (_Aligned._First == _Aligned._End) {
512-
// The buffer does not end at least one shadow memory section; nothing to do.
493+
if (!_Asan_vector_should_annotate) {
513494
return;
514495
}
515496

516-
const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last);
517-
const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last);
518-
519-
// old state:
520-
// [_Aligned._First, _Old_fixed) valid
521-
// [_Old_fixed, _Aligned._End) poison
522-
// [_Aligned._End, _End) valid
523-
// new state:
524-
// [_Aligned._First, _New_fixed) valid
525-
// [_New_fixed, _Aligned._End) poison
526-
// [_Aligned._End, _End) valid
527-
_CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed);
497+
const void* const _First = _STD _Unfancy(_First_);
498+
const void* const _End = _STD _Unfancy(_End_);
499+
const void* const _Old_last = _STD _Unfancy(_Old_last_);
500+
const void* const _New_last = _STD _Unfancy(_New_last_);
501+
if constexpr ((_Container_allocation_minimum_asan_alignment<vector>) >= _Asan_granularity) {
502+
// old state:
503+
// [_First, _Old_last) valid
504+
// [_Old_last, _End) poison
505+
// new state:
506+
// [_First, _New_last) valid
507+
// [_New_last, asan_aligned_after(_End)) poison
508+
_CSTD __sanitizer_annotate_contiguous_container(
509+
_First, _STD _Get_asan_aligned_after(_End), _Old_last, _New_last);
510+
} else {
511+
const auto _Aligned = _STD _Get_asan_aligned_first_end(_First, _End);
512+
if (_Aligned._First == _Aligned._End) {
513+
// The buffer does not end at least one shadow memory section; nothing to do.
514+
return;
515+
}
516+
517+
const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last);
518+
const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last);
519+
520+
// old state:
521+
// [_Aligned._First, _Old_fixed) valid
522+
// [_Old_fixed, _Aligned._End) poison
523+
// [_Aligned._End, _End) valid
524+
// new state:
525+
// [_Aligned._First, _New_fixed) valid
526+
// [_New_fixed, _Aligned._End) poison
527+
// [_Aligned._End, _End) valid
528+
_CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed);
529+
}
528530
}
529531
}
530532

stl/inc/xmemory

+4
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,10 @@ struct _Simple_types { // wraps types from allocators with simple addressing for
805805
_INLINE_VAR constexpr size_t _Asan_granularity = 8;
806806
_INLINE_VAR constexpr size_t _Asan_granularity_mask = _Asan_granularity - 1;
807807

808+
// Controls whether ASan `container-overflow` errors are reported for this allocator.
809+
template <class>
810+
constexpr bool _Disable_ASan_container_annotations_for_allocator = false;
811+
808812
struct _Asan_aligned_pointers {
809813
const void* _First;
810814
const void* _End;

stl/inc/xstring

+45-42
Original file line numberDiff line numberDiff line change
@@ -622,54 +622,57 @@ private:
622622

623623
static _CONSTEXPR20 void _Apply_annotation(const value_type* const _First, const size_type _Capacity,
624624
const size_type _Old_size, const size_type _New_size) noexcept {
625+
if constexpr (!_Disable_ASan_container_annotations_for_allocator<allocator_type>) {
625626
#if _HAS_CXX20
626-
if (_STD is_constant_evaluated()) {
627-
return;
628-
}
627+
if (_STD is_constant_evaluated()) {
628+
return;
629+
}
629630
#endif // _HAS_CXX20
630-
// Don't annotate small strings; only annotate on the heap.
631-
if (_Capacity <= _Small_string_capacity || !_Asan_string_should_annotate) {
632-
return;
633-
}
634631

635-
// Note that `_Capacity`, `_Old_size`, and `_New_size` do not include the null terminator
636-
const void* const _End = _First + _Capacity + 1;
637-
const void* const _Old_last = _First + _Old_size + 1;
638-
const void* const _New_last = _First + _New_size + 1;
632+
// Don't annotate small strings; only annotate on the heap.
633+
if (_Capacity <= _Small_string_capacity || !_Asan_string_should_annotate) {
634+
return;
635+
}
639636

640-
constexpr bool _Large_string_always_asan_aligned =
641-
(_Container_allocation_minimum_asan_alignment<basic_string>) >= _Asan_granularity;
637+
// Note that `_Capacity`, `_Old_size`, and `_New_size` do not include the null terminator
638+
const void* const _End = _First + _Capacity + 1;
639+
const void* const _Old_last = _First + _Old_size + 1;
640+
const void* const _New_last = _First + _New_size + 1;
642641

643-
// for the non-aligned buffer options, the buffer must always have size >= 9 bytes,
644-
// so it will always end at least one shadow memory section.
642+
constexpr bool _Large_string_always_asan_aligned =
643+
(_Container_allocation_minimum_asan_alignment<basic_string>) >= _Asan_granularity;
645644

646-
_Asan_aligned_pointers _Aligned;
647-
if constexpr (_Large_string_always_asan_aligned) {
648-
_Aligned = {_First, _STD _Get_asan_aligned_after(_End)};
649-
} else {
650-
_Aligned = _STD _Get_asan_aligned_first_end(_First, _End);
651-
}
652-
const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last);
653-
const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last);
654-
655-
// --- always aligned case ---
656-
// old state:
657-
// [_First, _Old_last) valid
658-
// [_Old_last, asan_aligned_after(_End)) poison
659-
// new state:
660-
// [_First, _New_last) valid
661-
// [_New_last, asan_aligned_after(_End)) poison
662-
663-
// --- sometimes non-aligned case ---
664-
// old state:
665-
// [_Aligned._First, _Old_fixed) valid
666-
// [_Old_fixed, _Aligned._End) poison
667-
// [_Aligned._End, _End) valid
668-
// new state:
669-
// [_Aligned._First, _New_fixed) valid
670-
// [_New_fixed, _Aligned._End) poison
671-
// [_Aligned._End, _End) valid
672-
_CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed);
645+
// for the non-aligned buffer options, the buffer must always have size >= 9 bytes,
646+
// so it will always end at least one shadow memory section.
647+
648+
_Asan_aligned_pointers _Aligned;
649+
if constexpr (_Large_string_always_asan_aligned) {
650+
_Aligned = {_First, _STD _Get_asan_aligned_after(_End)};
651+
} else {
652+
_Aligned = _STD _Get_asan_aligned_first_end(_First, _End);
653+
}
654+
const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last);
655+
const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last);
656+
657+
// --- always aligned case ---
658+
// old state:
659+
// [_First, _Old_last) valid
660+
// [_Old_last, asan_aligned_after(_End)) poison
661+
// new state:
662+
// [_First, _New_last) valid
663+
// [_New_last, asan_aligned_after(_End)) poison
664+
665+
// --- sometimes non-aligned case ---
666+
// old state:
667+
// [_Aligned._First, _Old_fixed) valid
668+
// [_Old_fixed, _Aligned._End) poison
669+
// [_Aligned._End, _End) valid
670+
// new state:
671+
// [_Aligned._First, _New_fixed) valid
672+
// [_New_fixed, _Aligned._End) poison
673+
// [_Aligned._End, _End) valid
674+
_CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed);
675+
}
673676
}
674677

675678
#define _ASAN_STRING_REMOVE(_Str) (_Str)._Remove_annotation()

tests/std/tests/GH_002030_asan_annotate_string/test.cpp

+59-10
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,29 @@ STATIC_ASSERT(_Container_allocation_minimum_asan_alignment<
289289
basic_string<wchar_t, char_traits<wchar_t>, implicit_allocator<wchar_t>>>
290290
== 2);
291291

292+
// Simple allocator that opts out of ASan annotations (via `_Disable_ASan_container_annotations_for_allocator`)
293+
template <class T, class Pocma = true_type, class Stateless = true_type>
294+
struct implicit_allocator_no_asan_annotations : implicit_allocator<T, Pocma, Stateless> {
295+
implicit_allocator_no_asan_annotations() = default;
296+
template <class U>
297+
constexpr implicit_allocator_no_asan_annotations(
298+
const implicit_allocator_no_asan_annotations<U, Pocma, Stateless>&) noexcept {}
299+
300+
T* allocate(size_t n) {
301+
T* mem = new T[n + 1];
302+
return mem + 1;
303+
}
304+
305+
void deallocate(T* p, size_t) noexcept {
306+
delete[] (p - 1);
307+
}
308+
};
309+
310+
template <class T, class Pocma, class Stateless>
311+
constexpr bool
312+
_Disable_ASan_container_annotations_for_allocator<implicit_allocator_no_asan_annotations<T, Pocma, Stateless>> =
313+
true;
314+
292315
template <class Alloc>
293316
void test_construction() {
294317
using CharType = typename Alloc::value_type;
@@ -1855,6 +1878,28 @@ void run_tests() {
18551878
#endif // ^^^ no workaround ^^^
18561879
}
18571880

1881+
// Test that writing to uninitialized memory in a string triggers an ASan container-overflow error. (See GH-5251.)
1882+
template <class CharType, class Alloc = allocator<CharType>>
1883+
void run_asan_container_overflow_death_test() {
1884+
1885+
// We'll give the string capacity 100 (all uninitialized memory, except for the null terminator).
1886+
basic_string<CharType, char_traits<CharType>, Alloc> myString;
1887+
myString.reserve(100);
1888+
1889+
// Write to the element at index 50 to trigger an ASan container-overflow check.
1890+
CharType* myData = &myString[0];
1891+
myData[50] = CharType{'A'};
1892+
}
1893+
1894+
// Test that ASan `container-overflow` checks can be disabled for a custom allocator.
1895+
template <class CharType>
1896+
void run_asan_annotations_disablement_test() {
1897+
1898+
// ASan annotations are disabled for the `implicit_allocator_no_asan_annotations` allocator,
1899+
// which should make the container-overflow 'death test' pass.
1900+
run_asan_container_overflow_death_test<CharType, implicit_allocator_no_asan_annotations<CharType>>();
1901+
}
1902+
18581903
template <class CharType, template <class, class, class> class Alloc>
18591904
void run_custom_allocator_matrix() {
18601905
run_tests<Alloc<CharType, true_type, true_type>>();
@@ -1869,6 +1914,11 @@ void run_allocator_matrix() {
18691914
run_custom_allocator_matrix<CharType, aligned_allocator>();
18701915
run_custom_allocator_matrix<CharType, explicit_allocator>();
18711916
run_custom_allocator_matrix<CharType, implicit_allocator>();
1917+
1918+
// To test ASan annotation disablement, we use an ad-hoc allocator type to avoid disrupting other
1919+
// tests that depend on annotations being enabled. Therefore, unlike the prior tests,
1920+
// this test is not parameterized by the allocator type.
1921+
run_asan_annotations_disablement_test<CharType>();
18721922
}
18731923

18741924
void test_DevCom_10116361() {
@@ -1919,15 +1969,6 @@ void test_gh_3955() {
19191969
assert(s == t);
19201970
}
19211971

1922-
void test_gh_5251() {
1923-
// GH-5251 <string>: ASan annotations do not prevent writing to allocated
1924-
// but uninitialized basic_string memory
1925-
string myString;
1926-
myString.reserve(100);
1927-
char* myData = &myString[0];
1928-
myData[50] = 'A'; // ASan should fire!
1929-
}
1930-
19311972
int main(int argc, char* argv[]) {
19321973
std_testing::death_test_executive exec([] {
19331974
run_allocator_matrix<char>();
@@ -1944,7 +1985,15 @@ int main(int argc, char* argv[]) {
19441985
test_gh_3955();
19451986
});
19461987
#ifdef __SANITIZE_ADDRESS__
1947-
exec.add_death_tests({test_gh_5251});
1988+
exec.add_death_tests({
1989+
run_asan_container_overflow_death_test<char>,
1990+
#ifdef __cpp_char8_t
1991+
run_asan_container_overflow_death_test<char8_t>,
1992+
#endif // __cpp_char8_t
1993+
run_asan_container_overflow_death_test<char16_t>,
1994+
run_asan_container_overflow_death_test<char32_t>,
1995+
run_asan_container_overflow_death_test<wchar_t>,
1996+
});
19481997
#endif // ASan instrumentation enabled
19491998
return exec.run(argc, argv);
19501999
}

0 commit comments

Comments
 (0)