エラー: 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

1 定義規則 (ODR) 違反がないことを確認するチェックがあります。 ODR 違反は、ある変換単位が ASan 注釈を使用して標準型 ( vector など) に注釈を付け、別の翻訳単位では注釈を付けない場合に発生します。 この例では、リンカーは、アドレスサニタイザー注釈を持つ 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 と注釈を有効にして各プロジェクトをビルドします。 (注釈は既定で有効になっています)。

例: 予約済みメモリにアクセスする 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 以降の Developer コマンド プロンプト ウィンドウで次のコマンドを実行します。

cl /EHsc example1.cpp /fsanitize=address /Zi
devenv /debugexe example1.exe

の予約済みメモリ アクセスのエラー結果 std::vector

例 1 のコンテナー オーバーフロー エラーが表示されているデバッガーのスクリーンショット。

カスタム アロケーターとコンテナー オーバーフロー

アドレスサニタイザー コンテナー オーバーフロー チェックでは、std::allocator 以外のアロケーターがサポートされます。 ただし、AddressSanitizer は、8 バイト境界での割り当てのアラインなど、カスタム アロケーターが AddressSanitizer の要件に準拠しているかどうか、または割り当ての終了と次の 8 バイト境界の間にデータを配置しない場合など、認識されないため、割り当ての後者の端でアクセスが正しいことを常に確認できない場合があります。

AddressSanitizer は、メモリのブロックを 8 バイトのチャンクでマークします。 アクセス可能なバイトの前にアクセスできないバイトを 1 つのチャンクに配置することはできません。 チャンク内に 8 つのアクセス可能なバイト、または 4 つのアクセス可能なバイトの後に 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 には、予約された 20 バイトの割り当ての終了 (v.reserve(20)) と 8 バイトの 3 番目の論理グループの終了の間に収まっているため、アクセスできないと見なされる 4 バイトのメモリがあります (AddressSanitizer はメモリのブロックを 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 エラーの例