AddressSanitizer(ASan)は、ネイティブ コードのメモリバグを検出する、高速なコンパイラベースのツールです。
ASan の検出対象は次のとおりです。
- スタックとヒープのバッファ オーバーフロー / アンダーフロー
- 解放後のヒープ使用
- スコープ外のスタック使用
- 二重解放 / ワイルド解放
ASan は、32 ビット ARM と 64 ビット ARM の両方に加え、x86 と x86-64 で動作します。ASan の CPU オーバーヘッドは約 2 倍、コードサイズ オーバーヘッドは 50% から 2 倍です。また、メモリ オーバーヘッドは大きくなります(割り当てパターンにもよりますが、2 倍程度です)。
Android 10 と AArch64 の AOSP main ブランチは、Hardware Assisted AddressSanitizer(HWASan)をサポートしています。RAM オーバーヘッドが少なく、検出されるバグの範囲が広い、同様のツールです。HWASan は ASan で検出されるバグに加え、返却後のスタック使用を検出します。
HWASan では、CPU とコードサイズのオーバーヘッドは同じですが、RAM オーバーヘッドははるかに小さくなります(15%)。HWASan は非決定論的です。取り得るタグの値は 256 個しかないため、バグを見逃す可能性は 0.4% です。HWASan には、オーバーフローを検出する ASan のサイズ制限レッドゾーンと、解放後の使用を検出する容量制限検疫機能がないため、オーバーフローがどれくらい大きいか、またはメモリがどれくらい前に割り当て解除されたかは、HWASan にとって問題ではありません。これにより、HWASan の方が ASan よりも優れています。詳細については、HWASan の設計または Android での HWASan の使用方法をご覧ください。
ASan はヒープ オーバーフローに加えてスタック / グローバル オーバーフローを検出し、メモリのオーバーヘッドを最小限に抑えつつ高速で動作します。
このドキュメントでは、ASan を使用して Android の一部または全部をビルドして実行する方法について説明します。ASan を使用して SDK / NDK アプリをビルドする場合は、代わりに Address Sanitizer をご覧ください。
個々の実行ファイルを ASan でサニタイズする
LOCAL_SANITIZE:=address
または sanitize: { address: true }
を実行ファイルのビルドルールに追加します。コードで既存の例を検索する、または他の利用可能なサニタイザーを探すことができます。
バグが検出されると、ASan は標準出力と logcat
の両方に詳細なレポートを出力し、プロセスをクラッシュさせます。
共有ライブラリを ASan でサニタイズする
ASan の機能の仕方により、ASan でビルドされたライブラリは、ASan でビルドされた実行ファイルでのみ使用できます。
すべてが ASan でビルドされているわけではない、複数の実行ファイルで使用されている共有ライブラリをサニタイズするには、ライブラリのコピーが 2 つ必要です。おすすめの方法は、問題のモジュールの Android.mk
に次の行を追加することです。
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
これにより、ライブラリは /system/lib
ではなく /system/lib/asan
に置かれます。次に、下記を指定して実行ファイルを実行します。
LD_LIBRARY_PATH=/system/lib/asan
システム デーモンの場合は、/init.rc
または /init.$device$.rc
の適切なセクションに次の行を追加します。
setenv LD_LIBRARY_PATH /system/lib/asan
/proc/$PID/maps
を読み取ることで、プロセスが /system/lib/asan
のライブラリ(存在する場合)を使用していることを確認します。そうでない場合は、SELinux を無効にする必要があります。
adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID # if it is a system service, or may be adb shell stop; adb shell start.
スタック トレースの向上
ASan は、フレーム ポインタに基づく高速なアンワインダーを使用して、プログラム内のすべてのメモリ割り当てイベントと割り当て解除イベントのスタック トレースを記録します。Android の大部分はフレーム ポインタなしでビルドされています。そのため、意味のあるフレームを 1 つか 2 つしか得られないことがよくあります。これを修正するには、ASan(推奨)または下記を使用してライブラリを再ビルドします。
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
またはプロセス環境で ASAN_OPTIONS=fast_unwind_on_malloc=0
を設定します。後者は、負荷によっては CPU 使用率が非常に高くなります。
シンボル化
最初、ASan レポートには、バイナリと共有ライブラリのオフセットへの参照が含まれています。ソースファイルと行の情報を取得する方法は 2 つあります。
llvm-symbolizer
バイナリが/system/bin
に存在することを確認します。llvm-symbolizer
はthird_party/llvm/tools/llvm-symbolizer
のソースからビルドされています。external/compiler-rt/lib/asan/scripts/symbolize.py
スクリプトを介してレポートをフィルタします。
2 つ目の方法では、ホスト上でシンボル化されたライブラリを利用できるため、より多くのデータ(つまり file:line
の場所)を提供できます。
アプリでの ASan
ASan は Java コードを調べることはできませんが、JNI ライブラリ内のバグは検出できます。そのためには、実行ファイルを ASan でビルドする必要があります(この場合は /system/bin/app_process(32|64)
)。これにより、デバイス上のすべてのアプリで同時に ASan が有効になり、負荷が大きくなりますが、2 GB の RAM を搭載したデバイスであれば処理できるはずです。
frameworks/base/cmds/app_process
の app_process
ビルドルールに LOCAL_SANITIZE:=address
を追加します。ここでは、同じファイル内の app_process__asan
ターゲットは無視します(これを読んでいる時点でまだ存在していた場合)。
適切な system/core/rootdir/init.zygote(32|64).rc
ファイルの service zygote
セクションを編集して、class main
を含むインデントされた行のブロックと、同じ量でインデントされた行のブロックに次の行を追加します。
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib setenv ASAN_OPTIONS allow_user_segv_handler=true
ビルド、adb sync、fastboot フラッシュ ブート、再起動します。
wrap プロパティを使用する
前のセクションのアプローチでは、システム内のすべてのアプリに(実際は Zygote プロセスのすべての子孫に)ASan を適用しました。ASan でアプリを 1 つだけ(または複数)実行することが可能であり、メモリ オーバーヘッドと引き換えにアプリの起動が遅くなります。
これを行うには、wrap.
プロパティを指定してアプリを起動します。次の例では、ASan で Gmail アプリを実行しています。
adb root
adb shell setenforce 0 # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"
このコンテキストでは、asanwrapper
が /system/bin/app_process
を /system/bin/asan/app_process
に書き換えます(これは ASan でビルドされています)。また、動的ライブラリ検索パスの先頭に /system/lib/asan
を追加します。このようにして、/system/lib/asan
の ASan インストゥルメント化ライブラリは、asanwrapper
で実行するとき、/system/lib
の通常のライブラリよりも優先されます。
バグが見つかるとアプリがクラッシュし、レポートがログに出力されます。
SANITIZE_TARGET
Android 7.0 以降では、Android プラットフォーム全体を ASan で一度にビルドできます(Android 9 より後のリリースをビルドする場合は、HWASan をおすすめします)。
同じビルドツリーで次のコマンドを実行します。
make -j42
SANITIZE_TARGET=address make -j42
このモードでは、userdata.img
に追加ライブラリが含まれており、デバイスにもフラッシュする必要があります。次のコマンドラインを使用します。
fastboot flash userdata && fastboot flashall
これにより、/system/lib
の normal(最初の make 呼び出し)、/data/asan/lib
の ASan-instrumented(2 番目の make 呼び出し)という、2 セットの共有ライブラリがビルドされます。2 番目のビルドの実行ファイルは、1 番目のビルドの実行ファイルを上書きします。ASan インストゥルメント化実行ファイルは、PT_INTERP
の /system/bin/linker_asan
を使用して、/system/lib
の前に /data/asan/lib
を含む別のライブラリ検索パスを取得します。
ビルドシステムは、$SANITIZE_TARGET
値が変更されると、中間オブジェクト ディレクトリを上書き削除します。これにより、/system/lib
にインストールされているバイナリを保持しつつ、すべてのターゲットを強制的に再ビルドします。
一部のターゲットは ASan でビルドできません。
- 静的にリンクされた実行ファイル
LOCAL_CLANG:=false
ターゲットSANITIZE_TARGET=address
の ASan でないLOCAL_SANITIZE:=false
このような実行ファイルは SANITIZE_TARGET
ビルドではスキップされ、最初の make 呼び出しのバージョンは /system/bin
に残ります。
このようなライブラリは ASan なしでビルドされています。依存する静的ライブラリからの ASan コードをいくつか含めることができます。