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

gosentry - форк Go для фаззинга от Trail of Bits

title
gosentry - форк Go для фаззинга от Trail of Bits
type
summary
summary
Кевин Валерио представил gosentry: форк Go с движком LibAFL+Nautilus под API testing.F для фаззинга структур, грамматик, поиска гонок, утечек и переполнений.
tags
golang, fuzzing, security, trail-of-bits
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

В публикации в блоге 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 - фаззинг как уровень защиты перед релизом, особенно для протокольного кода сторонней реализации