EnglishРусский Map
Supply Chain Security

Structure-aware фаззинг

title
Structure-aware фаззинг
type
concept
summary
Фаззинг функций через генерацию валидных типов языка (структур, срезов, указателей) вместо сырых байтов; среда сама кодирует и декодирует значения
tags
fuzzing, security, testing
created
2026-05-12
updated
2026-05-12
lang
ru
source_updated
2026-05-12
translated
2026-09-01
translator
lllm/antigravity/gemini-3.7-flash-medium

Интерфейс большинства фаззеров с контролем покрытия, принимающий сырые байты (libFuzzer'овский LLVMFuzzerTestOneInput(const uint8_t*, size_t), Go'шный f.Fuzz(func(t *testing.T, b []byte))), плохо подходит, когда целью является внутрипроцессный API, принимающий структуру. Разработчику приходится писать обёртку для wire-формата, декодирующую []byte в структуру, каждое поле становится источником потенциальных багов, а фаззер тратит мутации на генерацию входов, которые вообще не декодируются.

Structure-aware фаззинг позволяет тестовому harness'у объявить: "вход имеет вот такой тип". Внутри фаззер по-прежнему мутирует байты, но среда выполнения преобразует эти байты в валидное значение объявленного типа перед подачей на вход. С точки зрения целевой функции входные данные изначально типизированы.

type Input struct {
    Data []byte
    S    string
    N    int
}

func FuzzStructInput(f *testing.F) {
    f.Add(Input{Data: []byte("hello"), S: "world", N: 42})
    f.Fuzz(func(t *testing.T, in Input) {
        Process(in)
    })
}

Под капотом фаззер всё так же работает с байтами - корпус, мутаторы, обратная связь по покрытию оперируют байтами, - но промежуточный слой делает так, что одна случайная мутация означает "добавить элемент в Data", "изменить N на один бит" или "поменять местами два символа в S", смотря на какие байты она пришлась.

Ограничения стандартного фаззинга в Go

Фаззер из стандартной библиотеки Go - базовый случай: f.Fuzz принимает только callback'и с примитивными типами ([]byte, string, целочисленные типы, bool, типы с плавающей точкой). Всё составное - структура, срез структур, указатель - требует ручного декодирования прямо в теле harness'а. gosentry обобщает этот подход на произвольные типы с помощью runner'а LibAFL.

Аналоги в Rust и C++

Crate arbitrary (Rust) и FuzzedDataProvider (libFuzzer в C++) реализуют ту же идею: небольшая библиотека принимает буфер байтов и выдаёт типизированное значение - достаточно детерминированно, чтобы мутации фаззера транслировались через него. cargo-fuzz встраивает arbitrary в макрос fuzz_target! так же, как gosentry встраивает свой декодер в f.Fuzz.

Когда выбирать его вместо грамматик

grammar-based-fuzzing подходит тогда, когда на входе ожидаются байты из сети или потока (сериализованный RPC-фрейм, документ JSON, исходный код на каком-то языке): грамматика описывает байты. Structure-aware фаззинг нужен, когда на входе сразу передаётся значение для API - Go-структура во внутреннюю функцию, enum в Rust, объект в C++. Они отлично сочетаются: грамматика генерирует байты, которые парсятся в структуру, а structure-aware фаззинг напрямую мутирует поля этой структуры, проверяя код, выполняющийся после парсинга.