Describe the bug
When using an allocator whose allocations are at least 8-byte aligned (as determined by _Minimum_asan_allocation_alignment or alignment of value type, which includes the default std::allocator), the ASan annotations applied for vector and strings attempt to poison past the end of the allocation (since the allocator should not give out that memory due to the alignment requirement) in order to ensure full coverage. However, this means that when using the poisoning API, we need to adjust the concept of the "last valid element" when we are clearing the annotations in the container or else we leave behind a small amount of poisoning in the shadow map.
Example
std::wstring x = L"a 24 length string repr";
String will request 48 bytes from allocator to store the string, the entire allocation will be available for use.
Shadow for x: 00 00 00 00 00 00
(00 means all 8 bytes are addressable)
x.append(L"o"); // trigger resize
String will need more space so will request 70 bytes to store the string, but only 50 bytes are addressable.
Shadow for x: 00 00 00 00 00 00 02 fc fc
(02 means first 2 bytes are addressable, fc means the address is poisoned due to container poisoning)
Sicne shadow memory only operates in 8-byte chunks, that last fc actually covers space 2 extra bytes than it should (the allocator gave 70, not 72).
When x leaves scope, the extra 2 bytes won't be cleaned up, leaving:
Shadow for x: 00 00 00 00 00 00 00 00 06
(06 means first 6 bytes are addressable)
Command-line test case
X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup>type test.cpp
#include <string>
#include <type_traits>
#include <stdio.h>
#include <vector>
using namespace std;
extern "C" uintptr_t __asan_shadow_memory_dynamic_address;
__declspec(no_sanitize_address) unsigned char *shadow_addr_of(const void * addr) {
return (unsigned char *)(((uintptr_t)addr >> 3) + __asan_shadow_memory_dynamic_address);
}
__declspec(no_sanitize_address) unsigned char shadow_byte_of(const void * addr) {
return *shadow_addr_of(addr);
}
__declspec(no_sanitize_address) void print_shadow_bytes(const void * addr, const size_t string_size) {
for (size_t i = 0; i < string_size; i += 8) {
printf("%02x ", shadow_byte_of(reinterpret_cast<const char *>(addr) + i));
}
printf("\n");
}
template <typename T, size_t Size, size_t Alignment>
class asan_unaware_arena {
// In practice, custom memory pools should also be annotated for ASan.
// For simplicity, we aren't doing that so we can directly see the
// effects from only the container poisoning.
public:
asan_unaware_arena() noexcept {
fprintf(stderr, "Creating arena at %p with shadow at %p\n", _alloc_buffer, shadow_addr_of(_alloc_buffer));
}
~asan_unaware_arena() noexcept {
fprintf(stderr, "Shadow during destruction (should be all 00):\t");
print_shadow();
// Shadow should be cleared. If it isn't, this memset will trigger an ASan container-overflow error.
// This will likely appear as unknown-error since partial poisoning relies on nearby
// shadow bytes to determine error type.
memset(_alloc_buffer, 0, Size);
}
asan_unaware_arena(const asan_unaware_arena&) = delete;
asan_unaware_arena& operator=(const asan_unaware_arena&) = delete;
asan_unaware_arena(asan_unaware_arena&&) = delete;
asan_unaware_arena& operator=(asan_unaware_arena&&) = delete;
T* allocate(size_t n) {
if (_next_alloc + n * sizeof(T) > _alloc_buffer + Size) {
throw bad_alloc();
}
T* result = reinterpret_cast<T*>(_next_alloc);
_next_alloc += n * sizeof(T);
_next_alloc = reinterpret_cast<char*>((reinterpret_cast<uintptr_t>(_next_alloc) + (Alignment - 1)) & ~(Alignment - 1)); // align the next allocation pointer.
fprintf(stderr, "Allocated %p -> %p (%zu bytes) from arena, %p -> %p (%zu bytes) in shadow\n",
result, _next_alloc, n * sizeof(T),
shadow_addr_of(result), shadow_addr_of(_next_alloc), shadow_addr_of(_next_alloc) - shadow_addr_of(result));
return result;
}
void print_shadow() const noexcept {
print_shadow_bytes(_alloc_buffer, Size);
}
private:
char alignas(Alignment) _alloc_buffer[Size];
char * _next_alloc = _alloc_buffer;
};
template <typename T, size_t AllocSize, size_t Alignment>
struct arena_reuse_allocator {
using value_type = T;
static constexpr size_t Size = AllocSize;
static constexpr size_t _Minimum_asan_allocation_alignment = Alignment;
template <typename OtherCharType>
struct rebind {
using other = arena_reuse_allocator<OtherCharType, Size, Alignment>;
};
arena_reuse_allocator(asan_unaware_arena<T, AllocSize, Alignment> *a) noexcept : _arena(a) {
}
template <typename OtherCharType>
arena_reuse_allocator(const arena_reuse_allocator<OtherCharType, Size, Alignment>&) noexcept {}
T* allocate(size_t n) {
return _arena->allocate(n);
}
void deallocate(T* p, size_t) noexcept {
}
asan_unaware_arena<T, AllocSize, Alignment> * _arena;
};
template <size_t Alignment>
void test_vector() {
fprintf(stderr, "\nTesting vector with allocator alignment of %zu\n", Alignment);
asan_unaware_arena<wchar_t, 128, Alignment> test_arena;
arena_reuse_allocator<wchar_t, 128, Alignment> alloc(&test_arena);
vector<wchar_t, decltype(alloc)> test_vector(23, L'a', alloc);
fprintf(stderr, "Shadow after vector constructor:\t\t");
test_arena.print_shadow();
test_vector.push_back(L'o'); // trigger resize
fprintf(stderr, "Shadow after vector resize:\t\t\t");
test_arena.print_shadow();
}
template <size_t Alignment>
void test_string() {
fprintf(stderr, "\nTesting string with allocator alignment of %zu\n", Alignment);
asan_unaware_arena<wchar_t, 128, Alignment> test_arena;
arena_reuse_allocator<wchar_t, 128, Alignment> alloc(&test_arena);
basic_string<wchar_t, char_traits<wchar_t>, decltype(alloc)> test_string(L"a 24 length string repr", alloc);
fprintf(stderr, "Shadow after string constructor:\t\t");
test_arena.print_shadow();
test_string.append(L"o"); // add any character to trigger resize
fprintf(stderr, "Shadow after string resize:\t\t\t");
test_arena.print_shadow();
}
int main() {
// Annotations for std::string and std::vector follow different
// paths based off allocation alignment, so test with both.
// Alignment >= 8 is aligned with shadow bytes' 8-byte granularity, so string/vector
// annotations try to maximize coverage by poisoning past the allocation, since the allocator
// should not hand out that memory anyway.
// Alignment < 8 is not aligned with shadow bytes, so string/vector annotations
// are more conservative and only poison the memory that was handed out by the
// allocator, leaving some at the end unpoisoned.
#ifdef TEST_STRING
test_string<2>(); // under poisoned code path
test_string<8>(); // over poisoned code path
#endif
#ifdef TEST_VECTOR
test_vector<2>(); // under poisoned code path
test_vector<8>(); // over poisoned code path
#endif
return 0;
}
X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup>cl /EHsc /MD /fsanitize=address test.cpp /Zi /DTEST_STRING
Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35217 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
test.cpp
Microsoft (R) Incremental Linker Version 14.44.35217.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
/InferAsanLibs
/debug
test.obj
X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup>test.exe
Testing string with allocator alignment of 2
Creating arena at 000000E8860FF5A0 with shadow at 000002AB5999FEB4
Allocated 000000E8860FF5A0 -> 000000E8860FF5D0 (48 bytes) from arena, 000002AB5999FEB4 -> 000002AB5999FEBA (6 bytes) in shadow
Shadow after string constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 000000E8860FF5D0 -> 000000E8860FF616 (70 bytes) from arena, 000002AB5999FEBA -> 000002AB5999FEC2 (8 bytes) in shadow
Shadow after string resize: 00 00 00 00 00 00 00 00 00 00 00 00 02 fc 00 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Testing string with allocator alignment of 8
Creating arena at 000000E8860FF5B0 with shadow at 000002AB5999FEB6
Allocated 000000E8860FF5B0 -> 000000E8860FF5E0 (48 bytes) from arena, 000002AB5999FEB6 -> 000002AB5999FEBC (6 bytes) in shadow
Shadow after string constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 000000E8860FF5E0 -> 000000E8860FF628 (70 bytes) from arena, 000002AB5999FEBC -> 000002AB5999FEC5 (9 bytes) in shadow
Shadow after string resize: 00 00 00 00 00 00 00 00 00 00 00 00 02 fc fc 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00
=================================================================
==58592==ERROR: AddressSanitizer: unknown-crash on address 0x00e8860ff626 at pc 0x7ffbd54c99a4 bp 0x00e8860ff540 sp 0x00e8860fece0
WRITE of size 128 at 0x00e8860ff626 thread T0
#0 0x7ffbd54c99a3 in memset X:\msvc\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors_memintrinsics.inc:92
#1 0x7ff689724731 in asan_unaware_arena<wchar_t, 128, 8>::~asan_unaware_arena<wchar_t, 128, 8>(void) X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:42
#2 0x7ff689723e6d in test_string<8>(void) X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:133
#3 0x7ff6897210fd in main X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:149
#4 0x7ff68972a6df in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
#5 0x7ff68972a6df in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
#6 0x7ffcb194e8d6 (C:\WINDOWS\System32\KERNEL32.DLL+0x18002e8d6)
#7 0x7ffcb286c3fb (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18008c3fb)
Address 0x00e8860ff626 is located in stack of thread T0 at offset 150 in frame
#0 0x7ff689723c6f in test_string<8>(void) X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:120
This frame has 3 object(s):
[32, 168) 'test_arena' <== Memory access at offset 150 partially overflows this variable
[48, 88) 'test_string'
[64, 72) 'alloc' <== Memory access at offset 150 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp, SEH and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: unknown-crash X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:42 in asan_unaware_arena<wchar_t, 128, 8>::~asan_unaware_arena<wchar_t, 128, 8>(void)
Shadow bytes around the buggy address:
0x00e8860ff380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00e8860ff400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00e8860ff480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00e8860ff500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00e8860ff580: 00 00 f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00
=>0x00e8860ff600: 00 00 00 00[06]00 00 f2 f2 f2 f2 00 00 00 00 00
0x00e8860ff680: f2 f2 f2 f2 00 f3 f3 f3 f3 00 00 00 00 00 00 00
0x00e8860ff700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00e8860ff780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00e8860ff800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00e8860ff880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==58592==ABORTING
X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup>cl /EHsc /MD /fsanitize=address test.cpp /Zi /DTEST_VECTOR
Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35217 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
test.cpp
Microsoft (R) Incremental Linker Version 14.44.35217.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
/InferAsanLibs
/debug
test.obj
X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup>test.exe
Testing vector with allocator alignment of 2
Creating arena at 000000DD808FF7A0 with shadow at 00000296BBE9FEF4
Allocated 000000DD808FF7A0 -> 000000DD808FF7CE (46 bytes) from arena, 00000296BBE9FEF4 -> 00000296BBE9FEF9 (5 bytes) in shadow
Shadow after vector constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 000000DD808FF7CE -> 000000DD808FF812 (68 bytes) from arena, 00000296BBE9FEF9 -> 00000296BBE9FF02 (9 bytes) in shadow
Shadow after vector resize: 00 00 00 00 00 00 00 00 00 00 00 06 fc fc 00 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Testing vector with allocator alignment of 8
Creating arena at 000000DD808FF7B0 with shadow at 00000296BBE9FEF6
Allocated 000000DD808FF7B0 -> 000000DD808FF7E0 (46 bytes) from arena, 00000296BBE9FEF6 -> 00000296BBE9FEFC (6 bytes) in shadow
Shadow after vector constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 000000DD808FF7E0 -> 000000DD808FF828 (68 bytes) from arena, 00000296BBE9FEFC -> 00000296BBE9FF05 (9 bytes) in shadow
Shadow after vector resize: 00 00 00 00 00 00 00 00 00 00 00 00 fc fc fc 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 00
=================================================================
==27512==ERROR: AddressSanitizer: unknown-crash on address 0x00dd808ff824 at pc 0x7ffbd54c99a4 bp 0x00dd808ff740 sp 0x00dd808feee0
WRITE of size 128 at 0x00dd808ff824 thread T0
#0 0x7ffbd54c99a3 in memset X:\msvc\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors_memintrinsics.inc:92
#1 0x7ff67f4f5f51 in asan_unaware_arena<wchar_t, 128, 8>::~asan_unaware_arena<wchar_t, 128, 8>(void) X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:42
#2 0x7ff67f4f5072 in test_vector<8>(void) X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:117
#3 0x7ff67f4f10fd in main X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:154
#4 0x7ff67f4fbbff in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
#5 0x7ff67f4fbbff in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
#6 0x7ffcb194e8d6 (C:\WINDOWS\System32\KERNEL32.DLL+0x18002e8d6)
#7 0x7ffcb286c3fb (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18008c3fb)
Address 0x00dd808ff824 is located in stack of thread T0 at offset 148 in frame
#0 0x7ff67f4f4d3f in test_vector<8>(void) X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:104
This frame has 5 object(s):
[32, 168) 'test_arena' <== Memory access at offset 148 partially overflows this variable
[48, 56) 'alloc'
[64, 96) 'test_vector'
[80, 82) 'compiler temporary'
[96, 98) 'compiler temporary' <== Memory access at offset 148 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp, SEH and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: unknown-crash X:\msvc\src\vctools\crt\github\tests\std\tests\GH_999999_annotation_poison_cleanup\test.cpp:42 in asan_unaware_arena<wchar_t, 128, 8>::~asan_unaware_arena<wchar_t, 128, 8>(void)
Shadow bytes around the buggy address:
0x00dd808ff580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00dd808ff600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00dd808ff680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00dd808ff700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00dd808ff780: 00 00 f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00
=>0x00dd808ff800: 00 00 00 00[04]00 00 f2 f2 f2 f2 00 f2 00 00 00
0x00dd808ff880: 00 f2 f2 f2 f2 f8 f2 f8 f3 f3 f3 f3 00 00 00 00
0x00dd808ff900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00dd808ff980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00dd808ffa00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00dd808ffa80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==27512==ABORTING
Expected behavior
Container annotations do not leave behind any poisoning after they are destroyed. Test case should pass without hitting an ASan error.
Testing string with allocator alignment of 2
Creating arena at 00000049C08FFCE0 with shadow at 0000021C46C9FF9C
Allocated 00000049C08FFCE0 -> 00000049C08FFD10 (48 bytes) from arena, 0000021C46C9FF9C -> 0000021C46C9FFA2 (6 bytes) in shadow
Shadow after string constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 00000049C08FFD10 -> 00000049C08FFD56 (70 bytes) from arena, 0000021C46C9FFA2 -> 0000021C46C9FFAA (8 bytes) in shadow
Shadow after string resize: 00 00 00 00 00 00 00 00 00 00 00 00 02 fc 00 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Testing string with allocator alignment of 8
Creating arena at 00000049C08FFCF0 with shadow at 0000021C46C9FF9E
Allocated 00000049C08FFCF0 -> 00000049C08FFD20 (48 bytes) from arena, 0000021C46C9FF9E -> 0000021C46C9FFA4 (6 bytes) in shadow
Shadow after string constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 00000049C08FFD20 -> 00000049C08FFD68 (70 bytes) from arena, 0000021C46C9FFA4 -> 0000021C46C9FFAD (9 bytes) in shadow
Shadow after string resize: 00 00 00 00 00 00 00 00 00 00 00 00 02 fc fc 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Testing vector with allocator alignment of 2
Creating arena at 000000B56CEFF560 with shadow at 00000288BE45FEAC
Allocated 000000B56CEFF560 -> 000000B56CEFF58E (46 bytes) from arena, 00000288BE45FEAC -> 00000288BE45FEB1 (5 bytes) in shadow
Shadow after vector constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 000000B56CEFF58E -> 000000B56CEFF5D2 (68 bytes) from arena, 00000288BE45FEB1 -> 00000288BE45FEBA (9 bytes) in shadow
Shadow after vector resize: 00 00 00 00 00 00 00 00 00 00 00 06 fc fc 00 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Testing vector with allocator alignment of 8
Creating arena at 000000B56CEFF570 with shadow at 00000288BE45FEAE
Allocated 000000B56CEFF570 -> 000000B56CEFF5A0 (46 bytes) from arena, 00000288BE45FEAE -> 00000288BE45FEB4 (6 bytes) in shadow
Shadow after vector constructor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Allocated 000000B56CEFF5A0 -> 000000B56CEFF5E8 (68 bytes) from arena, 00000288BE45FEB4 -> 00000288BE45FEBD (9 bytes) in shadow
Shadow after vector resize: 00 00 00 00 00 00 00 00 00 00 00 00 fc fc fc 00
Shadow during destruction (should be all 00): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Describe the bug
When using an allocator whose allocations are at least 8-byte aligned (as determined by
_Minimum_asan_allocation_alignmentor alignment of value type, which includes the defaultstd::allocator), the ASan annotations applied forvectorandstrings attempt to poison past the end of the allocation (since the allocator should not give out that memory due to the alignment requirement) in order to ensure full coverage. However, this means that when using the poisoning API, we need to adjust the concept of the "last valid element" when we are clearing the annotations in the container or else we leave behind a small amount of poisoning in the shadow map.Example
std::wstring x = L"a 24 length string repr";String will request 48 bytes from allocator to store the string, the entire allocation will be available for use.
Shadow for x: 00 00 00 00 00 00(
00means all 8 bytes are addressable)x.append(L"o");// trigger resizeString will need more space so will request 70 bytes to store the string, but only 50 bytes are addressable.
Shadow for x: 00 00 00 00 00 00 02 fc fc(
02means first 2 bytes are addressable,fcmeans the address is poisoned due to container poisoning)Sicne shadow memory only operates in 8-byte chunks, that last
fcactually covers space 2 extra bytes than it should (the allocator gave 70, not 72).When
xleaves scope, the extra 2 bytes won't be cleaned up, leaving:Shadow for x: 00 00 00 00 00 00 00 00 06(
06means first 6 bytes are addressable)Command-line test case
Expected behavior
Container annotations do not leave behind any poisoning after they are destroyed. Test case should pass without hitting an ASan error.