消毒殺菌劑

提振精神

消毒工具是用於偵測程式碼中特定類別錯誤的工具。運作原則各有不同,但清理器通常 (但不一定) 會依靠某種編譯時檢測工具,以在執行階段公開錯誤的方式變更程式碼。Fuchsia 會使用各種消毒劑,找出並診斷難以發現的危險錯誤。

消毒殺菌劑會透過建構時的旗標啟用。消毒劑建構作業會持續在 Fuchsia 的持續整合 (CI) 和提交佇列 (CQ) 上執行,並為 Fuchsia C/C++ 和 Rust 開發人員提供服務。

開發人員通常可從消毒劑中受益,且無需採取任何特殊動作,只需在偵測到錯誤時才需要留意消毒劑。但有以下限制。請繼續閱讀,瞭解系統支援哪些消毒劑,以及如何使用。

支援的消毒工具

Fuchsia 目前支援下列消毒工具:

  • AddressSanitizer (ASan) 可偵測超出邊界存取、釋放 / 傳回 / 範圍後使用,以及雙重釋放的例項。
  • LeakSanitizer (LSan) 可偵測記憶體流失。在檢查記憶體外洩時,LeakSanitizer 的運作方式類似於保守式垃圾收集器。任何無法從參照根源 (執行緒堆疊、執行緒登錄、全域變數和執行緒本機變數) 存取的配置,都會視為已洩漏。
  • ThreadSanitizer (TSan) 可偵測資料競爭 (僅限主機)。
  • UndefinedBehaviorSanitizer (UBSan) 會偵測依賴未定義的程式行為的特定問題。

Zircon 核心提供下列消毒工具行為:

  • 實體記憶體管理器 (PMM) 檢查器 (pmm_checker) 會偵測使用已釋放記憶體的錯誤和流浪 DMA。
  • Kernel AddressSanitizer (KASan) 會與 PMM 合作,將 AddressSanitizer 擴充至核心程式碼。
  • Lockdep 是執行階段鎖定驗證工具,可偵測鎖定危險,例如死結。

系統會預設加入下列 C/C++ 編譯選項,以便在執行階段偵測或防止錯誤:

  • -ftrivial-auto-var-init=pattern (請參閱 RFC) 會將自動變數初始化為非零模式,以便公開從未初始化的記憶體讀取相關的錯誤。
  • ShadowCallStackSafeStack 可強化產生的程式碼,防止堆疊溢位。

最後,Fuchsia 會使用 libFuzzersyzkaller 執行涵蓋率導向的模糊測試。在嘗試在執行階段公開程式碼中的錯誤方面,模糊測試器與清理器相似,因此通常會一併使用。與清理器不同的是,模糊測試器會嘗試將正式版程式碼強制執行至可能會公開錯誤的路徑。

支援的設定

在下列設定下,本機版本和 CI/CQ 目前支援清理器:

  • bringup.x64
  • bringup.arm64
  • core.x64
  • core.arm64
  • zbi_tests.x64
  • zbi_tests.arm64

此外,消毒工具也適用於主機工具。

上述所有測試都會在 qemuIntel NUC 上執行 CI/CQ。由於資源和容量問題,我們並未使用消毒劑測試其他平台,但您可以使用以下的建構工作流程,在這些平台上進行本機測試。

針對 //vendor 下定義的設定,Gerrit 和 CI 主控台可能會針對特定已登入使用者顯示其他試做工作。尋找名稱中含有 -asan 的設定。

上述清單中的清理器會套用至 C/C++ 程式碼。此外,LSan 會套用至 Rust 程式碼,用於偵測 Rust 記憶體外洩

排解消毒工具問題

建構

Fuchsia 平台版本 (樹狀結構內)

如要重現消毒劑建構作業,您可以使用建構變化版本啟用消毒劑:

fx set product --variant asan-ubsan --variant host_asan-ubsan

您也可以選擇只對特定二進位檔進行檢測:

fx set product --variant asan-ubsan/executable_name

在硬體上本機測試時,如果裝置不支援完整檢測的版本,則可使用選擇性檢測工作流程。

如要特別偵測核心程式碼中的 use-after-free 錯誤,您必須啟用核心 PMM 檢查器

樹狀結構外建構

使用 Fuchsia 工具鍊編譯時,只要傳遞 -fsanitize= 旗標即可指出要使用的消毒劑。請參閱編譯器說明文件

建立含有檢測元件的 Fuchsia 套件時,您必須確保套件包含所有執行階段依附元件,包括檢測器執行階段 (以 Clang 工具鍊的一部分分發),以及檢測的 C 程式庫 (以 sysroot 底下的 Fuchsia SDK 的一部分分發)。

測試

在本機工作流程或已啟用消毒器的 CQ 建構工具上,按照平常的方式進行測試 (試用工作名稱中含有 asan)。如果清理器偵測到問題,則會將訊息列印到記錄檔中,其中包含下列其中一個字串:

  • ERROR: AddressSanitizer
  • ERROR: LeakSanitizer
  • SUMMARY: UndefinedBehaviorSanitizer
  • WARNING: ThreadSanitizer

根據這些訊息,您會找到堆疊追蹤,可用於找出問題的性質並指出根本原因。您可以在 fx log 中找到這些訊息。

