瞭解 MTE 報表

出現代碼 9 (SEGV_MTESERR) 或代碼 8 (SEGV_MTEAERR) 的 SIGSEGV 當機是「記憶體標記」錯誤。 Memory Tagging Extension (MTE) 是 Android 12 以上版本支援 Armv9 功能。MTE 是導入標記功能的硬體實作方式 記憶體用量它能提供精細的記憶體防護機制,可以偵測並緩解 記憶體安全錯誤

在 C/C++ 中,對 Malloc() 的呼叫傳回的指標、運算子 new() 或類似的函式可以 僅用於存取該分配範圍內的記憶體 配置為有效狀態 (非免費或已刪除)。Android 會使用 MTE 偵測 此規則在當機報告中稱為「緩衝區溢位」/「Buffer Underflow」和 「釋放後使用」以負載平衡機制分配流量 即可降低應用程式發生效能問題的風險

MTE 有同步 (或「同步」) 和非同步 (或「非同步」) 模式。較早的跑步訓練次數 但能提供更準確的診斷資料後者的執行速度較快,但只會讓 概略詳細資料我們會單獨說明這兩個選項,因為診斷方式略有不同。

同步模式 MTE

在 MTE 的同步 (「同步」) 模式下,SIGSEGV 會因代碼 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 偵測到錯誤的行將含有「[MTE]」如同 並提供更多詳細資料在這個案例中,系統偵測到的特定錯誤類型 「釋放後使用」和「0x7ae92853a0 的 32 個位元組配置」告訴我們 分配大小和位址,以及偏移量嘗試存取的配置中。

MTE 當機報告不僅包含從偵測點到的回傳追蹤,還會包含額外的返回追蹤。

「釋放後使用」新增「取消分配方式」「分配依據」「當機傾印」部分 會顯示這個記憶體在配置時 (使用之前!) 時的堆疊追蹤,以及 分配時間您也會知道哪個執行緒進行了 分配/取消分配全部三個偵測到的執行緒、分配執行緒和取消配置 這個簡單的例子都相同,但在現實生活中,這並不是 事實並非如此,只要瞭解兩者不同之處,就能得知 並行相關錯誤

「緩衝區溢位」以及「Buffer Underflow」錯誤只會提供額外的「分配依據」 堆疊追蹤,因為定義本身尚未取消配置 (或只會以 「免費使用後」):

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

請注意,這裡使用「right」一詞這表示您要查詢結尾有多少位元組 錯誤存取行為;反向溢位會顯示「左」 也就是分配開始之前的位元組

多個可能原因

有時 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...]

在上述範例中,我們偵測到同一個記憶體位址中有兩項近期配置作業,而這可能會 為無效記憶體存取行為的預期目標。如果配置重複使用,就可能發生這種情況 免費記憶體空間;舉例來說,如果您的序列包含「New」、「free」、「new」、「free」、「new」、「free」 資源存取權系統會先顯示最近的分配範圍。

詳細原因決定經驗法則

原因的當機問題應顯示存取指標最初衍生出的記憶體配置。很抱歉,MTE 硬體無法從含有不相符標記的指標轉譯為配置。為瞭解釋 SEGV_MTESERR 當機問題,Android 會分析以下資料:

  • 錯誤地址 (包括指標標記)。
  • 近期堆積配置清單,包含堆疊追蹤和記憶體標記。
  • 附近的目前 (使用中) 配置及其記憶體標記。

任何近期在錯誤位址中釋放的記憶體 (記憶體標記與錯誤位址標記相符) 都可能是「使用釋放後使用」。

如果記憶體標記與錯誤位址標記相符,任何鄰近的使用中記憶體都可能是「緩衝區溢位」(或「緩衝區不足」)。

系統判定較接近錯誤 (無論是在時間或太空中) 的配置,都會比距離遙遠的配置更有可能發生。

由於已分配的記憶體通常會重複使用,而不同標記值的數量較小 (小於 16),因此找到幾個可能的可能對象並不常見,也無法自動找出真正原因。因此,MTE 報表有時會列出多個可能原因。

建議應用程式開發人員從最有可能的原因開始著手。通常可以很容易地根據堆疊追蹤篩選出不相關的原因。

非同步模式 MTE

在 MTE 的非同步 (「非同步」) 模式下,SIGSEGV 會因程式碼 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

特殊的「記憶體標記」部分也會顯示在當機報告中,指出故障地址周圍的記憶體標記。在以下範例中,指標標記「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

在空值標記部分顯示所有暫存器值周圍的記憶體內容,也會顯示標記值。

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  ..'.}...........