RFC-0078:Fuchsia Fuzzing 核心核心涵蓋範圍

RFC-0078:Fernel Sanitizer 的 Fuchsia Fuzzing 涵蓋範圍
狀態已接受
區域
  • 核心
說明

匯出 Fuchsia 核心程式碼涵蓋率,以便與 Syzkaller 進行模糊化。

作者
審查人員
提交日期 (年/月)2021-02-26
審查日期 (年/月)2021-03-25

摘要

這項變更將導入新的 syscall,可收集及轉移核心程式碼涵蓋率資料。系統呼叫只會在「現有」sancov 版本中實作。其他建構變數不會受到影響 (傳回 ZX_ERR_NOT_SUPPORTED 的新系統呼叫)。核心涵蓋範圍的初始用戶端為系統呼叫模糊引擎 Syzkaller。我們已經導入這個提案的概念驗證和單元測試 (請參閱子系變更),以評估其效益。

背景

Syzkaller 是受保護的核心模糊化工具。它會產生一系列 syscall 以測試作業系統,並根據涵蓋率資訊改變這些呼叫,判斷哪些序列很有幫助。Fuchsia 已使用 Syzkaller,但目前的整合並未收集程式碼涵蓋率資料。

在 Fuchsia 中,Syzkaller 會以 HostFuzzer 模式執行,其中模糊化引擎 (syz-fuzzer) 位於模糊化的 VM 之外,並與模糊化代理程式 (syz-executor) 通訊。這類代理程式會執行一連串系統呼叫,並將涵蓋率傳回至引擎。

您可以使用 Clang 的 SanitizerCoverage (sancov) 檢測方式取得核心程式碼涵蓋率。此檢測作業的運作方式是在每個基本區塊上新增對 __sanitizer_cov_trace_pc_guard 的呼叫,然後實作該函式來追蹤造訪過的程式計數器 (PC)。Linux 自 2016 年起便開始支援這項服務,而其實作方式是透過每個執行緒列出涵蓋的電腦。

Zircon 支援同樣套用到 Zircon 核心的 sancov 建構變數,也就是以 sancov 格式 (被命中的電腦稀疏電腦) 匯出即時的 VMO。不過,Syzkaller 會同時執行多個程式,並查看每個系統呼叫的涵蓋範圍,因此需要更詳盡的資訊。

相關規定

實作成功時,必須匯出核心程式碼涵蓋率資料,以便讓模糊引擎使用。目前主要消費者會是 Syzkaller,因此許多需求是由 Syzkaller 的架構與假設設下的。相關規定如下:

  • 執行緒層級精細程度:Syzkaller 的 syz-executor 會使用執行緒集區執行系統呼叫,並針對每個系統呼叫收集涵蓋率,也就是核心在每次系統呼叫情境中執行的程式碼。

    存取的格式是一組命中的程式計數器。每個工作站執行緒的虛擬程式碼如下 (每項工作都是系統呼叫):

Thread:
    Enable Coverage
    While True:
        Job <- wait for job event
        Start Tracking Coverage
        Execute Job
        Collect Coverage
        Signal job done

Syzkaller 導入的策略表示,只需要從由 syz-executor 工作站執行緒啟動的服務系統呼叫互動的核心執行緒,收集核心涵蓋率資訊。

  • 快速:由於每次執行系統呼叫後 syz-executor 會查詢涵蓋範圍資訊,因此收集涵蓋範圍資料並傳送到模糊引擎所需的時間應盡量減少。收集與轉移流程更快速,讓 Syzkaller 在短時間內測試更多程式。

  • 低雜訊:如果程式碼之外沒有涵蓋範圍信號,且測試中的 Syskaller 決定其執行方式,Syzkaller 就能充分發揮效用。完整檢測為 syz-executor 執行緒提供的核心執行緒幾乎就能做到這一點,但雜訊是由排程器程式碼導入,服務中斷也會隨之中斷。如果導入方式成功,應盡可能減少雜訊。

  • 僅供測試:一般版本不應啟用收集和匯出涵蓋範圍的功能。而且應該只屬於使用 sancov 變數的建構作業的一部分。我們不保證涵蓋率收集介面能夠保持穩定,只會用於 Syskaller 等模糊引擎。非 sancov 版本的記憶體用量或執行階段效能不會受到影響。

不在範圍內

最好能滿足下列需求,但這不在 RFC 的涵蓋範圍內。就好像「未來工作」的樣子。

  • 導入更精細的精細程度和/或控制流程追蹤,例如程序層級追蹤或「個人化追蹤」,以追蹤通訊程序的涵蓋範圍。

  • 提供排除核心特定部分涵蓋範圍的機制。這有時與低雜訊的要求可能有些困難,但可合理預期核心廣泛的涵蓋範圍將有足夠的雜訊,在 Syskaller 初始實作中引導 Syskaller。

  • 從在測試中執行系統呼叫的執行緒以外,從核心執行緒收集涵蓋率資料。

設計

現有的 sancov 變數將進一步支援新的系統呼叫,藉此收集及轉移核心程式碼涵蓋率資料。

實作

Syscall (僅適用於 sancov Variant)

我們導入了下列新的 syscall。sancov 變體版本支援這些系統呼叫。

  • coverage_control(uint32_t action):要求核心使用新的緩衝區 (action=KCOV_CTRL_START) 開始收集涵蓋範圍資料,或是停止收集涵蓋範圍資料 (action=KCOV_CTRL_STOP)。

  • coverage_collect(uintptr_t* buf, size_t count, size_t* actual, size_t* avail):要求核心將目前的涵蓋率資料複製到 buf 所參照的使用者空間緩衝區。唯一的選用參數是 avail。作業不會耗用資料;在核心緩衝區由 coverage_control(KCOV_CTRL_START) 重設之前,資料仍可使用。複製到 buf 的位元組數量會儲存在 actual 中,目前可用的位元組總數會儲存在 avail 中 (如果已設定)。如果傳回 ZX_ERR_NO_SPACE,表示核心的涵蓋範圍緩衝區大小不足,涵蓋範圍資料可能會遺失。傳回值旨在提示用戶端,可能需要提高收集頻率,確保不會遺漏任何資料。

請注意,這些系統呼叫只會控制單一執行緒的涵蓋範圍,並對任何全域涵蓋範圍的收集作業沒有任何影響。如此一來,Syzkaller 和其他小麥就能確保他們只收集到他們最關心的零件。

核心記憶體需求 (僅限 Sancov 變化版本)

  • 已啟用涵蓋率功能的每個使用者執行緒,每個 300KiB 緩衝區

  • 指向 ThreadDispatcher 中上述緩衝區的一個指標

  • ThreadDispatcher 中提及緩衝區的項目計數器

啟用執行緒的涵蓋率後,核心會分配足夠的緩衝區,以便收集涵蓋率,直到收集功能停用或重設為止。這個緩衝區大小日後可能會變更;一開始為 300KiB,大約是 Sancov PC 資料表的大小。此記憶體必須始終認可;方法是在核心的根 VMAR 中建立僅限核心的 VmMapping,然後將其儲存在 ThreadDispatcher 中,將 VMAR 控制代碼儲存在 ThreadDispatcher 中。

ThreadDispatcher 會儲存這個緩衝區的指標,以及其包含的項目數量。如果執行緒超過涵蓋率限制,系統就不會註冊新的涵蓋率。coverage_control(KCOV_CTRL_START) 會將計數器重設為零 (並開始收集全新的涵蓋率資料緩衝區),不需花費時間清除緩衝區。如要避免非 sancov 建構作業增加記憶體負擔,這些 ThreadDispatcher 欄位可能會#ifdef 排除。

Sancov 資料收集

__sanitizer_cov_trace_pc_guard 會檢查目前執行中的執行緒,查看是否已啟用緩衝區。如果有,請在清單中附加之前按下的電腦。系統呼叫會在目前的執行緒上執行,因此不會與其他執行緒競爭。在處理系統呼叫時,核心執行緒可能會中斷,但會產生雜訊,但並不是競賽。啟用執行緒的涵蓋率後,系統會保留緩衝區,直到執行緒刪除為止。

__sanitizer_cov_trace_pc_guard 無法在執行期間接受錯誤或例外狀況,因為處理常式可能會再次呼叫 __sanitizer_cov_trace_pc_guard,且可能形成迴圈。為避免錯誤,必須一律承諾用來儲存電腦的記憶體。

重複風險

__sanitizer__* 函式之間可能會有重複性的風險。請謹慎控制再回的做法,確保不會造成問題。請特別注意以下幾點:

  • __sanitizer__* 實作不會直接或間接叫用 __sanitizer__* 函式

  • __sanitizer_* 函式不會取得任何鎖定

