Go 測試模糊化
начиная с Go 1.18, Go поддерживает фаззинг в своей стандартной цепочке инструментов. OSS-Fuzz поддерживает нативные тесты Go fuzz.
Попробуйте учебник по тестированию нечеткости с Go.
Обзор
Фаззинг - это тип автоматизированного тестирования, который постоянно изменяет входные данные программы для поиска ошибок. Фаззинг Go использует руководство по охвату, чтобы разумно просматривать фаззинг-код, находить и сообщать об отказах пользователю. Поскольку он может достигать крайних случаев, которые люди часто упускают, фаззинг-тестирование может быть особенно ценным для поиска эксплойтов и уязвимостей безопасности.
Ниже приведен пример фаззинг-теста с выделением его основных компонентов.
Написание тестов нечеткости
Требования
Ниже приведены правила, которым должны следовать фаззинг-тесты.
- Фаззинг-тест должен быть функцией с именем, например
FuzzXxx
, которая принимает только*testing.F
и не возвращает значение. - Для запуска фаззинг-тесты должны находиться в файлах *_test.go.
- 一個 模糊測試目標 必須是對 `
(*testing.F).Fuzz
` 進行的方法呼叫,它接受一個*testing.T
作為第一個參數,後跟模糊測試引數。它沒有傳回值。 - 每個模糊測試必須只有一個模糊測試目標。
- 所有 原始樣本集 條目的類型都必須與 模糊測試引數 完全相同,順序也相同。這適用於對 `
(*testing.F).Add
` 的呼叫,以及模糊測試的 testdata/fuzz 目錄中的任何樣本集檔案。 - 模糊測試引數只能是下列類型:
string
,[]byte
int
,int8
,int16
,int32
/rune
,int64
uint
,uint8
/byte
,uint16
,uint32
,uint64
float32
,float64
bool
建議
以下建議能幫助你充分運用模糊測試。
- 模糊測試目標應快速且確定性,讓模糊測試引擎能有效運作,並且能輕鬆重現新的錯誤和程式碼涵蓋範圍。
- 由於模糊測試目標會在多個工作執行緒中以非決定性順序並行呼叫,因此模糊測試目標的狀態不應持續存在於每次呼叫結束後,而模糊測試目標的行為不應依賴於全域狀態。
執行模糊測試
有兩種執行模糊測試的方式:作為單元測試(預設的 go test
)或搭配模糊測試(go test -fuzz=FuzzTestName
)。
預設情況下,模糊測試的執行方式與單元測試非常類似。每個 原始樣本集 項目會針對模糊測試目標進行測試,並在結束前回報任何失敗。
若要啟用模糊測試,請搭配 -fuzz
旗標執行 go test
,並提供與單一模糊測試相符的正規表示式。預設情況下,該軟體套件中所有其他測試都會在模糊測試開始前執行。這是為了確保模糊測試不會回報現有測試已能發現的問題。
請注意,由你決定要執行模糊測試多久。如果模糊測試沒有發現任何錯誤,則它極有可能無限期地執行。未來會支援使用 OSS-Fuzz 等工具持續執行這些模糊測試,請參閱 問題 #50192。
注意:模糊測試應在支援涵蓋範圍工具的平台上執行(目前為 AMD64 和 ARM64),這樣在執行時,樣本集才能有意義地成長,而且在執行模糊測試時可以涵蓋更多程式碼。
命令列輸出
當 fuzzing 作業正在進行時,fuzzing 引擎 會產生新的輸入並針對所提供的 fuzz target 執行它們。預設情況下,它會持續執行直到找到會失敗的輸入,或直到使用者取消此程序(例如使用 Ctrl^C)。
輸出的外觀會類似於這樣
~ go test -fuzz FuzzFoo
fuzz: elapsed: 0s, gathering baseline coverage: 0/192 completed
fuzz: elapsed: 0s, gathering baseline coverage: 192/192 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 325017 (108336/sec), new interesting: 11 (total: 202)
fuzz: elapsed: 6s, execs: 680218 (118402/sec), new interesting: 12 (total: 203)
fuzz: elapsed: 9s, execs: 1039901 (119895/sec), new interesting: 19 (total: 210)
fuzz: elapsed: 12s, execs: 1386684 (115594/sec), new interesting: 21 (total: 212)
PASS
ok foo 12.692s
第一行表示在開始執行 fuzzing 之前,已收集「基準覆蓋率」。
為了收集基準覆蓋率,fuzzing 引擎會執行種子語料庫和產生的語料庫,以確保沒有發生錯誤,並了解現有語料庫已提供的程式碼覆蓋率。
以下行提供對主動 fuzzing 執行的詳細資訊
- elapsed:從程序開始以來經過的時間
- execs:針對 fuzz target 已執行輸入的總數(加上從上次記錄行以來平均每秒執行的次數)
- new interesting:在此 fuzzing 執行期間已新增到產生的語料庫中的「有趣」輸入的總數(加上整個語料庫的總大小)
要讓輸入「有趣」,它必須擴展超出現有產生的語料庫所能達到的程式碼覆蓋率。通常,新的有趣輸入數量在開始時會快速增加,然後最終會減緩,當發現新的分支時會偶爾激增。
當語料庫中的輸入開始涵蓋更多程式碼行時,您應期待看到「new interesting」的數字隨著時間而減少,如果 fuzzing 引擎找到新的程式碼路徑,則會偶爾激增。
錯誤的輸入
在執行 fuzzing 時可能會因許多原因導致失敗
- 程式碼或測試中發生恐慌。
- fuzz target 呼叫
t.Fail
,直接呼叫或透過使用t.Error
或t.Fatal
等方法呼叫。 - 發生無法復原的錯誤,例如
os.Exit
或堆疊溢位。 - fuzz target 花費太長時間才完成。目前,執行 fuzz target 的逾時時間為 1 秒。這可能會因死結或無限循環,或程式碼中的預期行為而失敗。這是建議您的 fuzz target 執行速度快的其中一個原因。
如果發生錯誤,fuzzing 引擎會嘗試將輸入最小化至最小的可能值且最易於人類判讀,這仍會產生錯誤。如需進行設定,請參閱自訂設定檔部分。
最小化完成後,錯誤訊息會被記錄下來,產出內容會以類似這樣的字串結尾
Failing input written to testdata/fuzz/FuzzFoo/a878c3134fe0404d44eb1e662e5d8d4a24beb05c3d68354903670ff65513ff49
To re-run:
go test -run=FuzzFoo/a878c3134fe0404d44eb1e662e5d8d4a24beb05c3d68354903670ff65513ff49
FAIL
exit status 1
FAIL foo 0.839s
模糊測試引擎將這個 失敗輸入 寫入模糊測試的種子語料庫,現在它會使用 go test
預設執行,當 bug 修復後,此輸入會用作回歸測試。
接下來步驟是診斷問題,修復 bug,透過重新執行 go test
驗證修復狀況,然後提交包含新測試資料檔案、並充當回歸測試的修補程式。
自訂設定
對大多數模糊測試的使用案例來說,預設的 go 指令設定應該都能順利運作。因此,通常在指令列上執行模糊測試的寫法如下
$ go test -fuzz={FuzzTestName}
不過,執行模糊測試時,go
指令確實提供了一些設定。這些設定在 cmd/go
套件文件 中有說明。
重點列出幾個
-fuzztime
:模糊目標在結束執行前的總時間或迭代次數,預設為無限期。-fuzzminimizetime
:在每次最小化嘗試期間會執行模糊目標的時間或迭代次數,預設為 60 秒。在模糊測試時,透過設定-fuzzminimizetime 0
,您可以完全停用最小化。-parallel
:同時執行的模糊測試程序數量,預設值為$GOMAXPROCS
。目前,在模糊測試期間設定 -cpu 沒有任何作用。
語料庫檔案格式
語料庫檔案是以特殊格式編碼。這是 種子語料庫 和 產生的語料庫 共用的格式。
以下是語料庫檔案範例
go test fuzz v1
[]byte("hello\\xbd\\xb2=\\xbc ⌘")
int64(572293)
第一行用於告知模糊測試引擎檔案的編碼版本。雖然目前尚未計畫未來版本的編碼格式,但設計必須支援這種可能性。
其後每一行都是構成語料庫項目的值,如果需要,可以直接複製到 Go 程式碼中。
在上述範例中,我們有一個 []byte
後面接著一個 int64
。這些類型必須完全與模糊測試參數相符,並按順序排列。針對這些類型的模糊目標會如下所示
f.Fuzz(func(*testing.T, []byte, int64) {})
指定您自己的種子語料庫值最簡單的方法是使用 (*testing.F).Add
方法。在上述範例中,會如下所示
f.Add([]byte("hello\\xbd\\xb2=\\xbc ⌘"), int64(572293))
不過,您可能有大型二進位檔案,您可能不希望將它們作為程式碼複製到測試中,而是希望將它們保留為測試資料/模糊/{FuzzTestName} 目錄中的個別種子語料庫項目。可以使用 golang.org/x/tools/cmd/file2fuzz 上的 file2fuzz
工具將這些二進位檔案轉換為以 []byte
編碼的語料庫檔案。
要使用此工具
$ go install golang.org/x/tools/cmd/file2fuzz@latest
$ file2fuzz -h
資源
- 教學課程:
- 試試使用 以 Go 進行模糊測試的教學課程 深入了解新概念。
- 如需較簡短的 Go 模糊測試入門教學課程,請參閱 部落格文章。
- 文件:
- 技術細節:
詞彙表
語料庫項目:模糊測試時可以使用語料庫中的輸入。這可以是特別格式化的檔案,或是呼叫 (*testing.F).Add
。
覆蓋率指導:模糊測試方法,使用程式碼覆蓋率的擴充,來判定哪些語料庫項目值得保留供未來使用。
失敗輸入:失敗輸入是執行於 模糊測試目標 時,會造成錯誤或錯誤的語料庫項目。
模糊測試目標:模糊測試中執行的測試函式的功能,以輸入語料庫和產生的值。透過將函式傳遞給 (*testing.F).Fuzz
來提供給模糊測試。
模糊測試:測試檔案中 func FuzzXxx(*testing.F)
格式函式,可用于模糊測試。
模糊測試:一種持續處理輸入至程式,找出問題,例如錯誤或容易受到程式碼影響的 漏洞,的自動化測試類型。
模糊測試參數:傳遞給模糊測試目標的類型,並由 轉換工具 轉換。
模糊測試引擎:一種管理模糊測試的工具,包括維護語料庫、呼叫轉換工具、辨識新覆蓋率及報告失敗。
產生的語料庫:一種由模糊測試引擎,在模糊測試時維護,來追蹤進度的語料庫。儲存在 $GOCACHE
/fuzz 中。這些項目僅在模糊測試時使用。
轉換工具:模糊測試時使用的工具,在傳遞給模糊測試目標之前,隨機修改語料庫項目。
套件:同一目錄中同時編譯的原始檔案集合。請參閱 Go 語言規格中的 套件區段。
種子語料庫:使用者提供的語料庫,可供模糊測試用於導引模糊引擎。它由模糊測試中 f.Add 呼叫提供的語料庫項目及套件中 testdata/fuzz/{FuzzTestName} 目錄中的檔案所組成。無論是否執行模糊測試,這些項目均會預設執行 go test
。
測試檔案:xxx_test.go 格式的檔案,可能包含測試、基準、範例和模糊測試。
回饋
如果您遇到任何問題或對功能有任何構想,請 提出問題。
針對此項功能的討論及一般回饋,您也可以參與 Gophers Slack 上的 #fuzzing 頻道。