Arm 記憶體標記擴充功能

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

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

在 Android 12 中,核心和使用者空間堆積記憶體分配器可以使用結構描述符增強每個分配作業。這有助於偵測使用後釋放和緩衝區溢位錯誤,這類錯誤是程式碼庫中記憶體安全錯誤最常見的來源。

MTE 運作模式

MTE 有三種運作模式:

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

同步模式 (SYNC)

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

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

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

非同步模式 (ASYNC)

相較於錯誤報告的準確度,此模式較重視效能,因此即使負載低也能偵測記憶體安全錯誤。
如果標記不相符,處理器會繼續執行,直到最接近的核心項目 (例如發生系統呼叫或計時器中斷的情形) 為止,此時,該程序會透過 SIGSEGV (code 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 預設為停用狀態,但如果應用程式想要使用 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_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,但不會為應用程式 (從 zygote64 分支) 啟用 MTE,因為您可以按照上方的指示啟用 MTE。

設定 CPU 專屬的偏好 MTE 等級

在某些 CPU 上,MTE 在 ASYMM 或 SYNC 模式下的效能可能與 ASYNC 模式相似。因此,在要求較不嚴格的檢查模式時,啟用這些 CPU 的嚴格檢查功能就很有價值,這樣就能獲得嚴格檢查的錯誤偵測優點,而不必犧牲效能。
根據預設,在 ASYNC 模式中執行的程序會在所有 CPU 上以 ASYNC 模式執行。如要設定核心,以便在特定 CPU 上以 SYNC 模式執行這些程序,必須在啟動時將值同步處理寫入 sysfs 項目 /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred。您可以使用初始化指令碼執行這項操作。舉例來說,如要將 CPU 0-1 設為在 SYNC 模式下執行非同步模式程序,並將 CPU 2-3 設為在 ASYMM 模式下執行,您可以將下列內容加入供應商初始化指令碼的初始化子句:

  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。
在 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 個位元組對齊的區塊) 中偵測溢位,即使在這個模式下,也可能會遺漏小型溢位。這種溢位不會導致記憶體毀損,因為單一細目內的記憶體不會用於多個配置。
  • 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 網域無法存取的程序)
  • 處理不安全的輸入內容 (兩人規則)
  • 效能降幅可接受 (降幅不會造成使用者可見的延遲)

我們鼓勵供應商依據上述條件,為更多元件啟用實際工作環境。在開發期間,建議您使用 SYNC 模式測試這些元件,以便偵測容易修正的錯誤,並評估 ASYNC 對其效能造成的影響。
未來,Android 將根據即將推出的硬體設計效能特性,擴大啟用 MTE 的系統元件清單。