Arm v9 では、タグ付きメモリのハードウェア実装である Arm Memory Tagging 拡張機能(MTE)が導入されました。
大まかに言うと、MTE は各メモリ割り当て / 割り当て解除に追加のメタデータをタグ付けし、タグをメモリ位置に割り当てます。タグは、そのメモリ位置を参照するポインタに関連付けることができます。CPU は実行時に、ポインタとメタデータのタグが読み込みと保存のそれぞれで一致するかどうかをチェックします。
Android 12 では、カーネルとユーザー空間のヒープメモリ アロケータによって各割り当てをメタデータで拡張できます。これにより、コードベースにおけるメモリ安全性のバグの最も一般的な原因である、解放後の使用とバッファ オーバーフローのバグを検出できます。
MTE の動作モード
MTE の動作モードは 3 つあります。
- 同期モード(SYNC)
- 非同期モード(ASYNC)
- 非対称モード(ASYMM)
同期モード(SYNC)
このモードは、パフォーマンスよりもバグ検出の正確性を重視して最適化されており、パフォーマンス オーバーヘッドが大きくなっても構わない場合、正確なバグ検出ツールとして使用できます。有効にすると、MTE SYNC はセキュリティ対策として機能します。タグが一致しない場合、プロセッサは実行を直ちに中止し、SIGSEGV
(コード SEGV_MTESERR
)と、メモリアクセスや障害発生アドレスに関する完全な情報を使用してプロセスを終了します。
このモードは、HWASan / KASAN の代わりとしてテスト時に使用するか、ターゲット プロセスが脆弱な攻撃対象領域を表す場合は製品版で使用することをおすすめします。また、ASYNC モードでバグの存在が示された場合は、ランタイム API を使用して実行を SYNC モードに切り替えることで、正確なバグレポートを取得できます。
SYNC モードで実行すると、Android アロケータは、すべての割り当てと割り当て解除のスタック トレースを記録し、それを使用して、メモリエラーの説明(解放後の使用、バッファ オーバーフローなど)と関連するメモリイベントのスタック トレースを含む詳細なエラーレポートを提供します。このようなレポートは、コンテキストに即した情報を提供し、バグのトレースと修正を容易にします。
非同期モード(ASYNC)
このモードは、バグレポートの精度よりもパフォーマンスを重視して最適化されており、オーバーヘッドの小さい、メモリ安全性のバグ検出ツールとして使用できます。
タグが一致しない場合、プロセッサは最も近いカーネル エントリ(syscall や interrupt など)まで実行を継続し、障害発生アドレスやメモリアクセスを記録せずに SIGSEGV
(コード SEGV_MTEAERR
)を使用してプロセスを終了します。
このモードは、メモリ安全性のバグの密度が低いことがわかっている(SYNC モードをテスト時に使用することで実現)、よくテストされたコードベースの製品版で使用することをおすすめします。
非対称モード(ASYMM)
Arm v8.7-A の追加機能である非対称 MTE モードは、メモリ読み取りの同期チェックとメモリ書き込みの非同期チェックを行い、ASYNC モードと同様のパフォーマンスを実現します。ほとんどの場合、このモードは ASYNC モードよりも改善されています。可能であれば ASYNC モードではなくこのモードを使用することをおすすめします。
このため、以下に説明する API はいずれも非対称モードを指定しません。代わりに、非同期がリクエストされたときは常に非対称モードを使用するように OS を構成できます。詳細については、「CPU 固有の優先 MTE レベルの構成」をご覧ください。
ユーザー空間の MTE
以降のセクションでは、システム プロセスとアプリの MTE を有効にする方法について説明します。特定のプロセスで以下のオプションのいずれかが設定されている場合を除き、MTE はデフォルトで無効になっています(MTE が有効なコンポーネントについては後述します)。
ビルドシステムを使用して MTE を有効にする
プロセス全体のプロパティとして、MTE はメインの実行ファイルのビルド時間設定によって制御されます。次のオプションを使用すると、個々の実行ファイルまたはソースツリーのサブディレクトリ全体に対してこの設定を変更できます。この設定は、ライブラリや、実行ファイルでもテストでもないターゲットでは無視されます。
1. 特定のプロジェクトの Android.bp
(例)で MTE を有効にします。
MTE モード | 設定 |
---|---|
非同期 MTE | sanitize: { memtag_heap: true, } |
同期 MTE | sanitize: { memtag_heap: true, diag: { memtag_heap: true, }, } |
または Android.mk:
で有効にします。
MTE モード | 設定 |
---|---|
Asynchronous MTE |
LOCAL_SANITIZE := memtag_heap |
Synchronous MTE |
LOCAL_SANITIZE := memtag_heap LOCAL_SANITIZE_DIAG := memtag_heap |
2. プロダクト変数を使用してソースツリーのサブディレクトリで MTE を有効にします。
MTE モード | 包含リスト | 除外リスト |
---|---|---|
非同期 | PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
同期 | PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS
MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
または
MTE モード | 設定 |
---|---|
非同期 MTE | MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
同期 MTE | MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
または、実行ファイルの除外パスを指定します。
MTE モード | 設定 |
---|---|
非同期 MTE | PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
同期 MTE |
例(PRODUCT_CFI_INCLUDE_PATHS
と同様の使用方法)
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor) PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \ vendor/$(vendor)/projectB
システム プロパティを使用して MTE を有効にする
上記のビルド設定は、次のシステム プロパティを設定することで実行時にオーバーライドできます。
arm64.memtag.process.<basename> = (off|sync|async)
ここで、basename
は実行ファイルのベース名を表しています。
たとえば、非同期 MTE を使用するように /system/bin/ping
や /data/local/tmp/ping
を設定するには、adb shell setprop arm64.memtag.process.ping async
を使用します。
環境変数を使用して MTE を有効にする
ビルド設定をオーバーライドするもう一つの方法は、環境変数 MEMTAG_OPTIONS=(off|sync|async)
を定義することです。環境変数とシステム プロパティの両方が定義されている場合は、環境変数が優先されます。
アプリの MTE を有効にする
指定しなければ MTE はデフォルトで無効になりますが、アプリで MTE を使用するには、AndroidManifest.xml
の <application>
タグまたは <process>
タグで android:memtagMode
を設定します。
android:memtagMode=(off|default|sync|async)
この属性を <application>
タグで設定すると、アプリが使用するすべてのプロセスに影響が及びます。<process>
タグを設定すると個々のプロセスについてオーバーライドできます。
実験的に、マニフェストで値を指定していない(または default
を指定している)アプリについて memtagMode
属性のデフォルト値を設定するために、互換性の変更を使用できます。
これはグローバル設定メニューの System > Advanced > Developer options
> App Compatibility Changes
にあります。NATIVE_MEMTAG_ASYNC
または NATIVE_MEMTAG_SYNC
を設定すると、特定のアプリに対して MTE が有効になります。
あるいは、次のように am
コマンドを使用して設定することもできます。
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
MTE システム イメージをビルドする
開発時と立ち上げ時は、すべてのネイティブ バイナリに対して MTE を有効にすることを強くおすすめします。これにより、メモリ安全性のバグを早期に検出でき、テストビルドで有効にした場合、現実的なユーザー カバレッジが提供されます。
開発時は、すべてのネイティブ バイナリに対して MTE を同期モードで有効にすることを強くおすすめします。
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
ビルドシステム内の他の変数と同様に、SANITIZE_TARGET
は環境変数または make
設定(product.mk
ファイルなど)として使用できます。
なお、これによりすべてのネイティブ プロセスについて MTE が有効になりますが、アプリ(zygote64
からフォークされたもの)については、前述の手順では MTE を有効にできません。
CPU 固有の優先 MTE レベルを構成する
CPU によっては、ASYMM または SYNC モードでの MTE のパフォーマンスが ASYNC モードでのパフォーマンスと同様になる場合があります。そのため、パフォーマンスを低下させることなく厳格なチェックによるエラー検出のメリットを得るために、あまり厳格でないチェックモードがリクエストされた場合に、そうした CPU で厳格なチェックを有効にする価値はあります。
デフォルトでは、ASYNC モードで動作するように構成されたプロセスは、すべての CPU において ASYNC モードで動作します。特定の CPU においてこれらのプロセスを SYNC モードで動作するようにカーネルを構成するには、値 sync を起動時に sysfs
エントリ /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred
に書き込む必要があります。そのためには init スクリプトを使用します。たとえば、ASYNC モードのプロセスが SYNC モードで動作するように CPU 0~1 を構成し、ASYMM モードで動作するように CPU 2~3 を設定するには、ベンダー init スクリプトの init 句に以下の行を追加します。
write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm
SYNC モードで動作している ASYNC モードのプロセスの Tombstone には、メモリエラーの位置の正確なスタック トレースが含まれます。ただし、割り当てまたは割り当て解除のスタック トレースは含まれません。これらのスタック トレースは、プロセスが SYNC モードで動作するように構成されている場合にのみ利用できます。
int mallopt(M_THREAD_DISABLE_MEM_INIT, level)
ここで level
は 0 または 1 です。
malloc でのメモリの初期化を無効にし、正確性のために必要な場合を除き、メモリタグの変更を回避します。
int mallopt(M_MEMTAG_TUNING, level)
ここで level
は次のとおりです。
M_MEMTAG_TUNING_BUFFER_OVERFLOW
M_MEMTAG_TUNING_UAF
タグの割り当て方法を選択します。
- デフォルト設定は
M_MEMTAG_TUNING_BUFFER_OVERFLOW
です。 M_MEMTAG_TUNING_BUFFER_OVERFLOW
- 隣接する割り当てに異なるタグ値を割り当てることで、線形バッファのオーバーフローとアンダーフローのバグを決定論的に検出できます。このモードでは、各メモリ位置で利用可能なタグ値の半分しか利用できないため、解放後の使用バグを検出する可能性が若干低くなります。MTE では、同じタグ グラニュール(16 バイトに揃ったチャンク)内のオーバーフローを検出できず、このモードでも小さなオーバーフローを見逃す可能性があります。1 つのグラニュール内のメモリが複数の割り当てに使用されることはないため、このようなオーバーフローはメモリ破損の原因にはなりません。M_MEMTAG_TUNING_UAF
- 空間的バグ(バッファ オーバーフロー)と時間的バグ(解放後の使用)の両方を、一様に 93% の確率で検出するために、独立してランダム化されたタグを有効にします。
上記の API に加えて、経験豊富なユーザーは次の点に注意してください。
PSTATE.TCO
ハードウェア レジスタを設定すると、タグチェックを一時的に抑制できます(例)。たとえば、タグの内容が不明なメモリ範囲をコピーする場合や、ホットループでパフォーマンスのボトルネックに対処する場合などです。M_HEAP_TAGGING_LEVEL_SYNC
を使用する場合、システム クラッシュ ハンドラは、割り当てと割り当て解除のスタック トレースなどの追加情報を提供します。この機能はタグビットにアクセスする必要があり、シグナル ハンドラの設定時にSA_EXPOSE_TAGBITS
フラグを渡すことで有効になります。独自のシグナル ハンドラを設定し、不明なクラッシュをシステム ハンドラに委任するプログラムでも、同様にすることをおすすめします。
カーネルの MTE
カーネルで MTE アクセラレーテッド KASAN を有効にするには、CONFIG_KASAN=y
、CONFIG_KASAN_HW_TAGS=y
を指定してカーネルを構成します。これらの構成は、GKI カーネルでは Android
12-5.10
以降、デフォルトで有効になっています。
これは、次のコマンドライン引数を使用して起動時に制御できます。
kasan=[on|off]
- KASAN を有効または無効にします(デフォルト:on
)。kasan.mode=[sync|async]
- 同期モードか非同期モードかを選択します(デフォルト:sync
)。kasan.stacktrace=[on|off]
- スタック トレースを収集するかどうか(デフォルト:on
)。- スタック トレースの収集には
stack_depot_disable=off
も必要です。
- スタック トレースの収集には
kasan.fault=[report|panic]
- レポートのみを出力するか、カーネル パニックも起こすか(デフォルト:report
)。このオプションに関係なく、最初に報告されたエラーの後にタグチェックが無効になります。
おすすめの使用方法
立ち上げ時、開発時、テスト時は SYNC モードを使用することを強くおすすめします。このオプションは、環境変数を使用して、またはビルドシステムにより、すべてのプロセスについてグローバルに有効にする必要があります。このモードでは、開発プロセスの早い段階でバグが検出され、コードベースが早く安定し、後ほど製品版でバグを検出するためのコストをかけずに済みます。
製品版では ASYNC モードを使用することを強くおすすめします。プロセス内のメモリ安全性の有無を検出し、さらに多層防御を行うための、オーバーヘッドの少ないツールが提供されます。バグが検出されると、デベロッパーはランタイム API を利用して SYNC モードに切り替え、サンプリングされた一連のユーザーから正確なスタック トレースを取得できます。
SoC の CPU 固有の優先 MTE レベルを構成することを強くおすすめします。通常、非対称モードはパフォーマンス特性が ASYNC モードと同じであり、ほとんどの場合は ASYNC モードをおすすめします。スモール インオーダー コアは 3 つのモードすべてで同様のパフォーマンスを示すことが多く、SYNC を優先するように構成できます。
デベロッパーは、/data/tombstones
、logcat
をチェックするか、ベンダーの DropboxManager
パイプラインでエンドユーザーのバグをモニタリングして、クラッシュがないか確認する必要があります。Android ネイティブ コードのデバッグについて詳しくは、こちらをご覧ください。
MTE が有効なプラットフォーム コンポーネント
Android 12 では、セキュリティの面で重要なシステム コンポーネントの多くが MTE ASYNC を使用してエンドユーザーのクラッシュを検出し、多層防御の追加のレイヤとして機能します。これらのコンポーネントは以下のとおりです。
- ネットワーク デーモンとユーティリティ(
netd
を除く) - Bluetooth、SecureElement、NFC HAL、システムアプリ
statsd
デーモンsystem_server
zygote64
(アプリで MTE の使用をオプトインできるようにする)
これらのターゲットは、次の基準に基づいて選択されました。
- 特権プロセス(unprivileged_app SELinux ドメインでアクセスできないものにアクセスできるプロセスとして定義)
- 信頼できない入力を処理する(2 のルール)
- 許容可能なパフォーマンス低下(低下してもユーザーが認識できるレイテンシが発生しない)
Google はベンダーに対し、上記の基準に沿って、より多くのコンポーネントについて製品版で MTE を有効にすることをおすすめします。開発時は、SYNC モードを使用してこれらのコンポーネントをテストし、簡単に修正できるバグを検出して、ASYNC がパフォーマンスに与える影響を評価することをおすすめします。
Android では将来的に、今後のハードウェア設計のパフォーマンス特性に基づいて、MTE が有効なシステム コンポーネントのリストを拡大する予定です。