Arm 記憶體標記擴充功能

Arm v9 推出了 Arm Memory Tagging Extension (MTE),這是標記記憶體的硬體實作

從高層次來看,MTE 會使用額外的中繼資料標記每個記憶體配置/解除配置。它會將標記指派給記憶體位置,然後與參照該記憶體位置的指標建立關聯。在執行階段,CPU 會檢查指標和中繼資料標記在每次載入和儲存時是否相符。

在 Android 12 中,核心和使用者空間堆積記憶體分配器可以為每個分配項目擴增中繼資料。這有助於偵測釋放後使用和緩衝區溢位錯誤,這類錯誤是程式碼庫中最常見的記憶體安全錯誤來源。

MTE 運作模式

MTE 有三種運作模式:

  • 同步模式 (SYNC)
  • 非同步模式 (ASYNC)
  • 非對稱模式 (ASYMM)

同步模式 (SYNC)

相較於效能,這個模式改善的是錯誤偵測的準確度。如果您認為效能負載高並不構成問題,可以利用此工具準確偵測錯誤。此外,您也可將啟用 MTE SYNC 做為因應安全性問題的有效措施。如果標記不相符,處理器會立即中止執行作業,並透過 SIGSEGV (代碼 SEGV_MTESERR) 以及記憶體存取和錯誤位址的完整資訊,終止處理程序。

建議您在測試期間使用此模式,做為 HWASan/KASAN 的替代方案,或是在實際工作環境中,用於目標程序出現安全漏洞攻擊途徑的場合。此外,當 ASYNC 模式指出有錯誤時,您只要使用執行階段 API 將執行作業切換為 SYNC 模式,即可取得準確的錯誤報告。

以 SYNC 模式執行時,Android 分配器會記錄所有分配和解除分配的堆疊追蹤記錄,並使用這些記錄提供更完善的錯誤報告,包括說明記憶體錯誤 (例如使用已釋放記憶體或緩衝區溢位) 和相關記憶體事件的堆疊追蹤記錄。這類報告可提供更多背景資訊,讓您更輕鬆地追蹤及修正錯誤。

非同步模式 (ASYNC)

相較於錯誤報告的準確度,此模式較重視效能,因此即使負載低也能偵測記憶體安全錯誤。
如果標記不相符,處理器會繼續執行,直到最接近的核心項目 (例如系統呼叫或計時器中斷) 為止,此時,該程序會透過 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 模式 包含清單 排除清單
async 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 代表可執行檔的基本名稱。

舉例來說,如要將 /system/bin/ping/data/local/tmp/ping 設定為使用非同步 MTE,請使用 adb shell setprop arm64.memtag.process.ping async

使用環境變數啟用 MTE

如要為原生程序 (非應用程式) 覆寫建構設定,也可以定義環境變數:MEMTAG_OPTIONS=(off|sync|async) 如果同時定義環境變數和系統屬性,變數會優先採用。

為應用程式啟用 MTE

如未指定,系統預設會停用 MTE,但應用程式可在 AndroidManifest.xml<application><process> 標記底下設定 android:memtagMode,藉此使用 MTE。

android:memtagMode=(off|default|sync|async)

<application> 標記上設定屬性時,屬性會影響應用程式使用的所有程序;如要針對個別程序覆寫這項屬性,請設定 <process> 標記。

進行實驗時,如果應用程式尚未在資訊清單中指定任何值 (或指定 default),您可以透過相容性變更,為上述應用程式設定 memtagMode 屬性的預設值。
這些設定位於全域設定選單的 System > Advanced > Developer options > App Compatibility Changes 下方。只要設定 NATIVE_MEMTAG_ASYNCNATIVE_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,但不會為應用程式啟用 MTE (應用程式是從 zygote64 分叉而來),如要為應用程式啟用 MTE,請按照上述指示操作。

設定 CPU 專屬的偏好 MTE 層級

