AddressSanitizer のシャドウ バイト

シャドウ バイトの概念と、/fsanitize=address のランタイム実装でどのように使用できるのかについて簡単に説明します。 詳細については、潜在的な論文AddressSanitizer アルゴリズムに関する記事を参照してください。

主な概念

アプリケーションの仮想アドレス空間内の 8 バイトごとに、1 つのシャドウ バイトを使用して記述できます。

1 つのシャドウ バイトは、現在アクセスできるバイト数を次のように記述します。

  • 0 は、すべて 8 バイトを意味します
  • 1 から 7 は、1 から 7 バイトを意味します
  • 負の数値は、診断のレポートに使用するランタイムのコンテキストをエンコードします。

シャドウ バイトの凡例

すべての負の数値が定義されている場合は、次のシャドウ バイト凡例を考慮してください。

Screenshot of the AddressSanitizer shadow-byte legend.

マッピング - アドレス空間を記述する

"0-mod-8" アラインされたアプリケーションの仮想アドレス空間内の 8 バイトごとに、仮想アドレス空間内のそのスロットを記述するシャドウ バイトにマップできます。 このマッピングは、単純なシフトと追加で実現できます。

x86 の場合:

char shadow_byte_value = *((Your_Address >> 3) + 0x30000000)

x64 の場合:

char shadow_byte_value = *((Your_Address >> 3) + _asan_runtime_assigned_offset)

コード生成: テスト

コンパイラによって生成されたコード、静的データ、またはランタイムによって、特定のシャドウ バイトが書き込まれる方法を検討します。 この擬似コードは、読み込みまたは格納の前にチェックを生成する方法を示しています。

ShadowAddr = (Addr >> 3) + Offset;
if (*ShadowAddr != 0) {
    ReportAndCrash(Addr);
}

幅が 8 バイト未満のメモリ参照をインストルメント化する場合、インストルメンテーションはやや複雑になります。 シャドウ値が正の場合 (つまり、8 バイトワードの最初の k バイトだけがアクセスできる)、アドレスの最後の 3 ビットを k と比較する必要があります。

ShadowAddr = (Addr >> 3) + Offset;
k = *ShadowAddr;
if (k != 0 && ((Addr & 7) + AccessSize > k)) {
    ReportAndCrash(Addr);
}

ランタイムとコンパイラによって生成されたコードは、どちらもシャドウ バイトを書き込みます。 これらのシャドウ バイトは、スコープの終了またはストレージの解放時にアクセスを許可または取り消します。 上記のチェックは、アプリケーションのアドレス空間内の 8 バイトの "スロット" を記述する読み取りシャドウ バイトを、プログラムの実行中の特定の時点で確認します。 これらの明示的に生成されたチェックに加え、ランタイムは CRT 内の多くの関数をインターセプト (または "フック") した後にシャドウ バイトもチェックします。

詳細については、インターセプトされた関数の一覧を参照してください。

シャドウ バイトの設定

コンパイラによって生成されるコードと AddressSanitizer ランタイムの両方で、シャドウ バイトを書き込む可能性があります。 たとえば、コンパイラはシャドウ バイトを設定して、内部スコープで定義されたスタック ローカルへの固定サイズのアクセスを許可できます。 ランタイムは、データ セクションのグローバル変数をシャドウ バイトで囲みます。

関連項目

AddressSanitizer の概要
AddressSanitizer の既知の問題
AddressSanitizer のビルドと言語リファレンス
AddressSanitizer ランタイム リファレンス
AddressSanitizer クラウドまたは分散テスト
AddressSanitizer デバッガーの統合
AddressSanitizer エラーの例