MTE レポートについて

code 9(SEGV_MTESERR)または code 8(SEGV_MTEAERR)での SIGSEGV クラッシュは、メモリタグ付けエラーです。 Memory Tagging 拡張機能(MTE)は、Android 12 以降でサポートされている Armv9 の機能です。MTE はタグ付きメモリのハードウェア実装であり、メモリ安全性のバグの検出と軽減のためにきめ細かいメモリ保護を提供します。

C/C++ では、malloc() または演算子 new()、もしくは同様の関数の呼び出しから返されたポインタは、その割り当ての境界内に限り、かつ割り当てが確保されている(free または delete により解放されていない)間のみ、メモリアクセスに使用できます。Android では、このルールの違反を検出するために MTE を使用しています。この違反は、クラッシュ レポートでは「バッファ オーバーフロー」/「バッファ アンダーフロー」および「解放後の使用」のエラーとして示されます。

MTE には、同期(「sync」)と非同期(「async」)の 2 つのモードがあります。前者は実行に時間がかかりますが、より正確な診断を提供できます。後者は高速に実行されますが、おおよその情報しか示されません。それぞれの診断は多少異なるため、個別に説明します。

MTE の同期モード

MTE の同期(「sync」)モードでは、SIGSEGV が code 9(SEGV_MTESERR)でクラッシュします。

pid: 13935, tid: 13935, name: sanitizer-statu  >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0  0000007cd94227cc  x1  0000007cd94227cc  x2  ffffffffffffffd0  x3  0000007fe81919c0
x4  0000007fe8191a10  x5  0000000000000004  x6  0000005400000051  x7  0000008700000021
x8  0800007ae92853a0  x9  0000000000000000  x10 0000007ae9285000  x11 0000000000000030
x12 000000000000000d  x13 0000007cd941c858  x14 0000000000000054  x15 0000000000000000
x16 0000007cd940c0c8  x17 0000007cd93a1030  x18 0000007cdcac6000  x19 0000007fe8191c78
x20 0000005800eee5c4  x21 0000007fe8191c90  x22 0000000000000002  x23 0000000000000000
x24 0000000000000000  x25 0000000000000000  x26 0000000000000000  x27 0000000000000000
x28 0000000000000000  x29 0000007fe8191b70
lr  0000005800eee0bc  sp  0000007fe8191b60  pc  0000005800eee0c0  pst 0000000060001000

backtrace:
      #00 pc 00000000000010c0  /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #01 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #02 pc 00000000000019cc  /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000487d8  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)

deallocated by thread 13935:
      #00 pc 000000000004643c  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 00000000000421e4  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 00000000000010b8  /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)

allocated by thread 13935:
      #00 pc 0000000000042020  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 0000000000042394  /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 000000000003cc9c  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #03 pc 00000000000010ac  /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #04 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)

すべての MTE クラッシュ レポートに、問題が検出されたポイントの通常のレジスタダンプとバックトレースが含まれます。MTE によって検出されたエラーの「Cause:」(原因)行には、上記の例のように「[MTE]」と詳細な情報が示されます。このケースでは、検出された特定の種類のエラーは「Use After Free」(解放後の使用)であり、「0 bytes into a 32-byte allocation at 0x7ae92853a0」という記述から、割り当てのサイズとアドレス、およびアクセスしようとした割り当てまでのオフセットがわかります。

MTE クラッシュ レポートには、検出ポイントからのバックトレースだけでなく、追加のバックトレースも出力されます。

「Use After Free」エラーの場合、クラッシュダンプに「deallocated by」セクションと「allocated by」セクションが追加されます。ここには、このメモリの割り当てが解除された時点(使用前)と、前に割り当てられていた時点のスタック トレースが示されます。これにより、どのスレッドが割り当てまたは割り当て解除を行ったかもわかります。このシンプルな例では、検出したスレッド、割り当てを行ったスレッド、割り当てを解除したスレッドの 3 つがすべて同じものですが、より複雑な実際のケースでは必ずしもこうなるとは限りません。これらの不一致に気付くことが、同時実行に関連するバグを見つける際の重要な手がかりになります。

「Buffer Overflow」(バッファ オーバーフロー)と「Buffer Underflow」(バッファ アンダーフロー)のエラーでは追加の「allocated by」スタック トラックのみが提供されます。定義上、これらは割り当ての解除がまだ完了していないためです(そうでなければ「Use After Free」として表示されます)。

Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0
[...]
backtrace:
[...]
allocated by thread 13949:

ここで「right」という用語が使われているのに注目してください。これは、不正なアクセスが割り当ての最後から何バイト目だったかを示しています。アンダーフローの場合は「left」となり、割り当ての先頭から何バイト目かを表します。

考えられる複数の原因

SEGV_MTESERR レポートに次の行が含まれていることがあります。

Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.

これは、エラーの原因としていくつか候補がある場合に示されます。実際の原因はシステムでは識別できません。このような候補は可能性が高い順に 3 つまで出力され、分析はユーザーに委ねられます。

signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x400007b43063db5
backtrace:
    [stack...]

Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.

Cause: [MTE]: Use After Free, 5 bytes into a 10-byte allocation at 0x7b43063db0
deallocated by thread 6663:
    [stack...]
allocated by thread 6663:
    [stack...]

Cause: [MTE]: Use After Free, 5 bytes into a 6-byte allocation at 0x7b43063db0
deallocated by thread 6663:
    [stack...]

allocated by thread 6663:
    [stack...]

上記の例では、同じメモリアドレスで、無効なメモリアクセスの意図的なターゲットだった可能性がある、2 つの最近の割り当てが検出されています。これは、割り当てが空きメモリを再利用している場合に発生することがあります。たとえば、new、free、new、free、new、free、といった連続的アクセスがある場合です。時間的に新しい割り当てが最初に出力されます。

詳細な原因判定のためのヒューリスティック

クラッシュの「Cause」には、アクセスされたポインタが最初に生成されたメモリ割り当てが表示されます。残念ながら、MTE ハードウェアには、タグが一致しないポインタを特定の割り当てに変換する手段は実装されていません。SEGV_MTESERR のクラッシュを説明するために、Android では次のデータを分析します。

  • 障害アドレス(ポインタタグを含む)。
  • スタック トレースとメモリタグを含む、最近のヒープ割り当てのリスト。
  • 付近の現在の(ライブ)割り当てとそのメモリタグ。

障害アドレスのメモリタグが障害アドレスタグと一致する場合、そのアドレスで最近割り当て解除されたメモリが「Use After Free」の原因である可能性があります。

付近のライブメモリのメモリタグが障害アドレスタグと一致する場合は、そのライブメモリが「Buffer Overflow」(または「Buffer Underflow」)の原因である可能性があります。

(時間的または空間的のいずれかで)障害の付近にある割り当ては、離れている割り当てよりも、原因である可能性が高いと考えられます。

割り当て解除されたメモリは再利用されることが多く、また異なるタグ値の数は少ないため(16 未満)、複数の候補が見つかることは珍しくありません。また、真の原因を自動的に特定する方法もありません。MTE レポートに複数の考えられる原因が示されることがあるのはそのためです。

アプリ デベロッパーには、可能性が高い順に、考えられる原因を確認することをおすすめします。多くの場合、スタック トレースに基づいて無関係な原因を簡単に除外できます。

MTE の非同期モード

MTE の非同期(「async」)モードでは、SIGSEGV が code 8(SEGV_MTEAERR)でクラッシュします。

SEGV_MTEAERR の障害は、プログラムが無効なメモリアクセスを実行した直後には発生しません。この事象はイベントの直後に検出され、その時点でプログラムが終了します。通常、このタイミングは次のシステムコールですが、タイマー割り込みになることもあります。つまり、ユーザー空間からカーネルに遷移するときということです。

SEGV_MTEAERR の障害では、メモリアドレスは保持されません(常に「-------」と表示されます)。バックトレースに相当するのは、無効なアクセスが実行されたタイミングではなく、条件が検出された瞬間(次のシステムコールまたは他のコンテキスト スイッチ時)です。

つまり、非同期の MTE クラッシュの「メイン」バックトレースは通常、関連性がありません。したがって、非同期モードの障害は、同期モードの障害よりもデバッグが難しくなります。特定のスレッドの近くのコードにメモリバグが存在することを示していると解釈するのが、最も妥当です。Tombstone ファイル下部のログに、実際に何が起きたのかのヒントが見つかる場合もありますが、それ以外の場合は、同期モードでエラーを再現し、同期モードが提供する詳細な診断を利用することをおすすめします。

高度なトピック

内部的には、メモリのタグ付けでは、すべてのヒープ割り当てに 4 ビット(0..15)のタグ値がランダムに割り当てられます。この値は、割り当てられたヒープメモリに対応する特別なメタデータ領域に格納されます。malloc() や 演算子 new() などの関数から返されたヒープポインタの最上位バイトに、同じ値が割り当てられます。

このプロセスでタグチェックを有効にすると、CPU はメモリアクセスごとにポインタの上位バイトとメモリタグを自動的に比較します。タグが一致しない場合、CPU はクラッシュにつながるエラー シグナルを出力します。

取り得るタグ値の数が少ないため、このアプローチは確率論的です。境界外を指すポインタや、割り当て解除後のポインタ(「ダングリング ポインタ」)など、特定のポインタでアクセスすべきでないメモリ位置はタグ値が一致しない可能性があり、クラッシュが発生します。バグの発生が検出されない確率は高くても 7% 程度です。一方、タグの値はランダムに割り当てられます。つまり、次回発生するバグが検出される確率は独立であり、93% 程度になります。

