メインコンテンツへスキップ
  1. ブログ/

LinuxでASanを使ってメモリ破壊脆弱性を検出する方法

·
Linux セキュリティ GCC C言語
目次

LinuxでASanを使ってメモリ破壊脆弱性を検出する方法
LinuxでASanを使ってメモリ破壊脆弱性を検出する方法

はじめに
#

サーバー上で利用される多くのミドルウェアやライブラリは、C言語やC++言語などで実装されています。

これらの言語は高い性能を発揮できる一方で、メモリ管理を誤ると深刻な不具合につながることがあります。

ヒープバッファオーバーフローや解放済みメモリへのアクセス(Use After Free)といったメモリ破壊脆弱性は、現在でもミドルウェアやライブラリ、OSカーネルなどで継続的に発見されています。

一方で、こうしたメモリ関連の不具合は、通常のログや監視だけでは原因を特定しづらく、通常の実行では表面化しない場合もあります。

このような問題を早期に検出し、原因箇所を特定するための手段として、AddressSanitizer(ASan) が有効です。

本記事では、ASanを利用し、メモリに関する脆弱性を検出する方法について解説します。

注意

本記事で紹介する内容は、学習および検証目的でのみ利用してください。

脆弱性の悪用や第三者への攻撃行為は行わないでください。

また、ASan はデバッグ用途の機能であるため、実行時のオーバーヘッドが増加します。

そのため、本番環境での利用は推奨されません。

ASanとは
#

ASan(AddressSanitizer)は、C/C++ プログラムのメモリ関連のバグを検出するためのツールです。

GCC や Clang などのコンパイラに組み込まれた サニタイザー(Sanitizer)機能の一つとなります。

単独でインストールし起動する検査ツールではなく、コンパイル時に専用のオプションを指定して有効化します。

ASan を有効化したプログラムを実行することで、不正なメモリアクセスを検出し、原因箇所の特定に役立つエラーレポートを出力することができます。

検出できる主な問題
#

ASan で検出できる代表的な問題は以下のとおりです。

  • Heap Buffer Overflow(ヒープ領域の境界外アクセス)
  • Stack Buffer Overflow(スタック領域の境界外アクセス)
  • Use After Free(解放済みメモリへのアクセス)
  • Use After Return(関数の終了後に無効となったスタック領域へのアクセス)

LinuxでASanを有効化する
#

本記事では、検証環境として AlmaLinux 10 を利用しています。

他の Linux ディストリビューションでも基本的な手順は同様ですが、パッケージ名やインストール方法が異なる場合があります。

GCCのインストール
#

初めに以下のコマンドを実行し、GCC が環境にインストールされているか確認します。

gcc --version

GCC がインストールされている場合は、以下のようにバージョン情報が表示されます。

gcc (GCC) 14.3.1 20251022 (Red Hat 14.3.1-4)
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

インストールされていない場合は以下のようなメッセージが表示されます。

-bash: gcc: command not found

その場合は、以下のコマンドで GCC をインストールします。

dnf install gcc

libasanのインストール
#

ASan を利用するには、GCC に加えて libasan が必要となります。

以下のコマンドを実行し、libasan をインストールします。

dnf install libasan

libasan がインストールされていない状態でコンパイルすると、以下のようなエラーが発生することがあります。

/usr/bin/ld: cannot find /usr/lib64/libasan.so.8.0.0: No such file or directory
collect2: error: ld returned 1 exit status

ASanの基本的な使い方
#

ASan を利用するには、プログラムのコンパイル時に専用のオプションを指定する必要があります。

基本的には -fsanitize=address オプションを付与してコンパイルします。

gcc -fsanitize=address -o <出力ファイル名> <ソースファイル名>

また、以下のように -g オプションを付与してデバッグ情報を含めることで、エラー発生時にソースコードの行番号を確認できるようになります。

gcc -fsanitize=address -g -o <出力ファイル名> <ソースファイル名>

ヒープバッファオーバーフローを検出する
#

ヒープバッファオーバーフロー(Heap Buffer Overflow)は、プログラムが動的に確保したメモリ領域に対して、確保したサイズを超えてアクセスしてしまう脆弱性です。

確保した領域の外側へアクセスすることで、情報漏えいやメモリ破壊などの問題を引き起こす可能性があります。

では実際に、ヒープバッファオーバーフローを発生させるサンプルコードを作成し、ASan で検出してみます。

サンプルコードの作成
#

以下のコマンドを実行し検証に利用するサンプルコードを作成します。

vi heap_overflow.c

作成したファイルに以下の内容を記述してください。

#include <stdlib.h>

int main(void) {
    int *numbers = malloc(3 * sizeof(int));

    numbers[0] = 1;
    numbers[1] = 2;
    numbers[2] = 3;
    numbers[3] = 4; // 範囲外への書き込み

    free(numbers);
    return 0;
}

このコードでは、int 型のメモリ領域を 3 個分確保しています。

しかし、numbers[0] から numbers[3] まで、合計 4 個分の値を書き込んでいます。

