控制流程完整性

截至 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 由編譯器提供,並在編譯時將偵測新增至二進位檔案。我們在 Clang 工具鏈中支援 CFI,在 AOSP 中支援 Android 建置系統。

預設情況下,對於/platform/build/target/product/cfi-common.mk中的一組元件,Arm64 設備啟用 CFI。它也可以直接在一組媒體元件的 makefiles/blueprint 檔案中啟用,例如/platform/frameworks/av/media/libmedia/Android.bp/platform/frameworks/av/cmds/stagefright/Android.mk

實施系統CFI

如果您使用 Clang 和 Android 建置系統,則預設啟用 CFI。由於 CFI 有助於保護 Android 用戶的安全,因此您不應停用它。

事實上,我們強烈建議您為其他元件啟用 CFI。理想的候選者是特權本機程式碼,或處理不受信任的使用者輸入的本機程式碼。如果您使用 clang 和 Android 建置系統,則可以透過在 makefile 或藍圖檔案中新增幾行來在新元件中啟用 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 不會影響裝置。