Ошибка: container-overflow
Ошибка санитизатора адресов: переполнение контейнера
В Visual Studio 2022 версии 17.2 и более поздних версиях стандартная библиотека Microsoft Visual C++ (STL) частично просвещена для работы с AddressSanitizer. В следующих типах контейнеров есть заметки для обнаружения проблем с доступом к памяти:
Стандартный тип контейнера | Отключение макросов примечаний | Поддерживается в версии |
---|---|---|
std::vector |
_DISABLE_VECTOR_ANNOTATION |
Visual Studio 2022 17.2 |
std::string |
_DISABLE_STRING_ANNOTATION |
Visual Studio 2022 17.6 |
Существуют проверки, чтобы убедиться, что нарушений единого определения (ODR) нет. Нарушение ODR возникает, когда одна единица перевода аннотирует стандартный тип, например vector
с заметками ASan, но другая единица перевода не выполняется. В этом примере компоновщик может одновременно видеть одно объявление vector<int>::push_back
о том, что имеет заметки с санитизатором адресов, а другое объявление vector<int>::push_back
этого не происходит. Чтобы избежать этой проблемы, каждая статическая библиотека и объект, используемые для связывания двоичного файла, также должна включать заметки ASan. Фактически необходимо создать эти статические библиотеки и объекты с поддержкой AddressSanitizer. Сочетание кода с различными параметрами заметки приводит к ошибке:
my_static.lib(my_code.obj) : error LNK2038: mismatch detected for 'annotate_vector': value '0' doesn't match value '1' in main.obj
Чтобы устранить эту ошибку, отключите заметки во всех проектах, использующих соответствующий макрос, или создайте каждый проект с включенными /fsanitize=address
заметками. (Заметки включены по умолчанию.)
Пример. Доступ к зарезервированной памяти в a std::vector
// Compile with: cl /EHsc /fsanitize=address /Zi
#include <vector>
int main() {
// Create a vector of size 10, but with a capacity of 20.
std::vector<int> v(10);
v.reserve(20);
// In versions prior to 17.2, MSVC ASan does NOT raise an exception here.
// While this is an out-of-bounds write to 'v', MSVC ASan
// ensures the write is within the heap allocation size (20).
// With 17.2 and later, MSVC ASan will raise a 'container-overflow' exception:
// ==18364==ERROR: AddressSanitizer: container-overflow on address 0x1263cb8a0048 at pc 0x7ff6466411ab bp 0x005cf81ef7b0 sp 0x005cf81ef7b8
v[10] = 1;
// Regardless of version, MSVC ASan DOES raise an exception here, as this write
// is out of bounds from the heap allocation.
v[20] = 1;
}
Чтобы создать и проверить этот пример, выполните следующие команды в окне командной строки разработчика Visual Studio 2022 версии 17.2 или более поздней версии:
cl /EHsc example1.cpp /fsanitize=address /Zi
devenv /debugexe example1.exe
Результат ошибки зарезервированного доступа к памяти в std::vector
Пользовательские распределители и переполнение контейнеров
Проверка переполнения контейнера Sanitizer адреса поддерживает неоценочныеstd::allocator
средства. Тем не менее, поскольку AddressSanitizer не знает, соответствует ли пользовательский распределитель требованиям AddressSanitizer, таким как выравнивание выделений по границам 8-байтов или не помещает данные между окончанием выделения и следующей 8-байтовой границой, она не всегда может быть в состоянии проверить правильность доступа к последнему концу выделения.
AddressSanitizer помечает блоки памяти в 8-байтовых блоках. Он не может поместить недоступные байты перед доступными байтами в одном блоке. Допустимо иметь 8 доступных байтов в блоке или 4 байта, а затем 4 недоступных байта. За четырьмя недоступными байтами нельзя следовать 4 доступных байта.
Если конец выделения из настраиваемого распределителя не соответствует концу 8-байтового блока, AddressSanitizer должен предположить, что распределитель делает байты между окончанием выделения и концом блока доступным для распределителя или пользователя для записи. Поэтому он не может пометить байты в последнем блоке 8-байтов как недоступные. В следующем примере vector
, который выделяет память с помощью пользовательского распределителя, "?" ссылается на неинициализированные данные и "-" ссылается на память, которая недоступна.
std::vector<uint8_t, MyCustomAlloc<uint8_t>> v;
v.reserve(20);
v.assign({0, 1, 2, 3});
// the buffer of `v` is as follows:
// | v.data()
// | | v.data() + v.size()
// | | | v.data() + v.capacity()
// [ 0 1 2 3 ? ? ? ? ][ ? ? ? ? ? ? ? ? ][ ? ? ? ? - - - - ]
// chunk 1 chunk 2 chunk 3
В предыдущем примере блок 3 имеет 4 байта памяти, которые, как предполагается, недоступны, так как они падают между окончанием выделения 20 байтов, зарезервированных (v.reserve(20)
) и концом третьей логической группировки 8 байт (помните, что АдресSanitizer помечает блоки памяти в 8-байтовых блоках).
В идеале мы помечаем теневую память, которая санитизатор адресов выделяется для каждого 8-байтового блока памяти для отслеживания допустимости байтов в блоке 8-байтов и недопустимых (и почему), таких как v.data() + [0, v.size())
доступные и v.data() + [v.size(), v.capacity())
недоступны. Обратите внимание, что использование нотации интервала здесь: "[" означает инклюзивное и ")" означает монопольное. Если пользователь использует пользовательский распределитель, мы не знаем, доступна ли память после v.data() + v.capacity()
этого. Мы должны предположить, что это так. Мы хотели бы пометить эти байты как недоступные в теневой памяти, но мы должны пометить их как доступные, чтобы получить доступ к этим байтам после выделения.
std::allocator
использует статическую переменную-член, _Minimum_asan_allocation_alignment
чтобы сообщить vector
и string
что они могут доверять распределителю, чтобы не помещать данные сразу после выделения. Это гарантирует, что распределитель не будет использовать память между окончанием выделения и концом блока. Таким образом, часть блока может быть помечена недоступной с помощью санитизатора адресов для перехвата перерасходов.
Если вы хотите, чтобы реализация доверяла тому, что пользовательский распределитель обрабатывает память между окончанием выделения и концом блока, чтобы она помечала, что память недоступна и перехватывает перерасходы, установите _Minimum_asan_allocation_alignment
фактическое минимальное выравнивание. Для правильной работы AddressSanitizer выравнивание должно быть не менее 8.
См. также
Обзор AddressSanitizer
Известные проблемы AddressSanitizer
Справочник по сборке и языку AddressSanitizer
Справочник по среде выполнения AddressSanitizer
Теневой байт AddressSanitizer
Облачное или распределенное тестирование AddressSanitizer
Интеграция отладчика AddressSanitizer
Примеры ошибок AddressSanitizer