了解 MTE 報告

SIGSEGV 崩潰並顯示代碼 9 (SEGV_MTESERR) 或代碼 8 (SEGV_MTEAERR) 是記憶體標記錯誤。記憶體標記擴充 (MTE)是 Android 12 及更高版本支援的 Armv9 功能。 MTE 是標記記憶體的硬體實作。它提供細粒度的記憶體保護,用於檢測和緩解記憶體安全錯誤

在 C/C++ 中,從呼叫 malloc() 或運算子 new() 或類似函數傳回的指標只能用於存取該分配範圍內的內存,並且只能在分配處於活動狀態時(未釋放或釋放)刪除編輯)。 Android 中使用 MTE 來偵測是否違反此規則,在崩潰報告中稱為「緩衝區溢位」/「緩衝區下溢」和「釋放後使用」問題。

MTE 有兩種模式:同步(或“sync”)和非同步(或“async”)。前者運行速度較慢,但提供更準確的診斷。後者運行速度較快,但只能給出大概的細節。我們將分別介紹兩者,因為診斷方法略有不同。

同步模式MTE

在 MTE 的同步(“sync”)模式下,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 偵測到的錯誤的「Cause:」行將包含「[MTE]」(如上例所示)以及更多詳細資訊。在這種情況下,檢測到的特定錯誤類型是“釋放後使用”,“0x7ae92853a0 處的 32 位元組分配中的 0 位元組”告訴我們分配的大小和位址,以及分配的偏移量。嘗試訪問。

MTE 崩潰報告還包括額外的回溯,而不僅僅是來自偵測點的回溯。

「釋放後使用」錯誤將「釋放者」和「分配者」部分新增至故障轉儲中,顯示釋放此記憶體時的堆疊追蹤(在使用之前!)以及先前分配的時間。這些也告訴您哪個執行緒進行了分配/解除分配。在這個簡單的範例中,檢測線程、分配線程和解除分配線程這三個線程都是相同的,但在更複雜的實際情況中,這不一定是正確的,並且了解它們的不同可能是查找並發性的重要線索相關的錯誤。

「緩衝區溢位」和「緩衝區下溢」錯誤僅提供額外的「分配者」堆疊跟踪,因為根據定義它們尚未被釋放(或它們會顯示為「釋放後使用」):

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

請注意此處使用“正確”一詞:這意味著我們告訴您錯誤存取超出了分配末尾的位元組數;下溢表示“左”,並且是分配開始之前的位元組數。

多種潛在原因

有時,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、access 等序列。首先列印最近的分配。

詳細的原因確定啟發法

崩潰的「原因」應顯示所存取的指標最初源自的記憶體分配。不幸的是,MTE 硬體無法將帶有不匹配標記的指標轉換為分配。為了解釋 SEGV_MTESERR 崩潰,Android 分析了以下數據:

  • 故障位址(包括指針標記)。
  • 最近的堆疊分配列表,包含堆疊追蹤和記憶體標籤。
  • 附近當前(即時)分配及其記憶體標籤。

任何最近在故障位址處釋放的記憶體(其中記憶體標籤與故障位址標籤相符)都是潛在的「釋放後使用」原因。

記憶體標籤與故障位址標籤相符的任何附近的活動記憶體都是潛在的「緩衝區溢位」(或「緩衝區下溢」)原因。

距離故障較近的分配(無論是在時間上還是在空間上)被認為比距離較遠的分配更有可能。

由於釋放的記憶體經常被重複使用,並且不同標籤值的數量很少(小於16),因此找到幾個可能的候選者並不罕見,並且沒有辦法自動找到真正的原因。這就是為什麼有時 MTE 報告會列出多個潛在原因的原因。

建議應用程式開發人員從最有可能的原因開始尋找潛在原因。根據堆疊追蹤過濾掉不相關的原因通常很容易。

非同步模式MTE

在 MTE 的非同步(“async”)模式下,SIGSEGV 崩潰並顯示代碼 8 (SEGV_MTEAERR)。

當程式執行無效記憶體存取時,SEGV_MTEAERR 故障不會立即發生。該問題在事件發生後不久就被偵測到,程式隨即終止。該點通常是下一個系統調用,但也可以是計時器中斷 - 簡而言之,任何用戶空間到核心的轉換。

SEGV_MTEAERR 故障不保留記憶體位址(它始終顯示為“-------”)。回溯對應於偵測到條件的時刻(即在下一個系統呼叫或其他上下文切換時),而不是執行無效存取時。

這意味著非同步 MTE 崩潰中的「主要」回溯通常是不相關的。因此,非同步模式故障比同步模式故障更難調試。最好將它們理解為顯示給定線程中附近程式碼中存在記憶體錯誤。邏輯刪除檔案底部的日誌可能會提示實際發生的情況。否則,建議的操作過程是在同步模式下重現錯誤並使用同步模式提供的更好的診斷!

高級主題

在底層,記憶體標記的工作原理是為每個堆分配一個隨機的 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  ..'.}...........