控制流程驗證工具設計文件¶
目標¶
本文件概述了一個外部工具,用於驗證由 Clang 的「控制流程完整性」(CFI) 方案 (-fsanitize=cfi
) 所實作的保護機制。這個工具在提供二進制檔案或 DSO 的情況下,應推斷間接控制流程操作是否受到 CFI 的保護,並以人類可讀的形式輸出這些結果。
這個工具也應該作為 Clang 持續整合測試框架的一部分加入,其中編譯器的修改應確保最終二進制檔案中仍然存在 CFI 保護方案。
位置¶
這個工具將作為 LLVM 工具鏈的一部分存在,並位於相對於 LLVM 主幹的「/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*
) 由於位元組碼提供的資訊不足而未實作。
此工具會透過搜尋反組譯程式碼中的間接控制流程指令來運作。系統會從「目標」控制流程指令周圍一小段指令緩衝區產生控制流程圖。如果目標指令是被分支到的,則分支的穿透應為 CFI 陷阱(在 x86 上,這是 ud2
指令)。如果目標指令是條件跳轉的穿透(亦即緊隨其後),則條件跳轉目標應為 CFI 陷阱。如果間接控制流程指令不符合這些格式之一,則目標將被標記為未受 CFI 保護。
請注意,在上述第二種情況下(目標指令是條件跳轉的穿透),如果目標表示採用參數的 vcall,則這些參數可能會在分支之後但在目標指令之前被推送到堆疊。在這些情況下,會建構輔助「溢出圖」,以確保間接跳轉/呼叫使用的暫存器參數在中間過程中不會從堆疊中溢出。如果沒有影響目標暫存器的溢出,則目標會被標記為受 CFI 保護。
其他設計注意事項¶
只有標記為可執行的機器碼區段才會進行此分析。不可執行的區段不需要分析,因為這些區段中存在的任何執行都已違反控制流程完整性。
日後可能會進行適當的擴展,以納入針對跨 DSO 邊界的間接控制流程操作的分析。目前,這些 CFI 功能僅為實驗性質,ABI 不穩定,因此不適合用於分析。
此工具目前僅支援 x86、x86_64 和 AArch64 架構。