Кристофер Кринсли, автор блога nullprogram, за шесть лет проверил фаззингом примерно тысячу проектов и собрал рекомендации для работы с AFL++. Его советы относятся к программам на C и C++ с доступным исходным кодом в Linux на базе glibc. По его опыту, даже уже проверенные цели могут раскрыть новые дефекты, если изменить настройку тестов.
Первый шаг — включить AddressSanitizer и UndefinedBehaviorSanitizer вместе с дополнительными проверками. Автор рекомендует настроить ASan на немедленную остановку при ошибке, обнаружение use-after-return и заполнение шаблоном практически всех новых участков кучи, а не только первых 4 КиБ. Для C++ с libstdc++ полезен режим _GLIBCXX_DEBUG: он добавляет проверки контейнеров, хотя меняет ABI.
По возможности стоит использовать persistent mode, который обычно ускоряет работу примерно на порядок. Вместо сложной системы сборки Кринсли предлагает включать нужные исходные файлы прямо в один тестовый translation unit. Так проще проверять внутренние и static-функции, очищать состояние между попытками и подменять мешающие зависимости, не редактируя исходную программу.
Каждый тест должен быть независим от предыдущих. Для этого необходимо сбрасывать глобальные переменные и внутреннее состояние библиотек, инициализировать выходные буферы и учитывать влияние адресов памяти на воспроизводимость. Неинициализированная память особенно опасна в persistent mode: она может сохранять данные прошлых итераций и порождать трудно повторяемые сбои.
Отдельная частая ошибка — передавать целевой функции непосредственно буфер AFL++. Его реальный размер больше длины тестового входа, поэтому ASan может не заметить чтение за пределами логических данных. Нужно создавать копию точного размера и при необходимости добавлять нулевой байт.
Освобождение памяти в конце каждой итерации обычно излишне, если только сам деструктор не является объектом проверки. Для интерфейсов, принимающих только путь к файлу, автор советует memfd_create и путь в /proc: данные остаются в памяти, а параллельные процессы не мешают друг другу.
Наконец, чрезмерно большие внутренние буферы лучше временно уменьшить. Это ускоряет достижение граничных условий и помогает быстрее находить ошибки, которые при обычных размерах требовали бы слишком длинных входов.