その結果、4 個目の numbers[3] が確保した範囲外への書き込みになります。

コンパイルの実行
#

次に先ほど作成した heap_overflow.c を、ASan を有効にしてコンパイルします。

以下のコマンドを実行してください。

gcc -fsanitize=address -g -o heap_overflow heap_overflow.c

コンパイルが成功すると、カレントディレクトリに heap_overflow という実行ファイルが作成されます。

$ ls
heap_overflow  heap_overflow.c

以下のコマンドにて実行権限を付与してください。

chmod +x heap_overflow

実行と検知
#

先ほどコンパイルして作成した heap_overflow を実行します。

./heap_overflow

実行すると、以下のように ASan のエラーレポートが出力されます。

==45419==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50200000001c at pc 0x000000401286 bp 0x7ffe616e3600 sp 0x7ffe616e35f0
WRITE of size 4 at 0x50200000001c thread T0
    #0 0x401285 in main /root/test/heap_overflow.c:9
    #1 0x7f3634e505c7 in __libc_start_call_main (/lib64/libc.so.6+0x2a5c7) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #2 0x7f3634e5068a in __libc_start_main_alias_2 (/lib64/libc.so.6+0x2a68a) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #3 0x4010a4 in _start (/root/test/heap_overflow+0x4010a4) (BuildId: 832f947bf33412eb2b6c2c205a347e63f6a0e090)

0x50200000001c is located 0 bytes after 12-byte region [0x502000000010,0x50200000001c)
allocated by thread T0 here:
    #0 0x7f36350fc667 in malloc (/lib64/libasan.so.8+0xfc667) (BuildId: 6056c16ccd464e5ca11b0b9dc404d3ada4eaea8a)
    #1 0x401177 in main /root/test/heap_overflow.c:4
    #2 0x7f3634e505c7 in __libc_start_call_main (/lib64/libc.so.6+0x2a5c7) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #3 0x7f3634e5068a in __libc_start_main_alias_2 (/lib64/libc.so.6+0x2a68a) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #4 0x4010a4 in _start (/root/test/heap_overflow+0x4010a4) (BuildId: 832f947bf33412eb2b6c2c205a347e63f6a0e090)

SUMMARY: AddressSanitizer: heap-buffer-overflow /root/test/heap_overflow.c:9 in main
Shadow bytes around the buggy address:
  0x501ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x502000000000: fa fa 00[04]fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==45419==ABORTING

以下は ASan が ヒープバッファオーバーフロー を検出したことを示しています。

ERROR: AddressSanitizer: heap-buffer-overflow

また、SUMMARY には検出された脆弱性の種類と、問題が発生した場所が表示されています。

SUMMARY: AddressSanitizer: heap-buffer-overflow /root/test/heap_overflow.c:9 in main

Shadow bytes は、ASan がメモリ領域の状態を表すために出力している情報です。

00 はアクセス可能な領域、fa は ASan が検出用に配置したアクセス禁止領域を示しています。

今回の例では、以下の行が該当します。

=>0x502000000000: fa fa 00[04]fa fa fa fa fa fa fa fa fa fa fa fa

00[04] は、確保された 12 バイト分のメモリ領域を表しています。

その直後の fa はアクセス禁止領域であり、今回のコードではこの領域に書き込んだため、ヒープバッファオーバーフローとして検出されています。

Use After Free を検出する
#

Use After Free は、一度解放したメモリ領域へ再度アクセスしてしまう脆弱性です。

解放済みのメモリ領域は、他の用途で再利用される可能性があります。

そのため、解放後にプログラム側で読み書きを行うと、予期しない動作やプロセスの異常終了、任意コード実行などにつながる可能性があります。

では実際に、Use After Free を発生させるサンプルコードを作成し、ASan で検出してみます。

サンプルコードの作成
#

以下のコマンドを実行し検証に利用するサンプルコードを作成します。

vi use_after_free.c

作成したファイルに以下の内容を記述してください。

#include <stdlib.h>

int main(void) {
    int *numbers = malloc(sizeof(int));

    free(numbers);

    *numbers = 100;

    return 0;
}

このコードでは、int 型のメモリ領域を 1 個分確保し、free() によって解放しています。

その後、解放済みのメモリ領域に対して書き込みを行っています。

この解放済みメモリへの書き込み(アクセス)が、Use After Free に該当します。

コンパイルの実行
#

それでは、先ほど作成した use_after_free.c を、ASan を有効にしてコンパイルします。

以下のコマンドを実行してください。

gcc -fsanitize=address -g -o use_after_free use_after_free.c

コンパイルが成功すると、カレントディレクトリに use_after_free という実行ファイルが作成されます。

$ ls |grep "use_after_free"
use_after_free
use_after_free.c

以下のコマンドにて実行権限を付与してください。

chmod +x use_after_free

実行と検知
#

先ほどコンパイルして作成した use_after_free を実行します。

./use_after_free

実行すると、以下のように ASan のエラーレポートが出力されます。