在某些 CPU 上,ASYMM 甚至 SYNC 模式的 MTE 效能可能與 ASYNC 模式相似。因此,當要求較不嚴格的檢查模式時,啟用對這些 CPU 的更嚴格檢查是值得的,這樣既能獲得更嚴格檢查的錯誤偵測優勢,又不會導致效能下降。
根據預設,設定以 ASYNC 模式執行的程序會在所有 CPU 上以 ASYNC 模式執行。如要設定核心,讓這些程序在特定 CPU 上以 SYNC 模式執行,必須在啟動時將值同步寫入 sysfs 項目 /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred。您可以使用初始化指令碼完成這項操作。舉例來說,如要設定 CPU 0-1 在 SYNC 模式中執行 ASYNC 模式程序,並設定 CPU 2-3 使用 ASYMM 模式執行,則可將下列項目新增至供應商 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 模式程序所產生的墓碑,會包含記憶體錯誤位置的精確堆疊追蹤記錄。不過,這些記錄不會包含分配或取消分配的堆疊追蹤記錄。只有在程序設定為以 SYNC 模式執行時,才能使用這些堆疊追蹤記錄。

int mallopt(M_THREAD_DISABLE_MEM_INIT, level)

其中 level 為 0 或 1。
Disables memory initialization in malloc, and avoids changing memory tags unless necessary for correctness.

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 位元組對齊區塊) 內的溢位,即使在此模式下,也可能錯過小型溢位。這類溢位不會導致記憶體損毀,因為一個粒度內的記憶體絕不會用於多個配置。
  • M_MEMTAG_TUNING_UAF - 啟用獨立隨機標記,以約 93% 的一致機率偵測空間 (緩衝區溢位) 和時間 (釋放後使用) 錯誤。

除了上述 API 之外,經驗豐富的使用者可能還需要注意下列事項:

  • 設定 PSTATE.TCO 硬體暫存器可以暫時禁止檢查標記 (範例)。舉例來說,複製含有不明標記內容的記憶體範圍,或解決熱迴圈中的效能瓶頸。
  • 使用 M_HEAP_TAGGING_LEVEL_SYNC 時,系統當機處理常式會提供額外資訊,例如分配和解除分配堆疊追蹤。這項功能需要存取標記位元,並在設定信號處理常式時傳遞 SA_EXPOSE_TAGBITS 標記來啟用。如果程式會設定自己的信號處理常式,並將不明當機事件委派給系統,建議也採取相同做法。

核心中的 MTE

如要為核心啟用 MTE 加速的 KASAN,請使用 CONFIG_KASAN=yCONFIG_KASAN_HW_TAGS=y 設定核心。從 Android 12-5.10 開始,GKI 核心預設會啟用這些設定。
您可以使用下列指令列引數,在啟動時控制這項設定:

  • 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 層級。Asymm 模式通常具有與 ASYNC 相同的效能特徵,且幾乎一律優於 ASYNC。小型依序執行核心在所有三種模式中,通常會顯示相似的效能,且可設定為偏好 SYNC。

開發人員應檢查 /data/tombstoneslogcat,或監控供應商 DropboxManager 管道中的使用者錯誤,確認是否發生當機情形。如要進一步瞭解如何偵錯 Android 原生程式碼,請參閱這篇文章

支援 MTE 的平台元件

在 Android 12 中,許多安全關鍵系統元件會使用 MTE ASYNC,偵測使用者當機情形,並做為額外的縱深防禦層。這些元件包括:

  • 網路常駐程式和公用程式 (netd 除外)
  • 藍牙、SecureElement、NFC HAL 和系統應用程式
  • statsd daemon
  • system_server
  • zygote64 (允許應用程式選擇使用 MTE)

這些目標是根據下列條件選取:

  • 具備權限的程序 (定義為可存取 unprivileged_app SELinux 網域無法存取的項目)
  • 處理不可靠的輸入內容 (規則 之二)
  • 可接受的效能降低 (降低效能不會造成使用者可見的延遲)

我們建議供應商在實際工作環境中為更多元件啟用 MTE,並遵循上述條件。開發期間,建議使用 SYNC 模式測試這些元件,以便偵測容易修正的錯誤,並評估 ASYNC 對元件效能的影響。
Android 計畫在日後擴充 MTE 適用的系統元件清單,並以即將推出的硬體設計效能特性為指引。