AArch64 二進位檔案的只執行記憶體 (XOM)

AArch64 系統二進位檔案的可執行程式碼部分預設標記為僅執行(不可讀取),作為即時程式碼重複使用攻擊的強化緩解措施。將資料和程式碼混合在一起的程式碼以及有目的地檢查這些部分的程式碼(無需首先將記憶體段重新映射為可讀)不再起作用。如果目標 SDK 為 10(API 等級 29 或更高)的應用程式嘗試讀取記憶體中啟用只執行記憶體 (XOM) 的系統函式庫的程式碼部分,而不先將該部分標記為可讀,則會受到影響。

為了充分受益於這種緩解措施,需要硬體和核心支援。如果沒有這種支持,緩解措施可能只能部分實施。 Android 4.9 通用核心包含適當的補丁,可在 ARMv8.2 裝置上提供對此的全面支援。

執行

編譯器產生的 AArch64 二進位檔案假定程式碼和資料不混合。啟用此功能不會對裝置的效能產生負面影響。

對於必須對其可執行段執行有意內存自省的程式碼,建議對需要檢查的程式碼段呼叫mprotect以允許它們可讀,然後在檢查完成時刪除可讀性。
此實作會導致讀取標記為只執行的記憶體段,從而導致分段錯誤 ( SEGFAULT )。這可能是由於錯誤、漏洞、資料與程式碼混合(文字池)或有意的記憶體自省而發生的。

設備支援和影響

具有早期硬體或早期核心(低於 4.9)且沒有所需修補程式的裝置可能無法完全支援或受益於此功能。沒有核心支援的裝置可能不會強制使用者存取只執行內存,但是明確檢查頁面是否可讀的核心程式碼仍可能強制執行此屬性,例如process_vm_readv()

必須在核心中設定核心標誌CONFIG_ARM64_UAO ,以確保核心尊重標記為只執行的使用者態頁面。早期的 ARMv8 裝置或停用了使用者存取覆蓋 (UAO) 的 ARMv8.2 裝置可能無法完全受益於此,並且可能仍然能夠使用系統呼叫讀取只執行頁面。

重構現有程式碼

從 AArch32 移植的程式碼可能包含混合的資料和程式碼,從而導致出現問題。在許多情況下,解決這些問題就像將常數移至組譯文件中的.data部分一樣簡單。

手寫程序集可能需要重構以分離本地池常數。

例子:

Clang 編譯器產生的二進位檔案不應該存在資料在程式碼中混合的問題。如果包含 GNU 編譯器集合 (GCC) 產生的程式碼(來自靜態函式庫),請檢查輸出二進位檔案以確保常數未合併到程式碼片段中。

如果需要對可執行程式碼段進行程式碼自省,請先呼叫mprotect將程式碼標記為可讀。然後操作完成後,再次呼叫mprotect將其標記為不可讀。

啟用

預設情況下,建置系統中的所有 64 位元二進位檔案都啟用僅執行。

停用

您可以在模組層級、整個子目錄樹或整個建置的全域停用只執行。

透過將LOCAL_XOMxom變數設為false ,可以對無法重構或需要讀取其可執行程式碼的各個模組停用 XOM。

// Android.mk
LOCAL_XOM := false

// Android.bp
cc_binary { // or other module types
   ...
   xom: false,
}

如果靜態庫中停用了只執行內存,則建置系統會將其套用於該靜態庫的所有依賴模組。您可以使用xom: true,覆寫它。

若要停用特定子目錄(例如 foo/bar/)中的只執行內存,請將值傳遞給XOM_EXCLUDE_PATHS

make -j XOM_EXCLUDE_PATHS=foo/bar

或者,您可以在產品配置中設定PRODUCT_XOM_EXCLUDE_PATHS變數。

您可以透過將ENABLE_XOM=false傳遞給make命令來全域停用只執行二進位。

make -j ENABLE_XOM=false

驗證

沒有可用於只執行記憶體的 CTS 或驗證測試。您可以使用readelf並檢查段標誌來手動驗證二進位。