
Нативный фаззинг в Go 1.18
17 февраля 2022 г.Go adds фаззинг как часть инструментов тестирования. Эта функция запланирована в версии 1.18 и уже доступна для бета-тестирования. Давайте посмотрим, для чего он нужен и зачем его добавлять в стандартную библиотеку.
Что такое фаззинг
Согласно Википедии, «фаззинг или нечеткое тестирование — это метод автоматизированного тестирования программного обеспечения, который включает предоставление неверных, неожиданных или случайных данных в качестве входных данных для компьютерной программы». Полезно обнаружить сбои и широкий спектр ошибок, которые часто являются причиной уязвимостей в программном обеспечении. Фаззинг очень эффективен для тестирования программного обеспечения, которое использует структурированные входные данные, такие как специальный формат или протокол.
Вот несколько примеров того, что можно (и нужно) фаззить: криптография, форматы сжатия и сериализации, сетевые протоколы, медиакодеки, библиотеки обработки текста и все, что использует ненадежные входные данные или открыто для Интернета.
Фаззинг — не новая техника. Он существует уже несколько десятилетий и хорошо известен, по крайней мере, при тестировании безопасности. В последние годы фаззинг становится все более популярным. Причиной тому является совокупность следующих факторов:
- CI и автоматизированное тестирование стали стандартом — фаззинг требует времени, а автоматизация — хорошая идея;
- Более доступное процессорное время делает его дешевым в использовании;
- Появление управляемых фаззеров. Это уже не просто наполнение кода случайными данными. Фаззеры умнее и им требуется меньше времени для поиска ошибок.
Вы должны понимать, что фазз-тестирование не заменяет модульное тестирование. Это скорее дополнительная методика тестирования. Подумайте об этом так: модульное тестирование проверяет, работает ли код должным образом, а фаззинг гарантирует, что ваши предположения о входных данных и о том, как с ними работать, верны. И одна из основных причин его успеха в том, что фаззинг беспристрастен; он не страдает предвзятостью подтверждения.
пыхтить
Для Go уже доступно несколько инструментов фаззинга, наиболее известным из которых является go-fuzz. Он ориентирован на покрытие — означает, что фаззер обрабатывает код и анализирует покрытие, пытаясь обнаружить новые строки кода. И это просто в использовании. Итак, давайте проверим это:
1. Напишите fuzz-функцию, здесь я поместил ее в тот же пакет, что и тестируемую функцию.
```иди
func Fuzz(данные []байт) int {
FuzzedFunc (данные)
вернуть 0
2. Установите компоненты go-fuzz и соберите тестовую программу
``` ударить
получить -u github.com/dvyukov/go-fuzz/go-fuzz@latest \
github.com/dvyukov/go-fuzz/go-fuzz-build@latest
идти-fuzz-строить
3. Запустите тестовую программу и подождите
``` ударить
пушистик
11.01.2022 18:53:15 воркеры: 8, корпус: 1 (3с назад), крашеры: 1, перезапуски: 1/0, execs: 0 (0/сек), обложка: 0, аптайм: 3с
11.01.2022 18:53:18 воркеры: 8, корпус: 1 (6с назад), аварийщики: 1, рестарты: 1/0, execs: 0 (0/сек), обложка: 3, аптайм: 6с
11.01.2022 18:53:21 рабочие: 8, корпус: 1 (9с назад), аварийщики: 1, перезапуски: 1/7, исполнители: 14998 (1666/сек), обложка: 3, аптайм: 9с
А теперь ты флудишь. Дайте ему поработать некоторое время и проверьте, увеличивается ли значение сбоев в выходных данных. Это означает, что go-fuzz обнаружил некоторые сбои, и вы можете найти трассировку стека и входные данные для них в каталоге crashers
. Входные данные, доступные позже в двоичном формате и формате строки в кавычках, легче использовать в тестах.
Конечно, с этим есть некоторые нюансы: вы не можете фаззить пакет main; можно предоставить исходный корпус, и он поможет фаззеру с исходными данными; возврат различных значений из функции Fuzz может увеличивать и уменьшать приоритет ввода; если вы FuzzedFunc принимает несколько аргументов, вам может потребоваться разделить переменную данных на необходимое количество аргументов.
Посмотрим, что изменится с фаззингом, доступным в составе стандартной библиотеки.
Фаззинг в Go 1.18
Go добавляет поддержку фаззинга как часть своего тестового пакета, и это [хорошо задокументировано] (https://pkg.go.dev/testing@master#hdr-Fuzzing). Кроме того, есть дополнительная документация с более подробной информацией по теме [здесь] (https://go.dev/doc/fuzz/).
Вот как работает новая поддержка фаззинга:
1. Напишите требуемый код. Теперь это часть тестового пакета, и код должен находиться в тестовом файле (*_test.go
)
```иди
импортировать "тестирование"
func FuzzFuzzedFunc(f *testing.F) {
f.Добавить([]байт("ааа"))
f.Fuzz(func(t *testing.T, data []byte) {
FuzzedFunc (данные)
2. Запустите тест. Я буду запускать его, используя подсказку языка
``` ударить
установить golang.org/dl/gotip@latest
готип скачать
gotip test -fuzz=FuzzFuzzedFunc ./...
fuzz: истекло: 0 с, сбор базового покрытия: 0/1 завершено
фаззинг: истекло: 0 с, сбор базового покрытия: 1/1 завершено, сейчас фаззинг с 8 работниками
Как видите, здесь нет этапа сборки. Просто выполните go test
с флагом -fuzz
и именем fuzz-теста для запуска.
Fuzz-тест — это, по сути, модульный тест, но он принимает тип *testing.F
в качестве аргумента. Функция f.Add
используется для предоставления исходных значений корпуса для теста. Предоставление сложного ввода проще, по сравнению с go-fuzz нет необходимости разбивать массив байтов.
```иди
f.Добавить([]байт("ааа"), правда)
f.Fuzz(func(t *testing.T, data []byte, flag bool) {
FuzzedFunc(данные, флаг)
Тест Fuzz действует как модульный тест, когда go test
запускается без флага -fuzz
. Приведенные ниже тесты эквивалентны.
```иди
func FuzzFuzzedFunc(f *testing.F) {
f.Добавить([]байт("ааа"))
f.Fuzz(func(t *testing.T, data []byte) {
FuzzedFunc (данные)
функция TestFuzzedFunc(t *testing.T) {
семена := [][]байт{
[]байт("ааа"),
для i данные: = семена диапазона {
t.Run(fmt.Sprintf("seed#%d", i), func(t *testing.T) {
FuzzedFunc (данные)
В отличие от go-fuzz, при обнаружении сбоя фаззинг прекращается, а трассировка стека выводится на консоль. Неудачный ввод записывается в файл в каталоге testdata/fuzz/FuzzFuzzedFunc
в каталоге пакета. Значения из этого файла можно напрямую скопировать в код Go. Этот файл, если он будет сохранен, также будет использоваться go test в качестве начального корпуса для фаззинга. Это также означает, что этот тест не будет работать до тех пор, пока вы не исправите код.
Выводы
Вот мои наблюдения:
- Нет необходимости скачивать какие-либо инструменты или что-либо создавать;
- Очень удобный способ предоставления исходного корпуса в виде кода;
- Fuzz-тест, поставляемый с начальным корпусом, можно повторно использовать как обычный модульный тест;
- Поскольку теперь это часть go test, для этого легко создать задание CI — просто клонируйте существующее тестовое задание и добавьте флаг -fuzz.
Единственным недостатком на данный момент является отсутствие непрерывного фаззинга. Но уже есть запрос функции относительно этого.
Фаззинг — важный метод тестирования, который помогает находить ошибки и уязвимости в программном обеспечении, которое работает с произвольными входными данными или доступно в Интернете. Добавление его в стандартную библиотеку значительно снижает входные барьеры и упрощает начало тестирования вашего кода.
Оригинал