控制流程完整性

自 2016 年起,Android 上約有 86% 的漏洞與記憶體安全相關。攻擊者通常會利用漏洞變更應用程式的正常控制流程,以取得遭利用應用程式的所有權限,並執行任意惡意活動。控制流程完整性 (CFI) 是一種安全機制,可禁止變更已編譯二進位的原始控制流程圖,大幅提高這類攻擊的難度。

在 Android 8.1 中,我們在媒體堆疊中啟用 LLVM 的 CFI 實作方式。在 Android 9 中,我們在更多元件和核心中啟用了 CFI。系統 CFI 預設為開啟,但您需要啟用核心 CFI。

LLVM 的 CFI 需要使用連結時間最佳化 (LTO) 進行編譯。LTO 會保留物件檔案的 LLVM 位元碼表示法,直到連結時間為止,讓編譯器能更妥善地判斷可執行的最佳化作業。啟用 LTO 可縮減最終二進位檔的大小並提升效能,但會增加編譯時間。在 Android 測試中,LTO 和 CFI 的組合對程式碼大小和效能造成的負擔微不足道,在少數情況下,兩者都有所改善。

如要進一步瞭解 CFI 和其他轉送控制項檢查的處理方式,請參閱 LLVM 設計文件

範例和來源

CFI 由編譯器提供,並在編譯期間將檢測功能新增至二進位檔。我們在 AOSP 的 Clang 工具鍊和 Android 建構系統中支援 CFI。

對於 Arm64 裝置,系統會預設為 /platform/build/target/product/cfi-common.mk 中的一組元件啟用 CFI。此外,這項功能也會直接在媒體元件的 makefile/藍圖檔案中啟用,例如 /platform/frameworks/av/media/libmedia/Android.bp/platform/frameworks/av/cmds/stagefright/Android.mk

實作系統 CFI

如果您使用 Clang 和 Android 建構系統,系統預設會啟用 CFI。CFI 有助於保障 Android 使用者安全,因此請勿停用這項功能。

事實上,我們強烈建議您為其他元件啟用 CFI。 理想的候選項目是具備權限的原生程式碼,或是處理不受信任使用者輸入內容的原生程式碼。如果您使用 clang 和 Android 建構系統,只要在 makefiles 或藍圖檔案中新增幾行程式碼,即可在新元件中啟用 CFI。

在 Makefile 中支援 CFI

如要在 make 檔案 (例如 /platform/frameworks/av/cmds/stagefright/Android.mk) 中啟用 CFI,請新增:

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE 會在建構期間將 CFI 指定為清除器。
  • LOCAL_SANITIZE_DIAG 會開啟 CFI 的診斷模式。 診斷模式會在當機期間將額外的偵錯資訊列印到 logcat 中,這在開發及測試建構版本時非常實用。不過,請務必移除正式版建構中的診斷模式。
  • LOCAL_SANITIZE_BLACKLIST 可讓元件選擇性停用個別函式或來源檔案的 CFI 插樁。如果使用者遇到問題,您可以使用黑名單做為最後手段來解決。詳情請參閱「停用 CFI」。

在藍圖檔案中支援 CFI

如要在藍圖檔案 (例如 /platform/frameworks/av/media/libmedia/Android.bp) 中啟用 CFI,請新增:

   sanitize: {
        cfi: true,
        diag: {
            cfi: true,
        },
        blacklist: "cfi_blacklist.txt",
    },

疑難排解

如果您要在新元件中啟用 CFI,可能會遇到函式型別不符錯誤組語程式碼型別不符錯誤

發生函式型別不符錯誤的原因是,CFI 會限制間接呼叫,只允許跳至與呼叫中使用的靜態型別具有相同動態型別的函式。CFI 會限制虛擬和非虛擬成員函式呼叫,只允許跳至用於發出呼叫的物件靜態型別衍生類別物件。也就是說,當您的程式碼違反其中一項假設時,CFI 新增的插樁作業就會中止。舉例來說,堆疊追蹤記錄顯示 SIGABRT,而 logcat 包含控制流程完整性發現不符的行。

如要修正這個問題,請確認呼叫的函式與靜態宣告的函式類型相同。以下是兩個範例 CL:

另一個可能的問題是,嘗試在含有對組語的間接呼叫的程式碼中啟用 CFI。由於組語未輸入,因此會導致型別不符。

如要修正這個問題,請為每個組件呼叫建立原生程式碼包裝函式,並為包裝函式提供與呼叫指標相同的函式簽章。包裝函式隨後可直接呼叫組語。由於 CFI 不會檢測直接分支 (無法在執行階段重新指向,因此不會造成安全風險),這項做法可修正問題。

如果組語函式過多,無法全部修正,您也可以將所有含有組語間接呼叫的函式加入黑名單。不建議這麼做,因為這會停用這些函式的 CFI 檢查,進而擴大攻擊面。

停用 CFI

我們未觀察到任何效能負擔,因此您應該不需要停用 CFI。不過,如果會對使用者造成影響,您可以在編譯時提供清除器黑名單檔案,針對個別函式或來源檔案選擇性停用 CFI。黑名單會指示編譯器在指定位置停用 CFI 插樁。

Android 建構系統支援 Make 和 Soong 的個別元件黑名單,可讓您選擇不接收 CFI 插碼的來源檔案或個別函式。如要進一步瞭解黑名單檔案的格式,請參閱上游 Clang 文件

驗證

目前沒有專為 CFI 設計的 CTS 測試。請改為確認 CTS 測試是否通過 (無論是否啟用 CFI),驗證 CFI 不會影響裝置。