タグの値は、下記でハイライトされているように、障害アドレス フィールドとレジスタダンプで確認することが可能です。このセクションを使用して、タグが正しく設定されているかどうかを確認できます。また、レポートのリストに含まれていないエラーの潜在的な原因として、同じタグ値を持つ他の付近のメモリ割り当てを調べることもできます。これは、デベロッパーではなく、MTE 自体または低レベルのシステム コンポーネントの実装に携わる方々にとって主に有用であると考えられます。

signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
    x0  0000007cd94227cc  x1  0000007cd94227cc  x2  ffffffffffffffd0  x3  0000007fe81919c0
    x4  0000007fe8191a10  x5  0000000000000004  x6  0000005400000051  x7  0000008700000021
    x8  0800007ae92853a0  x9  0000000000000000  x10 0000007ae9285000  x11 0000000000000030
    x12 000000000000000d  x13 0000007cd941c858  x14 0000000000000054  x15 0000000000000000
    x16 0000007cd940c0c8  x17 0000007cd93a1030  x18 0000007cdcac6000  x19 0000007fe8191c78
    x20 0000005800eee5c4  x21 0000007fe8191c90  x22 0000000000000002  x23 0000000000000000
    x24 0000000000000000  x25 0000000000000000  x26 0000000000000000  x27 0000000000000000
    x28 0000000000000000  x29 0000007fe8191b70
    lr  0000005800eee0bc  sp  0000007fe8191b60  pc  0000005800eee0c0  pst 0000000060001000

クラッシュ レポートには、障害アドレス周辺のメモリタグを示すための「Memory tags」セクションも表示されます。以下の例では、ポインタタグ「4」がメモリタグ「a」と一致しませんでした。

Memory tags around the fault address (0x0400007b43063db5), one tag per 16 bytes:
  0x7b43063500: 0  f  0  2  0  f  0  a  0  7  0  8  0  7  0  e
  0x7b43063600: 0  9  0  8  0  5  0  e  0  f  0  c  0  f  0  4
  0x7b43063700: 0  b  0  c  0  b  0  2  0  1  0  4  0  7  0  8
  0x7b43063800: 0  b  0  c  0  3  0  a  0  3  0  6  0  b  0  a
  0x7b43063900: 0  3  0  4  0  f  0  c  0  3  0  e  0  0  0  c
  0x7b43063a00: 0  3  0  2  0  1  0  8  0  9  0  4  0  3  0  4
  0x7b43063b00: 0  5  0  2  0  5  0  a  0  d  0  6  0  d  0  2
  0x7b43063c00: 0  3  0  e  0  f  0  a  0  0  0  0  0  0  0  4
=>0x7b43063d00: 0  0  0  a  0  0  0  e  0  d  0 [a] 0  f  0  e
  0x7b43063e00: 0  7  0  c  0  9  0  a  0  d  0  2  0  0  0  c
  0x7b43063f00: 0  0  0  6  0  b  0  8  0  3  0  0  0  5  0  e
  0x7b43064000: 0  d  0  2  0  7  0  a  0  7  0  a  0  d  0  8
  0x7b43064100: 0  b  0  2  0  b  0  4  0  1  0  6  0  d  0  4
  0x7b43064200: 0  1  0  6  0  f  0  2  0  f  0  6  0  5  0  c
  0x7b43064300: 0  1  0  4  0  d  0  6  0  f  0  e  0  1  0  8
  0x7b43064400: 0  f  0  4  0  3  0  2  0  1  0  2  0  5  0  6

これらのタグ値は、全レジスタ値のメモリの内容が出力される Tombstone のセクションにも表示されます。

memory near x10 ([anon:scudo:primary]):
0000007b4304a000 7e82000000008101 000003e9ce8b53a0  .......~.S......
0700007b4304a010 0000200000006001 0000000000000000  .`... ..........
0000007b4304a020 7c03000000010101 000003e97c61071e  .......|..a|....
0200007b4304a030 0c00007b4304a270 0000007ddc4fedf8  p..C{.....O.}...
0000007b4304a040 84e6000000008101 000003e906f7a9da  ................
0300007b4304a050 ffffffff00000042 0000000000000000  B...............
0000007b4304a060 8667000000010101 000003e9ea858f9e  ......g.........
0400007b4304a070 0000000100000001 0000000200000002  ................
0000007b4304a080 f5f8000000010101 000003e98a13108b  ................
0300007b4304a090 0000007dd327c420 0600007b4304a2b0   .'.}......C{...
0000007b4304a0a0 88ca000000010101 000003e93e5e5ac5  .........Z^>....
0a00007b4304a0b0 0000007dcc4bc500 0300007b7304cb10  ..K.}......s{...
0000007b4304a0c0 0f9c000000010101 000003e9e1602280  ........."`.....
0900007b4304a0d0 0000007dd327c780 0700007b7304e2d0  ..'.}......s{...
0000007b4304a0e0 0d1d000000008101 000003e906083603  .........6......
0a00007b4304a0f0 0000007dd327c3b8 0000000000000000  ..'.}...........