=================================================================
==45829==ERROR: AddressSanitizer: heap-use-after-free on address 0x502000000010 at pc 0x0000004011bf bp 0x7ffd4bc9d9d0 sp 0x7ffd4bc9d9c0
WRITE of size 4 at 0x502000000010 thread T0
    #0 0x4011be in main /root/test/use_after_free.c:8
    #1 0x7f1908e505c7 in __libc_start_call_main (/lib64/libc.so.6+0x2a5c7) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #2 0x7f1908e5068a in __libc_start_main_alias_2 (/lib64/libc.so.6+0x2a68a) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #3 0x4010a4 in _start (/root/test/use_after_free+0x4010a4) (BuildId: d8a9ec2fbeef2d8dfdf528c04b8d1c9a24274a5d)

0x502000000010 is located 0 bytes inside of 4-byte region [0x502000000010,0x502000000014)
freed by thread T0 here:
    #0 0x7f19090fb1a8 in free.part.0 (/lib64/libasan.so.8+0xfb1a8) (BuildId: 6056c16ccd464e5ca11b0b9dc404d3ada4eaea8a)
    #1 0x401187 in main /root/test/use_after_free.c:6
    #2 0x7f1908e505c7 in __libc_start_call_main (/lib64/libc.so.6+0x2a5c7) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #3 0x7f1908e5068a in __libc_start_main_alias_2 (/lib64/libc.so.6+0x2a68a) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #4 0x4010a4 in _start (/root/test/use_after_free+0x4010a4) (BuildId: d8a9ec2fbeef2d8dfdf528c04b8d1c9a24274a5d)

previously allocated by thread T0 here:
    #0 0x7f19090fc667 in malloc (/lib64/libasan.so.8+0xfc667) (BuildId: 6056c16ccd464e5ca11b0b9dc404d3ada4eaea8a)
    #1 0x401177 in main /root/test/use_after_free.c:4
    #2 0x7f1908e505c7 in __libc_start_call_main (/lib64/libc.so.6+0x2a5c7) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #3 0x7f1908e5068a in __libc_start_main_alias_2 (/lib64/libc.so.6+0x2a68a) (BuildId: 6b26a9e2eb662ac189ef8cc23843ba2433db44d5)
    #4 0x4010a4 in _start (/root/test/use_after_free+0x4010a4) (BuildId: d8a9ec2fbeef2d8dfdf528c04b8d1c9a24274a5d)

SUMMARY: AddressSanitizer: heap-use-after-free /root/test/use_after_free.c:8 in main
Shadow bytes around the buggy address:
  0x501ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x502000000000: fa fa[fd]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==45829==ABORTING

以下の出力から、ASan が Use After Free を検出したことが分かります。

ERROR: AddressSanitizer: heap-use-after-free

SUMMARY には、検出された脆弱性の種類と、問題が発生した場所が表示されています。

SUMMARY: AddressSanitizer: heap-use-after-free /root/test/use_after_free.c:8 in main

Shadow bytes 内の [fd] は解放済みのヒープ領域を示しています。

=>0x502000000000: fa fa[fd]fa fa fa fa fa fa fa fa fa fa fa fa fa

今回の例では、解放済み領域を示す [fd] に対して書き込みが行われたため、Use After Free として検出されています。

脆弱性検証での活用例
#

ASan は学習や開発時のデバッグ用途だけでなく、脆弱性の発見や検証にも広く活用されています。

ASan を活用することで、通常の動作確認だけでは発見が難しいメモリ破壊脆弱性を検出できる場合があります。

以下は、OSS プロジェクトにおいて ASan による検出が報告されている事例となります。

まとめ
#

本記事では、AddressSanitizer(ASan)を利用して、ヒープバッファオーバーフローおよび Use After Free を検出する方法を紹介しました。

ASan を活用することで、メモリ破壊脆弱性の早期発見や原因調査を効率的に行うことができます。

Linuxでの脆弱性調査やOSSの検証を行う際は、ぜひ活用してみてください。

関連記事
#

インフラ技術部では、ASan をはじめとした脆弱性調査や Linux のセキュリティ対策に関する情報を発信しています。

興味がある方は是非こちらもあわせてご覧ください。

【脆弱性確認】RPMファイルを展開してソースコードとパッチを確認する方法

関連記事

auditdでファイルの変更を監視する方法
Linux AlmaLinux セキュリティ Auditd
cyrus-sasl(サイラスサスル)とは
Linux セキュリティ
【超入門】AlmaLinux 10でChrootによるSSHユーザー隔離環境を構築する方法
セキュリティ Linux AlmaLinux Chroot
【初心者向け】RPMのChangelogからセキュリティ修正を確認する方法
Linux RPM セキュリティ AlmaLinux
【簡単実装】firewalldで日本国内IPアドレスのみ許可する方法
Linux Firewalld セキュリティ ネットワーク
【初心者向け】firewalldで独自のサービスを追加する方法
Linux Firewalld セキュリティ