請注意,觸發清理器的測試可能仍會顯示為通過。消毒工具的問題不會顯示為測試失敗。

清理器偵測到的問題通常有類似的根本原因。您可以搜尋 Fuchsia 錯誤,找出與您在消毒器輸出內容中看到的關鍵字相同的錯誤,藉此找到先前工作的參考資料。

已知問題

#[should_panic]

Fuchsia 的 Rust 建構作業會panic! 中中斷。這麼做可大幅縮減二進位檔大小。不幸的是,使用 #[should_panic] 屬性的測試可能會誤判記憶體外洩。這些測試會發出預期的恐慌情況,然後在未解開的情況下退出,也就是說,它們不會釋放堆積分配。對 LeakSanitizer 來說,這與實際的記憶體流失無異。

如果這項問題影響到您的測試,您可以採取以下做法:

  • 請按照這個範例切換至 #[fuchsia::test] 屬性。這是首選屬性,因為 #[fuchsia::test] 只會停用 LeakSanitizer,但會保留其他消毒工具。

  • 如果您無法輕易切換至 #[fuchsia::test],請按照這個範例停用清理器建構中的測試。

請參閱:問題 88496:Rust 測試應觸發 leaksanitizer 的 should_panic

最佳做法

確保程式碼可執行測試

消毒殺菌劑會在執行階段揭露錯誤。除非程式碼已執行 (例如在測試中或在 Fuchsia 上以 CI/CQ 執行的方式執行),否則清理程式無法公開程式碼中的錯誤。

如要確保程式碼的清理器涵蓋率,最佳做法是在相同的設定下確保測試涵蓋率。請參閱測試涵蓋率指南。

請勿在程式碼中抑制清理器

某些建構目標可能會抑制消毒劑。這項功能通常用於在推出清理器支援之前出現的問題,特別是 Fuchsia 專案不擁有的第三方程式碼中的問題。

您應將已抑制的消毒劑視為技術債,因為這類消毒劑不僅會隱藏舊錯誤,還會讓您無法在程式碼中發現新錯誤。理想情況下,不應新增新的抑制條件,應移除現有的抑制條件,並修正底層錯誤。

如要抑制清理器,請編輯定義可執行目標的 BUILD.gn 檔案,如下所示:

executable("please_fix_the_bugs") {
  ...
  # TODO(https://fxbug.dev/42074368): delete the below and fix the memory bug.
  deps += [ "//build/config/sanitizers:suppress-asan-stack-use-after-return" ]
  # TODO(https://fxbug.dev/42074368): delete the below and fix the memory bug.
  deps += [ "//build/config/sanitizers:suppress-asan-container-overflow" ]
  # TODO(https://fxbug.dev/42074368): delete the below and fix the memory leak.
  deps += [ "//build/config/sanitizers:suppress-lsan.DO-NOT-USE-THIS" ]
}

上述範例示範了如何抑制所有消毒劑。不過,您最多只能抑制導致失敗的清理程式。請提交錯誤並在註解中提及,如上所示,以便追蹤抑制條件。

另一種常見的停用清理器方法如下:

executable("too_slow_when_built_with_asan") {
  ...
  exclude_toolchain_tags = [ "asan" ]
}

上述兩個範例都以整個可執行檔的細節來抑制。如要進行更精細的抑制,您可以偵測程式碼中是否有消毒劑。這在特定測試案例中抑制清理器時非常實用,但在更廣泛的情況下則不然。舉例來說,這項功能可用於測試刻意引入記憶體錯誤,以及測試消毒器執行階段本身。

如要瞭解 C/C++,請參閱:

針對 Rust,您可以遵循下列模式:

#[cfg(test)]
mod tests {
    #[test]
    // TODO(https://fxbug.dev/42074368): delete the below and fix the leak
    #[cfg_attr(feature = "variant_asan", ignore)]
    fn test_that_leaks() {
        // ...
    }
}

測試不穩定性

如果測試中的程式碼行為不確定,消毒工具的錯誤可能會不穩定。舉例來說,記憶體耗損可能只會在特定競爭情況下發生。如果清理器錯誤似乎不穩定,請參閱在 CQ 中測試不穩定性的指南。

回報良好的錯誤

遇到消毒工具問題時,請回報錯誤,並附上所有可用的疑難排解資訊。

示例:問題 73214:在 blobfs 中使用 ASAN 後範圍

錯誤報告包含以下內容:

  • 由消毒工具提供的錯誤 (在本例中為 ASan)。
  • 說明如何建構及測試以重現錯誤。
  • 後續調查的詳細資料,並視需要提供特定程式碼指標。
  • 相關變更的參照,例如在本例中,是指修正錯誤的根本原因所做的變更。

發展藍圖

持續性工作:

未來的作業範圍:

  • ThreadSanitizer (TSan):偵測資料競爭。
  • 核心支援功能,可用於偵測並行處理錯誤。
  • 擴充 Rust 的 Sanitizer 支援功能,例如在 Rust unsafe {} 程式碼區塊或跨 FFI 呼叫中偵測記憶體安全錯誤,或偵測未定義的行為錯誤。
  • MemorySanitizer (MSan):偵測未初始化的記憶體讀取作業。

另請參閱:2021 年發展藍圖中的消毒程式