AddressSanitizer

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-symbolizerthird_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_processapp_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 コードをいくつか含めることができます。

サポート ドキュメント