控制流程驗證工具設計文件¶
目標¶
本文概述了一個外部工具,用於驗證 Clang 的控制流程完整性 (CFI) 機制 (-fsanitize=cfi
) 所實作的保護機制。此工具在提供二進制檔或 DSO 的情況下,應推斷間接控制流程操作是否受到 CFI 保護,並應以人類可讀的形式輸出這些結果。
此工具也應加入 Clang 的持續整合測試框架中,在該框架中,對編譯器的修改可確保 CFI 保護機制仍然存在於最終二進制檔中。
位置¶
此工具將作為 LLVM 工具鏈的一部分存在,並將位於相對於 LLVM trunk 的 “/llvm/tools/llvm-cfi-verify” 目錄中。它將通過兩種方法進行測試
單元測試,用於驗證程式碼區段,位於 “/llvm/unittests/tools/llvm-cfi-verify” 中。
整合測試,位於 “/llvm/tools/clang/test/LLVMCFIVerify” 中。這些整合測試是 clang 作為持續整合框架的一部分,確保識別出減少間接控制流程指令 CFI 覆蓋率的編譯器更新。
背景¶
此工具將通過分析輸出的機器碼,持續驗證 CFI 指令是否在所有間接控制流程周圍正確實作。機器碼的分析非常重要,因為它可以確保鏈接器或編譯器中存在的任何錯誤都不會破壞最終交付二進制檔中的 CFI 保護。
未受保護的間接控制流程指令將被標記以進行人工審查。這些意外的控制流程可能只是在 CFI 的編譯器實作中未被考慮到(例如,用於方便 switch 語句的間接跳轉可能未受到完全保護)。
未來可能可以擴展此工具,以標記不必要的 CFI 指令(例如,圍繞對非多型基底類型的靜態調用的 CFI 指令)。這種類型的指令沒有安全隱患,但可能會帶來效能影響。
設計理念¶
此工具將從二進制檔和 DSO 的機器碼格式反組譯,並分析反組譯的機器碼。該工具將檢查虛擬調用和間接函數調用。此工具還將檢查間接跳轉,因為內聯函數和跳轉表也應受到 CFI 保護。由於位元組碼提供的資訊不足,因此未實作非虛擬調用 (-fsanitize=cfi-nvcall
) 和類型轉換檢查 (-fsanitize=cfi-*cast*
)。
該工具的運作方式是在反組譯碼中搜尋間接控制流程指令。將從圍繞「目標」控制流程指令的一小段指令緩衝區生成控制流程圖。如果目標指令是被分支到的,則分支的 fallthrough 應該是 CFI trap(在 x86 上,這是一個 ud2
指令)。如果目標指令是條件跳轉的 fallthrough(即緊隨其後),則條件跳轉目標應該是 CFI trap。如果間接控制流程指令不符合這些格式之一,則該目標將被標記為 CFI 未受保護。
請注意,在上面概述的第二種情況(其中目標指令是條件跳轉的 fallthrough),如果目標表示一個帶有參數的虛擬調用,則這些參數可能會在分支之後但在目標指令之前被推入堆疊。在這些情況下,會建構一個輔助的「溢出圖」,以確保間接跳轉/調用使用的暫存器參數在過渡期間的任何時候都不會從堆疊中溢出。如果沒有影響目標暫存器的溢出,則該目標被標記為 CFI 受保護。
其他設計注意事項¶
只有標記為可執行的機器碼區段才會受到此分析。非可執行區段不需要分析,因為這些區段中存在的任何執行都已違反控制流程完整性。
稍後可能會進行適當的擴展,以包括跨 DSO 邊界的間接控制流程操作的分析。目前,這些 CFI 功能僅處於實驗階段,ABI 不穩定,因此不適合進行分析。
該工具目前僅支援 x86、x86_64 和 AArch64 架構。