Поделиться через


Ошибка: 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

Снимок экрана: отладчик, отображающий ошибку переполнения контейнера в примере 1.

Пользовательские распределители и переполнение контейнеров

Проверка переполнения контейнера 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