RFC-0078:Fuchsia 模糊測試的核心安全性監控涵蓋率 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 匯出 Fuchsia 核心程式碼涵蓋率,以便搭配 Syzkaller 進行模糊測試。 |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2021-02-26 |
審查日期 (年-月-日) | 2021-03-25 |
摘要
這項變更會引入新的系統呼叫,可用於收集及傳輸核心程式碼涵蓋率資料。系統呼叫僅會在現有 sancov 版本中實作。其他版本變化版本不會受到影響 (新的系統呼叫會傳回 ZX_ERR_NOT_SUPPORTED
)。核心涵蓋範圍的初始用戶端是系統呼叫模糊測試引擎 Syzkaller。我們已實作此提案的概念驗證,並搭配單元測試 (請參閱後代變更) 來評估其效能。
背景
Syzkaller 是一種涵蓋率導向的核心模糊測試工具。它會產生系統呼叫序列來測試作業系統,並依據涵蓋率資訊來變異,並判斷哪些序列有用。Syzkaller 已在 Fuchsia 中使用,但目前的整合作業不會收集程式碼涵蓋率資料。
在 Fuchsia 上,Syzkaller 會在 HostFuzzer 模式下執行,其中模糊處理引擎 (syz-fuzzer
) 位於模糊處理 VM 外,並與執行一系列系統呼叫並將涵蓋率傳回引擎的模糊處理代理程式 (syz-executor
) 進行通訊。
您可以使用 Clang 的 SanitizerCoverage (sancov) 檢測功能取得核心程式碼涵蓋率。這項檢測功能會在每個基本區塊上新增對 __sanitizer_cov_trace_pc_guard
的呼叫,然後實作該函式,以追蹤已造訪的程式計數器 (PC)。Linux 自 2016 年起就支援此功能,其實作方式是保留涵蓋的電腦的每個執行緒清單。
Zircon 支援 sancov 建構變化版本,也適用於 Zircon 核心,可匯出以 sancov 格式 (包含遭遇的 PC 的稀疏表格) 的即時 VMO。不過,Syzkaller 會同時執行多個程式,並查看個別系統呼叫的涵蓋率,因此我們需要更精細的資訊。
需求條件
成功的實作方式必須以對模糊測試引擎有用的格式,匯出核心程式碼涵蓋率資料。目前的主要使用者是 Syzkaller,因此許多要求都受到 Syzkaller 的架構和假設限制。相關規定如下:
執行緒層級精細程度:Syzkaller 的
syz-executor
會使用執行緒集區執行系統呼叫,收集每個個別系統呼叫的涵蓋率,也就是在每個系統呼叫的內容中由核心執行的程式碼。其使用的格式是一組命中的程式計數器。每個 worker 執行緒的擬似程式碼如下 (每個工作都是系統呼叫):
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
執行緒的核心執行緒進行完整檢測,幾乎可以達到這個目的,但排程器程式碼和服務中斷會造成雜訊。成功導入功能時,應盡可能減少噪音。僅限測試:請勿在一般版本中啟用用於收集及匯出涵蓋率的功能。這些檔案應僅包含在使用 sancov 變數的版本中。覆蓋率收集介面不保證穩定性;它只適用於 Syskaller 等模糊測試引擎。對於非 sancov 版本,記憶體用量或執行階段效能不得受到任何影響。
不在範圍內
雖然我們希望能提供下列規定,但這超出本 RFC 的範圍。您可以將這些內容視為「未來工作」。
實作更精細的細節和/或控制流程追蹤功能,例如程序層級追蹤或「接力棒傳遞」,以便追蹤通訊程序的涵蓋率。
提供機制,可排除在核心的特定部分收集涵蓋率。這與低雜訊要求有些矛盾,但我們認為,核心層級的涵蓋率會降低雜訊,讓 Syskaller 在初期實作時能有足夠的參考依據。
從執行系統呼叫的執行緒以外的核心執行緒收集涵蓋率資料。
設計
現有的 sancov 變化版本將擴充支援新的系統呼叫,以便收集及傳輸核心程式碼涵蓋率資料。
實作
Syscalls (僅在 sancov 變化版本中啟用)
以下是新引入的系統呼叫。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 (類似於建立 kstack 的方式),並在 ThreadDispatcher 中儲存 VMAR 句柄。
ThreadDispatcher 會儲存指向此緩衝區的指標,以及該緩衝區的項目計數。如果執行緒超出涵蓋範圍限制,就不會註冊新的涵蓋範圍。coverage_control(KCOV_CTRL_START)
會將計數器重設為零 (並開始收集新的涵蓋率資料緩衝區),因此您不必花時間清除緩衝區。為避免在非 sancov 版本中增加記憶體開銷,這些 ThreadDispatcher 欄位可能會被 #ifdef
排除。
Sancov 資料收集
__sanitizer_cov_trace_pc_guard
會檢查目前執行中的執行緒,查看是否已啟用緩衝區,如果已啟用,就會將命中的 PC 附加至清單。系統呼叫會在目前執行緒上運作,因此不會與其他執行緒競爭。核心執行緒在處理系統呼叫時可能會遭到中斷,這會產生雜訊,但不會造成競爭狀態。為執行緒啟用涵蓋率後,緩衝區會持續分配,直到執行緒遭到銷毀為止。
__sanitizer_cov_trace_pc_guard
在執行期間無法處理錯誤或例外狀況,因為處理常式可能會再次呼叫 __sanitizer_cov_trace_pc_guard
,而 __sanitizer_cov_trace_pc_guard
可能會進入迴圈。為避免錯誤,用於儲存 PC 的記憶體必須一律提交。
重入風險
__sanitizer__*
函式之間可能會發生重入問題。我們會小心控制重入來源,確保不會造成問題。請特別注意以下幾點:
__sanitizer__*
實作項目不會直接或間接叫用__sanitizer__*
函式__sanitizer_*
函式不會取得任何鎖定
因此,唯一會造成重入的來源是不會導致無限遞迴或死結的中斷。下文將說明中斷所造成的覆蓋率資料雜訊。
涵蓋率資料中的雜訊
在這個設計的背景下,預期涵蓋率資料會受到下列雜訊來源的影響:
coverage_control(KCOV_CTRL_START)
中的程式碼會在緩衝區重設後,但在傳回之前執行在停止收集資料前執行的
coverage_control(KCOV_CTRL_STOP)
程式碼在資料完成複製至用戶端緩衝區之前,在
coverage_collect
中執行的程式碼在服務目標使用者執行緒的系統呼叫時執行的中斷
這些雜訊來源大多是可預測的 (也就是說,每個收集到的涵蓋率批次都會出現相同的程式碼路徑)。您可以使用 sancov 機制排除某些來源,以便將特定程式碼列入拒絕清單,但這些雜訊來源相當少,而且可預測,因此初始實作不會涉及管理拒絕清單的複雜作業。
成效
sancov 變化版本的效能不會有所變動。這項變更只會影響執行新系統呼叫的 sancov 變化版本建構作業。在這種情況下,預期效能會維持不變,但在執行緒啟用核心涵蓋率時會突然降低,這是因為執行緒發出的每個系統呼叫都會觸發在核心中執行的每個基本區塊上的緩衝區寫入作業。這項效能降幅是可以接受的,因為只有在為了收集這類資料而執行核心模糊測試引擎時,才會發生。
安全性考量
核心程式碼位址通常被視為非常敏感的資訊,對攻擊者而言極具價值。您可以啟用在 sancov (僅限測試,非實際工作環境) 變數版本中顯示這類資訊的系統呼叫,僅在實際工作環境裝置上降低資訊外洩的風險。
隱私權注意事項
這項提案不涉及收集或處理使用者資料。
測試
測試分為兩個階段:
- 單元測試會擷取從 Zircon 映像檔擷取的系統呼叫位址,並在多種條件下 (例如單一系統呼叫、多個系統呼叫、多個通訊執行緒、執行緒停止運作等) 檢查各種系統呼叫的存在 (或不存在)。
- 由於使用者空間程式無法使用核心符號資訊,因此整合測試會在 VM 上執行。虛擬機器會將涵蓋率資料匯出至主機環境,並使用 sancov 程式碼涵蓋率工具,驗證 PC 是否屬於預期的核心功能。
除了初步實作之外,我們也打算推出單元測試,並進行測試,確保新系統呼叫在非 sancov 建構變化版本中會傳回 ZX_ERR_NOT_SUPPORTED
。
說明文件
新的系統呼叫將以常規方式整合至 Zircon 說明文件,並附上警告和建構指示,說明系統呼叫和 sancov 建構變化版本之間的關係。
缺點、替代方案和未知事項
我們考慮了以下替代方案,但在設計和實作過程中予以拒絕:
資料格式:另一個替代做法是保留目前的 Sancov 格式,但透過系統呼叫為每個執行緒匯出格式。雖然這麼做可行,但效率不彰,因為每次都必須將整個 400KiB 的 PC 表格複製到使用者空間,但在單一系統呼叫期間命中的實際 PC 清單通常會少得多 (例如,具有 2 個句柄的 1KiB 緩衝區的
zx_channel_read
會收集 163 個 PC,而zx_channel_write
會收集 127 個 PC,而非總計約 51k 個 PC)。檢測方法:Intel Processor Trace 或 QEMU 檢測是兩種編譯器檢測涵蓋率的替代方法。這些替代方案或許可行,但設定起來相當費力,而且不如 Clang 的 SanitizerCoverage 檢測工具靈活。
API 設計:我們原本的設計是在核心和使用者空間之間共用 VMO,而非使用獨立的 cover_collect 方法將涵蓋率資訊複製到使用者空間。不過,我們決定不採用這個做法,因為 Zircon 團隊不建議這麼做:vmos 不應在核心和使用者空間之間共用,但這樣做的好處是,我們不需要將涵蓋範圍從核心複製到使用者空間。
測試方法:我們考慮採用較昂貴 (但可能更徹底) 的測試方法:在主機上執行測試,並啟動 VM。測試會執行一系列系統呼叫,然後從 VM 中提取涵蓋率,並使用 sancov 程式碼涵蓋率工具,驗證 PC 是否屬於預期的核心函式。