Нативный фаззинг в Go 1.18

Нативный фаззинг в 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.

Единственным недостатком на данный момент является отсутствие непрерывного фаззинга. Но уже есть запрос функции относительно этого.


Фаззинг — важный метод тестирования, который помогает находить ошибки и уязвимости в программном обеспечении, которое работает с произвольными входными данными или доступно в Интернете. Добавление его в стандартную библиотеку значительно снижает входные барьеры и упрощает начало тестирования вашего кода.



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE