RFC-0078:Fchsia Fuzzing 的核心 Sanitizer 涵蓋範圍 | |
---|---|
狀態 | 已接受 |
領域 |
|
說明 | 匯出 Fuchsia 核心程式碼涵蓋率,以便與 Syzkaller 搭配使用。 |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2021-02-26 |
審查日期 (年-月-日) | 2021-03-25 |
摘要
這項變更將導入新的 sys 呼叫,可收集及轉移核心程式碼涵蓋率資料。系統呼叫只會在現有的 sancov 版本上實作。其他建構變數不會受到影響 (含有傳回 ZX_ERR_NOT_SUPPORTED
的新 sys 呼叫)。核心涵蓋率的初始用戶端是系統呼叫模糊引擎 Syzkaller。我們已針對本提案導入概念驗證,搭配單元測試 (請參閱子系異動) 來評估功效。
背景
Syzkaller 是防護核心模糊元件,它會產生一系列用來測試作業系統的系統呼叫,並根據涵蓋範圍資訊來修改作業系統,並判斷哪些序列相當實用。Syzkaller 已用於 Fuchsia,但目前的整合不會收集程式碼涵蓋率資料。
在 Fuchsia 中,Syzkaller 會以 HostFuzzer 模式執行,在此模式下模糊引擎 (syz-fuzzer
) 就位於模糊的 VM 之外,並與模糊代理程式 (syz-executor
) 通訊,該代理程式會執行一連串的系統呼叫,並將涵蓋率傳回引擎。
您可以使用 Clang 的 SanitizerCoverage (sancov) 檢測工具取得核心程式碼涵蓋率。這項檢測作業的運作方式是在每個基本區塊中新增對 __sanitizer_cov_trace_pc_guard
的呼叫,然後實作該函式來追蹤造訪過的程式計數器 (PC)。Linux 自 2016 年起就開始支援這個 API,其實作方式則是保留所涵蓋電腦的每個執行緒清單。
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 能在相同的時間內測試更多程式。低雜訊:如果測試中的 Syzkaller 程式碼在程式碼外沒有任何涵蓋率信號,就能以最有效的方式執行。對服務
syz-executor
執行緒的核心執行緒幾乎可以達到這個目的,但排程器程式碼會導入雜訊,並造成服務中斷。成功的實作應盡可能降低雜訊。僅供測試:不應在一般版本中啟用收集和匯出涵蓋範圍的功能。這些 API 應該只出現在使用 sancov 變數的建構中。涵蓋率收集介面無法保證穩定;而這個設計目的在於讓測試用的引擎 (例如 Syskaller) 使用。非 sancov 版本的記憶體用量或執行階段效能不會受到影響。
不在範圍內
滿足以下需求會是很好,但這些規定不在此 RFC 的範圍內。您不妨把它們想成是「未來作品」。
導入更精細的分類和/或控制流程追蹤,例如程序層級追蹤或「打擊」追蹤整體溝通過程的涵蓋範圍
提供排除核心特定部分的涵蓋率收集機制。這有點不足為雜訊低的要求,但可以預期全核心的涵蓋範圍將足夠低雜訊,以便在初始實作中引導 Syskaller。
從執行測試中系統呼叫的執行緒以外的核心執行緒收集涵蓋率資料。
設計
現有的 sancov 變體將延伸以支援新的 sys 呼叫,可收集和轉移核心程式碼覆蓋資料資料。
實作
Syscalls (僅對 sancov 變數啟用)
下列為新的 syscalls 進行建構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 中上述緩衝區的一個計數器項目數量
為執行緒啟用涵蓋率後,核心會分配夠大的緩衝區來收集涵蓋率,直到集合停用或重設為止。這個緩衝區的大小日後可能會改變。初始容量為 300 KiB,大約是 sancov PC 表格的大小。這個記憶體必須持續承諾做法是在核心的根層級 VMAR 中建立僅限核心的 VmMapping (與建立 kstacks 的方式類似),並將 VMAR 控制代碼儲存在 ThreadDispatcher 中。
ThreadDispatcher 會儲存這個緩衝區的指標和其擁有的項目計數。如果執行緒超過涵蓋率上限,系統就不會登錄新的涵蓋率。coverage_control(KCOV_CTRL_START)
會將計數器重設為零,並開始收集新的覆蓋範圍資料。因此您不必花時間清除緩衝區。為避免在非 sancov 建構作業中增加記憶體負擔,您可以排除這些 #ifdef
這些 ThreadDispatcher 欄位。
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
中已執行的程式碼為目標使用者執行緒的系統呼叫提供服務時,系統會中斷執行
這些雜訊來源大多幾乎都是確定性的 (也就是說,相同的程式碼路徑會出現在所有收集到的涵蓋率中)。有些來源可以透過 sancov 機制來拒絕將特定程式碼列入拒絕清單,但這些雜訊來源相當小,且可預測,初始實作並不會管理拒絕清單的複雜作業。
成效
系統不會變更非 sancov 變化版本版本的效能。受變更影響的唯一部署作業是執行新 syscall 的 sancov 變化版本建構。這裡的預期是,當執行緒啟用核心涵蓋率時,效能會保持不變,但會突然降低,因為該執行緒每次在核心中執行的每個基本區塊都會觸發緩衝區寫入作業。這種效能降級是可接受的,因為只有在執行核心模糊引擎來收集這項資料時,才會出現這種降低情況。
安全性考量
一般而言,核心代碼地址屬於非常敏感的資訊,對攻擊者來說非常實用。系統「僅」在 sancov (僅限測試、非實際工作環境) 變化版本中啟用 syscalls,藉此降低在正式版裝置上洩漏這項資訊的風險。
隱私權注意事項
本提案不會收集或處理使用者資料。
測試
測試分為兩個階段實施:
- 單元測試:擷取從 Zircon 映像檔擷取的 Syscall 位址,並檢查在多種情況下 (例如單一系統呼叫、多次系統呼叫、多個通訊執行緒、執行緒當機等) 的各種系統呼叫是否存在 (但不存在)
- 整合測試會在 VM 上執行,因為使用者空間程式無法取得核心符號資訊。VM 會將涵蓋率資料匯出至主機環境,在這個環境中使用 Sancov 程式碼涵蓋率工具驗證電腦是否屬於預期核心功能。
除了初始實作外,這項計畫還打算制定單元測試和測試,確保新的 syscall 在非 Sancov 建構變數中傳回 ZX_ERR_NOT_SUPPORTED
。
說明文件
新的系統呼叫會以正常的方式整合到 Zircon 說明文件,也會附上註意事項和建構操作說明,說明系統呼叫與 sancov 建構變數之間的關係。
缺點、替代方案和未知
在這項設計與實作中,下列替代項目已考慮但遭到拒絕:
資料格式:替代方式之一是保留目前的 Sancov 格式,但透過 syscall 為每個執行緒匯出格式。雖然這種做法雖然可行,但效率並不高,因為必須每次都將整個 400 KiB 的 PC 資料表複製到使用者空間,但電腦在單一系統呼叫期間所命中的實際清單通常少上許多 (例如,具有 2 個控制點的 1KiB 緩衝區的
zx_channel_read
收集 163 台電腦,而zx_channel_write
會收集 127 pcs 的 127 片,與 約 51k 片的解析度)。檢測方法:搭配編譯器檢測服務的替代方案,是 Intel Processor Trace 或 QEMU 檢測。這些替代方法或許可以採用,但需要耗費大量心力才能設定完成,而且效果不如 Clang 的 Sanitizer 涵蓋範圍檢測設備不如預期。
API 設計:這次原始設計包括在核心和使用者空間之間共用 VMO,而不是使用單獨的 bar_collect 方法,將涵蓋率資訊複製到使用者空間。不過,由於 Zircon 團隊不建議這麼做,因此決定不這麼做:vmos 禁止在核心和使用者空間之間共用,但這樣做的好處是,我們不會將核心的涵蓋範圍從核心複製到使用者空間。
測試方法:考慮到比較昂貴 (但可能更完整的) 測試方式:測試會在主機上執行並啟動 VM。測試會執行一連串的系統呼叫,然後從 VM 外滲透,並使用 Sancov 程式碼涵蓋率工具驗證電腦是否屬於預期的核心功能。