預設情況下,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
將其標示為不可讀。
啟用 XOM
根據預設,建構系統中的所有 64 位元二進位檔都會啟用「僅執行」功能。
停用 XOM
您可以在模組層級、整個子目錄樹狀結構,或整個版本的全球層級停用僅執行權限。
您可以將 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
手動驗證二進位檔,並檢查區段標記。