因此,唯一的來源是中斷服務,不會造成無限次遞迴或死結。以下將探討因中斷而造成的涵蓋範圍資料雜訊。

涵蓋率資料中的雜訊

在這項設計中,涵蓋率資料中的雜訊來源預期如下:

  • coverage_control(KCOV_CTRL_START) 中的程式碼在緩衝區重設後、傳回前執行

  • 在停止收集前正在執行 coverage_control(KCOV_CTRL_STOP) 中的程式碼

  • 將資料複製到用戶端緩衝區前,系統會執行 coverage_collect 中的程式碼

  • 處理目標使用者執行緒的 Sys 呼叫時中斷執行

這些雜訊來源大多數幾乎都是確定性 (也就是說,相同的程式碼路徑會出現在每批收集到的涵蓋率中)。拒絕清單特定程式碼時,可以使用 sancov 機制來排除部分來源,但這些雜訊來源非常小,且可預測,最初的實作方式並不會降低管理拒絕清單的複雜度。

效能

系統不會變更非 sancov 變數版本的效能。受到變更影響的部署作業只有會執行新的 syscall 的 sancov 變體版本。在這個例子中,預期會有相同的效能,但在執行緒啟用核心涵蓋率後,效能會突然降低,這是因為該執行緒發出的每個系統呼叫都會在核心中執行的每個基本區塊上觸發緩衝區寫入作業。這種效能降低的情況是可接受的,因為只有在執行核心模糊工具引擎以收集這項資料時,才會導入效能降低。

安全性考量

核心碼位址通常屬於非常機密資訊,對攻擊者來說極具價值。藉由「僅」sancov (僅供測試、非正式環境) 變化版本版本中啟用顯示這項資訊的系統呼叫,即可降低在正式生產裝置上外洩這項資訊的風險。

隱私權注意事項

本提案不收集或處理使用者資料。

測試

測試實作程序分為兩個階段:

  1. 單元測試會擷取從 zircon 映像檔擷取的 syscall 位址,並且在數個條件 (例如單一系統呼叫、多個系統呼叫、多個通訊執行緒、執行緒當機等) 下,檢查各種系統呼叫是否存在 (與不存在)。
  2. 整合測試在 VM 上執行,因為使用者空間程式無法取得核心符號資訊。VM 會將涵蓋範圍資料匯出至主機環境,在該主機環境中使用 sancov 程式碼涵蓋率工具,驗證電腦是否屬於預期的核心函式。

除了初始實作外,我們也打算製造單元測試和測試,確保新的 syscall 會在非 Sancov 建構變數中傳回 ZX_ERR_NOT_SUPPORTED

說明文件

新的 syscall 將照常整合至 Zircon 說明文件、警告和建構操作說明,說明系統呼叫與 sancov 建構變數之間的關係。

缺點、替代方案和未知

系統在設計和實作過程中會考慮以下替代方案,但會予以拒絕:

  • 資料格式:替代方案是保留目前的 Sancov 格式,但透過系統呼叫在每個執行緒匯出。雖然這種方法有效,但由於這種模式每次都必須將整個 400KiB PC 資料表複製到使用者空間,因此效率不佳,而單一系統呼叫期間的實際電腦清單數量通常略低 (例如,一個 1 KiB 緩衝區,有 2 個控點會收集 163 台電腦,zx_channel_write 則收集 127 台電腦,約 51,000 台電腦)。zx_channel_read

  • 檢測方法:兩種編譯器檢測涵蓋範圍的替代方案為 Intel 處理器追蹤或 QEMU 檢測。這些替代做法可能可行,但需要耗費大量心力才能進行設定,而且不如 Clang 的 SanitizerCoverage 檢測工具靈活性。

  • API 設計:我們的原始設計是在核心和使用者空間之間共用 VMO,而非使用個別的 cover_collect 方法將涵蓋率資訊複製到使用者空間。不過,我們決定不這麼做,因為 Zircon 團隊不建議這麼做:VMs 不適合在核心和使用者空間之間共用,但的好處是我們不需要將核心的涵蓋率複製到使用者空間。

  • 測試方法:應採用較為昂貴 (但更全面) 的測試方式:測試在主機上執行並啟動 VM。測試會執行一系列系統呼叫,然後竊取 VM 的涵蓋率。Sancov 程式碼涵蓋率工具可用於驗證電腦是否屬於預期的核心函式。

先前的圖片和參考資料