AArch64 系統二進製文件的可執行代碼部分默認標記為僅執行(不可讀),作為針對即時代碼重用攻擊的強化緩解措施。將數據和代碼混合在一起的代碼以及有目的地檢查這些部分的代碼(沒有首先將內存段重新映射為可讀)不再起作用。如果應用程序嘗試讀取內存中啟用了僅執行內存 (XOM) 的系統庫的代碼段,而沒有先將該段標記為可讀,則目標 SDK 為 10(API 級別 29 或更高)的應用程序會受到影響。
要充分受益於這種緩解措施,需要硬件和內核支持。如果沒有這種支持,緩解措施可能只能部分實施。 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_XOM
和xom
變量設置為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
手動驗證二進製文件並檢查段標誌。