控制流完整性

截至 2016 年,Android 上大約 86% 的漏洞與內存安全相關。大多數漏洞被攻擊者利用更改應用程序的正常控制流,以使用被利用應用程序的所有權限執行任意惡意活動。控制流完整性(CFI) 是一種安全機制,它不允許對已編譯二進製文件的原始控制流圖進行更改,從而大大提高了執行此類攻擊的難度。

在 Android 8.1 中,我們在媒體堆棧中啟用了 LLVM 的 CFI 實現。在 Android 9 中,我們在更多組件和內核中啟用了 CFI。系統 CFI 默認開啟,但您需要啟用內核 CFI。

LLVM 的 CFI 需要使用Link-Time Optimization (LTO) 進行編譯。 LTO 保留目標文件的 LLVM 位碼表示,直到鏈接時,這允許編譯器更好地推斷可以執行哪些優化。啟用 LTO 會減小最終二進製文件的大小並提高性能,但會增加編譯時間。在 Android 上進行測試時,LTO 和 CFI 的組合對代碼大小和性能的開銷可以忽略不計;在少數情況下,兩者都有所改善。

有關 CFI 以及如何處理其他前向控制檢查的更多技術細節,請參閱LLVM 設計文檔

示例和來源

CFI 由編譯器提供,並在編譯期間將檢測添加到二進製文件中。我們支持 Clang 工具鏈中的 CFI 和 AOSP 中的 Android 構建系統。

對於 /platform/build/target/product/cfi-common.mk 中的組件集,CFI 默認為/platform/build/target/product/cfi-common.mk設備啟用。它還直接在一組媒體組件的 makefile/藍圖文件中啟用,例如/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 文件中啟用 CFI,例如/platform/frameworks/av/cmds/stagefright/Android.mk ,請添加:

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE將 CFI 指定為構建期間的 sanitizer。
  • 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。但是,如果存在面向用戶的影響,您可以通過在編譯時提供 sanitizer 黑名單文件來選擇性地禁用單個函數或源文件的 CFI。黑名單指示編譯器在指定位置禁用 CFI 檢測。

Android 構建系統為 Make 和 Soong 提供了對每個組件黑名單的支持(允許您選擇不會接收 CFI 檢測的源文件或單個函數)。有關黑名單文件格式的更多詳細信息,請參閱上游 Clang 文檔

驗證

目前,沒有專門針對 CFI 的 CTS 測試。相反,請確保在啟用或不啟用 CFI 的情況下通過 CTS 測試,以驗證 CFI 不會影響設備。