コンテンツにスキップ

最終更新:2026-10-12

AddressSanitizer メモリ検出

CとC++は非常に安全でなくエラーを起こしやすいプログラミング言語です。Address SanitizerはGoogleが開発した、解放後使用(use-after-free)やメモリリークなどのメモリアクセスエラーを検出するためのツールです。GCCバージョン>= 4.8に統合されており、CとC++のコードに使用できます。Address Sanitizerはメモリ割り当てを追跡するためにランタイムインストルメンテーションを使用します。つまり、その機能をフルに活用するには、AddressSanitizerを使ってコードをビルドする必要があります。

本記事では、上記の8つのシナリオに沿って、メモリ検出ツール**AddressSanitizer**を紹介します。

1.Use after free

以下のコードを例にとります。まず、newキーワードによって整数1つのメモリ領域を割り当て、そのアドレスをポインタptrに代入します。その後、deleteキーワードを使ってこのメモリを解放します。しかし、解放後もプログラムはポインタptrを通じて、すでに解放されたメモリへのアクセスを試み、そのメモリ位置に値10を代入しています。これが解放後使用エラーです。

#include <iostream>

int main() {
  int* ptr = new int;
  delete ptr;
  *ptr = 10;  // Use after free
  return 0;
}

コンパイル時に次のオプションを追加します。

-fsanitize=address

生成されたbinファイルを実行すると、次の結果が得られます。

heap use after free

2.heap buffer overflow

まず、newキーワードによってヒープ上に整数2つの配列を割り当て、その配列の先頭アドレスをポインタarrに代入します。その後、プログラムは値10を配列の3番目の位置(インデックス2)に格納しようとします。これは配列の有効範囲を超えており、ヒープバッファオーバーフローエラーにつながります。

#include <iostream>

int main() {
  int* arr = new int[2];
  arr[2] = 10;  // Heap buffer overflow
  delete[] arr;
  return 0;
}

上と同じ使い方です:

g++-13 heap_buffer_overflow.cc  -Wall -g -fsanitize=address 

その後:

./a.out

次の検出結果が得られます。

heap_buffer_overflow

3.stack buffer overflow

vulnerableFunction関数の中では、8文字のchar配列bufferが定義されています。その後、std::cinを使って標準入力からデータを読み取りbufferに格納しますが、入力サイズの制限がありません。入力された文字数がbufferのサイズを超えると、スタックバッファオーバーフローエラーが発生します。

void vulnerableFunction() {
  char buffer[8];
  std::cin >> buffer;  // Stack buffer overflow
  std::cout << "You entered: " << buffer << std::endl;
}

使い方は上と同じです。

stack_buffer_overflow

4.global buffer overflow

グローバルバッファ(buffer配列)はプログラムのグローバル領域で定義されており、入力サイズに対する十分な制限がありません。入力された文字数がバッファのサイズを超えると、グローバルバッファオーバーフローエラーが発生します。スタックバッファオーバーフローと同様に、グローバルバッファオーバーフローはプログラムの不安定化やセキュリティ問題を引き起こす可能性があります。

#include <iostream>

char buffer[8];

int main() {
    std::cin >> buffer; // 全局缓冲区溢出错误
    std::cout << "You entered: " << buffer << std::endl;
    return 0;
}

使い方は上と同じです。次が得られます:

global_buffer_overflow

5.use after return

関数fの中では、ローカル変数iのアドレスが返されていますが、main関数の中で、返されたポインタを通じて、すでに破棄されたローカル変数にアクセスしようとしています。このようなシナリオでは、binファイルを検出し、ASAN_OPTIONS=detect_stack_use_after_return=1を設定します。

g++-13 stack_use_after_return.cc -Wall -g -fsanitize=address
ASAN_OPTIONS=detect_stack_use_after_return=1 ./a.out

コード:

#include <iostream>

int *f() {
  int i = 42;
  int *p = &i;
  return p;
}

int main() { return *f(); }

すると、次の結果が得られます:

stack_use_after_return

6.use after scope

ローカル変数xのアドレスをポインタpに代入します。しかし、この内部スコープを抜けた後、ポインタpを通じて、すでに範囲外となったローカル変数xにアクセスしようとしており、スコープ外使用エラーが発生します。これも未定義動作です。

#include <iostream>
int main() {
  int* p = 0;
  {
    int x = 0;
    p = &x;
  }
  *p = 5;
  return 0;
}

コンパイルと実行:

g++-13 stack_use_after_scope.cc -Wall -g -fsanitize=address
ASAN_OPTIONS=detect_stack_use_after_scope=0  ./a.out 

実際に試したところ、clang++では検出できますが、g++では検出できません。

stack_use_after_scope

7.Initialization order bugs

2つの異なるソースファイルの中に、グローバル変数aの初期化が存在しますが、初期化の順序は不定です。init_order_b.ccファイルの中では、変数aの値を使用しようとしていますが、初期化順序の不整合により、未定義の動作が引き起こされる可能性があります。

コードは次のとおりです:

// init_order_a.cc
int duplicate(int n) { return n * 2; }
auto a = duplicate(2);

// init_order_b.cc
#include <iostream>

extern int a;
auto b = a;  

int main() {
  std::cout << b << std::endl;
  return 0;
}

このようなコードを書いたとき、その結果は不定であり、初期化の順序によって4になることも0になることもあります。

例えば:

clang++ init_order_a.cc init_order_b.cc  -Wall -g -fsanitize=address

この順序なら出力は4ですが、先にbをコンパイルし、後からaをコンパイルすると0が得られます。

clang++ init_order_b.cc init_order_a.cc  -Wall -g -fsanitize=address

このような問題が発生したとき、AddressSanitizerを使って初期化順序を検出できます。

ASAN_OPTIONS=check_initialization_order=true ./a.out 

init_order

8.memory leak

newキーワードによって整数1つのメモリを動的に割り当てましたが、プログラムが終了する前にdeleteキーワードでこのメモリを解放していないため、メモリリークが発生します。

#include <iostream>

int main() {
  int* ptr = new int;
  // Missing delete ptr; // Memory leak
  return 0;
}

ASAN_OPTIONS=detect_leaks=1を設定します。

ASAN_OPTIONS=detect_leaks=1 ./a.out

memory_leak

関連トピック

コメント