Фаззинг или тестирование мусорными данными
Зачем?
В качестве примера — поисковый запрос в 2ГИС, для которого используется не только сам текстовый ввод, но и параметры пользователя: предпочтения, город, текущий участок карты.
Ручное тестирование подобных вещей из-за количества комбинаций практически нереально.
Фаззинг
Фаззинг — это тестирование случайными входными данными.
Пример из реальной жизни: это Heartbleed из OpenSSL; уязвимость, которая позволяет читать память на сервере или клиенте, которая затронула гигантское количество устройств и серверов и была обнаружена только спустя два года
libFuzzer
Библиотека, которая подходит для тестирования: - парсеров - компрессии - криптографии - регулярных выражений
Нужно понимать, что он хорошо подходит для приложений на C++, т.к. имеет расширяеемое C-API для интеграции и входит в поставку с компилятором Clang. В документации, например, есть пример поиска ошибки в той самой уязвимой версии OpenSSL с помощью libFuzzer.
Фаззинг помогает найти нетривиальные баги в сложно-связанном коде.
Как это устроено?
Тестовый корпус данных -> Вызов функции -> Оценка сценариев -> Мутация данных -> 🔄 -> Артефакты бага
Однако libfuzzer работает с набором байт, а наше приложение чаще всего работает с более сложными структурированными данными, и если на него натравить libfuzzer, то мы получим множество исключений о неправильном формате, но не найдёт реальные баги.
Для того чтобы это исправить, можно воспользоваться C-API и дополнить фаззинг необходимой функциональностью, например использовать более сложные структуры и даже подсовывать тестовые данные из баз. В случае с поиском 2ГИС libFuzzer после тюнинга начал использовать реальные данные организаций, комбинировать и мутировать их, и находить реальные баги в продукте.
Что получилось у 2ГИС
- нашли 10 потенциальных крашей
- внедрили в пайплайн разработки
- оцениваем регрессию (тестовый корпус сохраняется между запусками)
- отслеживать аномально медленные запросы к поиску