gosentry - форк Go для фаззинга от Trail of Bits
- title
- gosentry - форк Go для фаззинга от Trail of Bits
- type
- summary
- summary
- Кевин Валерио представил gosentry: форк Go с движком LibAFL+Nautilus под API testing.F для фаззинга структур, грамматик, поиска гонок, утечек и переполнений.
- parent
- supply-chain-security
- sources
- gosentry-go-fuzzing-fork
- tags
- golang, fuzzing, security, trail-of-bits
- created
- 2026-05-12
- updated
- 2026-05-12
- lang
- ru
- translation_of
- gosentry-go-fuzzing-fork
- source_updated
- 2026-05-12
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
В публикации в блоге trail-of-bits Кевин Валерио анонсировал gosentry - форк тулчейна Go, призванный закрыть разрыв между встроенным фаззером Go и ожиданиями исследователей безопасности из мира Rust/C/C++. Главная идея - "тот же harness, более мощный движок": пишем func FuzzX(f *testing.F) как обычно, запускаем модифицированный go test -fuzz=… и получаем под капотом LibAFL.
Зачем нужен форк
Команда Go в Trail of Bits постоянно сталкивалась с ограничениями стандартного go test -fuzz:
- Сложные условные переходы создавали ограничения путей, с которыми штатный мутатор не справлялся
- Нет генерации входных данных на основе грамматик (создание структурно валидных входных данных приходилось решать вручную)
- Нет фаззинга структур, срезов, массивов и указателей - на вход принимались только
[]byteили базовые типы, а для всего остального требовалось писать своё кодирование - Целый ряд специфичных для Go классов ошибок оставался незамеченным: переполнения знаковых целых, утечки горутин, состояния гонки, бесконечные циклы
- Минимальная отчётность; для анализа фаззинг-кампаний требовались сторонние инструменты
- Перехват специфичных для приложения условий ошибок требовал правок в исходном коде
Вместо написания нового фаззера с новым API gosentry перехватывает вызов существующего f.Fuzz, собирает архив Go с точками входа в стиле libFuzzer и выполняет его внутри процесса под управлением runner'а на Rust на базе libafl.
Что он добавляет
Обнаружение багов, которые пропускает стандартный Go:
- Вставляемые компилятором проверки переполнения целых чисел; интеграция с go-panikint для отслеживания усечения типов
- Встроенный в Go детектор гонок, подключённый прямо к циклу фаззинга
- Интеграция с goleak для поиска утечек горутин
- Отслеживание таймаутов (выявляет бесконечные циклы)
- Флаг
--panic-on, превращающий определённые строки логов или условия ошибок в падение фаззера - полезно для кодовых баз, где вместо паники пишут в лог
Улучшенные входные данные (structure-aware-fuzzing):
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)
})
}
Фаззер мутирует байты внутри и сам выполняет кодирование и декодирование для произвольных структур - обёртки формата передачи данных вокруг тестируемой функции вручную писать не нужно.
Фаззинг по грамматике (grammar-based-fuzzing) с помощью Nautilus: правила грамматики задаются массивом JSON и служат для генерации входных данных, которые проходят начальные стадии парсинга, а не отваливаются на первом же байте. В примере разбирается грамматика JSON, с помощью которой фаззер генерирует значения вида {"postOfficeBox": <digits>}.
Что удалось найти
Первые кампании, в которых в основном использовался дифференциальный фаззинг на основе грамматик между разными реализациями клиентов, выявили ошибки в рабочей криптоинфраструктуре:
- Optimism / Kona Protocol - обработка неизвестного типа пакета вызывала панику и DoS
- Optimism - расхождение в канале Brotli между реализациями Kona и op-node одной и той же спецификации
- Optimism Stack - парсинг фреймов разошёлся со спецификацией
- Revm - при ошибке обработки депозита не увеличивался nonce, что приводило к несовпадению корня состояния (state root) с другими клиентами Ethereum
Всё это ошибки консенсуса, когда одна реализация принимает то, что другая отвергает. Именно для их поиска и создавался дифференциальный фаззинг с грамматиками, и именно с ними плохо справляется цикл случайных мутаций входных []byte в Go.
Использование
./bin/go test -fuzz=FuzzHarness \
--focus-on-new-code=false \
--catch-races=true \
--catch-leaks=true
Отчёты о покрытии:
go test -fuzz=FuzzTarget --generate-coverage
Интерфейс CLI полностью повторяет стандартный go test -fuzz, добавляя лишь несколько новых флагов: затраты на переход с апстрима Go сведены к минимуму.
Компромиссы, о которых статья умалчивает
Статья носит характер вводного обзора, а не аудита. Некоторые издержки остаются неявными:
- Это форк тулчейна: синхронизация с релизами апстрима Go требует постоянной поддержки, а бинарник для фаззинга не будет побайтово идентичен production-сборке
- Runner на базе LibAFL требует наличия тулчейна Rust в среде сборки
- Инструментация покрытия, детектор гонок и goleak в совокупности создают ощутимый оверхед по производительности: для фаззинга это нормально, но тонкая настройка кампаний усложняется
- Bus factor завязан на Trail of Bits - см. запись в списке отслеживания на gosentry
Связанные страницы
- t-context-go-testing - ещё одна заметка о Go-testing-API в вики, посвящённая управлению временем жизни через
T.Context() - clippy-stricter-config - аналог из мира Rust на тему "стандартные инструменты слишком мягкие, вот более строгая конфигурация"; gosentry - более радикальное проявление того же подхода (форк вместо настройки конфигурации)
- supply-chain-security - фаззинг как уровень защиты перед релизом, особенно для протокольного кода сторонней реализации