# Fuzzing

Fuzzing это техника тестирования, заключающаяся в передаче приложению на вход неправильных, неожиданных или случайных данных, совокупность которых называют корпусом. Предметом интереса являются падения и зависания, нарушения внутренней логики и проверок в коде приложения, утечки памяти, вызванные такими данными на входе.

## FUZZ

Для того, чтобы начать пользоваться фаззингом в Аркадии, нужно создать `FUZZ` модуль с реализацией функции `LLVMFuzzerTestOneInput` [пример](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/string_utils/base64/fuzz/lib/main.cpp?rev=r8091408).
Для эффективного фаззинга целевая функция должна быть быстрой и покрывать конкретную небольшую функциональность, которая будет фаззиться.

В Аркадии поддержаны `AFL` и `libFuzzer` поверх единого интерфейса, но автоматический фаззинг пока работает только через `libFuzzer`.

### Сборка с libFuzzer {#build-libfuzzer}

Для сборки `FUZZ` модуля с [libFuzzer](http://llvm.org/docs/LibFuzzer.html) достаточно дополнительно указать санитайзер и подсчёт sanitize покрытия - для того чтобы fuzzer понимал, смогли ли текущие входные данные привести к попаданию в новую трассу выполнения:

``` bash
ya make -A --sanitize=address --sanitize-coverage=trace-div,trace-gep
```

На выходе получается исполняемый файл, который и есть драйвер.
Тип санитайзера и тип покрытия можно менять, подробнее можно почитать в [документации llvm](https://clang.llvm.org/docs/SanitizerCoverage.html).

### Сборка c AFL {#build-afl}

Для сборки `FUZZ` модуля с [AFL](http://lcamtuf.coredump.cx/afl/) нужно запускать сборку с sanitize-coverage=trace-pc

``` bash
ya make --afl --sanitize=undefined --sanitize-coverage=trace-pc%%
```

В отличие от libFuzzer, для AFL нужен внешний драйвер - afl-fuzz:

``` bash
ya tool afl-fuzz -i INPUT -o OUTPUT -- /path/to/binary%%
```

## Concept {#concept}

Мы разделяем fuzzing и прогон корпуса - fuzzy test.

Fuzzing - процесс поиска примеров для расширения корпуса. Корпус состоит из двух частей - пользовательской и автоматически сгенерированной.
Пользовательские примеры должны лежать в директории `corpus`, рядом с `ya.make`, в виде отдельных файлов (имена не имеют значения). Эти примеры должны быть малочисленны и составлены вручную, они помогут fuzzer в поиске новых интересных случаев.

Автоматически сгенерированные данные сохраняются в виде Sandbox ресурсов и прикрепляются к проектам через специальный файл `corpus.json` в директории проекта в [arcadia/fuzzing](https://a.yandex-team.ru/arc/trunk/arcadia/fuzzing/). Это поведение можно отключить с помощью опции `--fuzz-local-store`, намайненные данные будут находится в `<project_path>/test-results/fuzz/<binname>/mined_corpus.tar`.

Пример локального запуска фаззинга (расширения корпуса):

``` bash
ya make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A --fuzzing
```

Fuzzy тест - это только прогон имеющегося корпуса (пользовательского и автоматического).
Fuzzy тесты запускаются автоматически в Автосборке CI на все релевантные изменения в репозитории, которые влияют на сборку `FUZZ` модуля.
Это позволяет проверять вносимые изменения на предмет ошибок/утечек на основе корпуса.

Пример локального запуска fuzzy теста:

``` bash
ya make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A
```

## Еженощный fuzzing {#nightly-fuzzing}

Автосборка запускает все fuzzy тесты достижимые от корня Аркадии аналогично обычным тестам.

Для автоматического фаззинга (расширение корпуса) путь до `FUZZ` модуля следует добавить в [autocheck/linux/fuzzy_tests_targets.inc](https://a.yandex-team.ru/arc/trunk/arcadia/autocheck/linux/fuzzy_tests_targets.inc).
Тест будет [обнаружен](https://sandbox.yandex-team.ru/scheduler/3542/view) и [зарегистрирован](https://a.yandex-team.ru/arc/trunk/arcadia/testenv/jobs/ymake/FuzzTests.json) в течении 10-15 минут.
После регистрации в TestEnv [базе](https://testenv.yandex-team.ru/?screen=manage&database=fuzzing) будет регулярно запускаться [metatest](https://a.yandex-team.ru/arc/trunk/arcadia/testenv/jobs/ymake/FuzzTests.py), регистрирующий тесты в базе.
Такие тесты запускаются с помощью Sandbox таски `FUZZ_YA_MAKE_TASK`, которая запустит фаззинг на 2 часа ночью `--fuzz-opts="-max_total_time=7200"` и закоммитит найденные интересные случаи в корпус соответствующего проекта.

Таким образом, если еженощный процесс расширения корпуса найдёт данные, которые будут приводить к падению/утечке, этот проект будет падать в автосборке CI при запуске соответствующего fuzzy теста.

Когда количество автоматически намайненных данных в `corpus.json` достигает 6, тестовая машинерия запускает процедуру минимизации корпуса, объединяя все части в один, убирая избыточные и более нерелевантные кейсы.
Если у теста выставлен макрос `TAG(ya:always_minimize)`, то минимизация инициируется каждый раз после фаззинга.

### Длительность еженощного фаззинга {#nightly-fuzz-duration}

По-умолчанию еженощный фаззинга запускается на 2 часа.
Это время можно увеличить с помощью `TAG(sb:fuzz_duration=Х)` в ya.make, где вместо `X` указываются секунды определяющие время фаззинга (являясь эквивалентом `--fuzz-opts="-max_total_time=Х"`).
Вместо секунд в `sb:fuzz_duration` можно указывать минуты в нотации `Xm` и часы в виде `Xh`.
Если указывается неверный формат, то используется время по-умолчанию.
Максимально допустимое значение - 20h, если указывается больше, оно уменьшается до указанного лимита.

## Отладка {#debug}

### Прогон конкретного кейса {#fuzz-case}

Для прогона одного конкретного кейса следует воспользоваться опцией `--fuzz-case`:

```bash
ya make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A --fuzz-case=abs_path
```

Кейc можно скачать с CI - `baseunit` в плашке файлов после раскрытия snippet или отдельно сохранить после локального прогона fuzzy теста.

### Ручная минимизация корпуса {#manual-minimization}

Если автоматическая минимизация не успевает выполниться (например из-за того, что роботы заливают слишком много данных в корпус или скорость прогона кейсов крайне мала), минимизацию можно произвести самостоятельно на разработческой машине:

``` bash
ya make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A --fuzzing --fuzz-minimization-only
```

После чего полученный корпус необходимо закоммитить.

Если корпус расширяется слишком быстро и минимизация постоянно не укладывается в таймаут, следует добавить тег в ya.make файл `ya:always_minimize`.
В этом случае корпус будет минимизироваться после каждого запуска фаззинга.
Этого же эффекта можно достичь с помощью ключа `--fuzz-minimize`.

## Метрики {#metrics}

`ya make` собирает следующие метрики во время fuzzing, доступные при локальном прогоне при указании `-P --show-metrics` и в CI у `fuzz:test` теста:

- `corpus_size` - зарегистрированный fuzzer'ом размер корпуса
- `fuzz_iterations_per_second` - количество итераций в секунду. Если тест таймаутится, до повышения его размера, следует посмотреть на эту метрику, возможно в код была добавлена регрессия, которая замедлила целевую функцию
- `number_of_executed_units` - количество проверенных кейсов
- `mined_corpus_size` - количество новых кейсов, которые нашёл fuzzer (расширение корпуса)
- `peak_rss_mb` - пиковое потребление rss во время fuzzing/прогона fuzzy теста
- `slowest_unit_time_sec` - время самого медленного из запущенных кейсов

## Опции по умолчанию {#defaults}

Для fuzzing и прогона fuzzy тестов выставляются дополнительные параметры:

- Опции для `libFuzzer`:

   ``` dart
   -max_total_time=600
   -rss_limit_mb=4096
   -timeout=600
   ```

- Переменные окружения `*SAN_OPTIONS`:

   ``` dart
   coverage=1
   allocator_may_return_null=1
   ```

## FUZZ_OPTS {#fuzz-opts}

В запуск `ya make` можно передать опции для fuzzing/fuzzy теста с помощью опции `--fuzz-opts`. Например так можно переопределить время fuzzing'a:

``` bash
ya make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep --fuzzing -A --fuzz-opts="-max_total_time=300"
```

Некоторые опции хочется фиксировать, чтобы fuzzing в автосборке/еженощно запускался с ними. Для этого следует использовать макрос `FUZZ_OPTS`, в котором следует перечислить требуемые опции через пробел. Например:

``` dart
FUZZ_OPTS(
    -max_len=1024
    -only_ascii=1
    -rss_limit_mb=2048
)
```

С помощью `FUZZ_OPTS` не следует переопределять `-max_total_time`, так как в этом случае фаззинг может начать падать - это значение не задаёт лимит на время работы Sandbox таски и она форсированно завершится через 3 часа.

## FUZZ_DICTS {#fuzz-dicts}

Для `libFuzzer` можно указать пути относительно корня Аркадии до файлов-словарей, каждый из которых содержит на каждой строке - один case, от которого может отталкиваться fuzzer движок для мутаций и поиска новых интересных входных данных.
Пример такого [словаря](https://a.yandex-team.ru/arc/trunk/arcadia/yql/tools/fuzzing/fuzzer_sql_v1/sql.dict?peg=8080063&rev=8080063)

Пример использования макроса:

``` dart
FUZZ_DICTS(
    devtools/test_tool/run_fuzz/tests/data/sample_with_dict/dict.txt
    devtools/test_tool/run_fuzz/tests/data/sample_with_several_dicts/dict.txt
)
```

## Fuzz proof {#fuzz-proof}

Для подтверждения высокого уровня покрытия кода имеющимся корпусом, можно воспользоваться режимом `--fuzz-proof X`, где X время в секундах, которое требуется дополнительно фаззить с момента обнаружения последнего найденного кейса в текущем запуске.
Если в течении X секунд с момента обнаружения последнего кейса (или с начала фаззинга, если не было найдено ни одного нового кейса в текущем запуске) будет найден хотя бы один новый - это приведёт к завершению фаззинга с ошибкой. Все найденные кейсы будут сохранены в корпусе.

Режим следует комбинировать с ограничением по времени или по суммарному количеству итераций.

``` bash
ya make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep --fuzzing -A --fuzz-opts="-max_total_time=3600" --fuzz-proof=1800
```

В этом случае будет запущена стандартная процедура расширения корпуса с ограничением времени фаззинга в 1 час (3600с).
После её успешного окончания, ищется самый свежий кейс в полученном корпусе.

Если машинерия фаззинга обнаружит последний найденный кейс более 30 минут (1800с) назад, то считается что proof состоялся и фаззинг успешно завершается.

Если последний кейс был обнаружен 10 минут назад (для примера), то фаззинг будет перезапущен на дополнительные 20 минут в режиме fuzz proof с использованием всего текущего корпуса (включая новые найденные данные). Если тестовая машинерия обнаружит новый кейс во время работы в этом режиме, процесс фаззинга будет прерван, а все найденные кейсы сохранены - proof не состоялся, об этом сообщит ошибка в тесте.

``` bash
ya make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep --fuzzing -A --fuzz-runs=1000000 --fuzz-proof=3600
```

Аналогично работает fuzz proof режим при запуске фаззинга с ограничением суммарного количества прогонов целевой функции.

## Опции фаззинга ya make -t {#cli-opts}

Опция | Описание
:--- | :---
`--fuzzing` | Запускает фаззинг (режим расширения корпуса), см. [concept](#concept)
`--fuzz-opts OPTS` | Задаёт опции для libFuzzer, см [fuzz-opts](#fuzz-opts)
`--fuzz-case PATH` | Позволяет указать путь до конкретного кейса, который следует проверить, см [fuzz-case](#fuzz-case)
`--fuzz-local-store` | Отключает автоматическое сохранение и загрузку расширения корпуса (новые намайненные данные) в Sandbox
`--fuzz-runs X` | По умолчанию fuzzing запускается в режиме ограничения по времени, см [max_total_time](#defaults), --fuzz-runs позволяет ограничивать количество запусков целевой функции.
`--fuzz-minimize` | Всегда минимизировать корпус после фаззинга, см [minimization](#manual-minimization)
`--fuzz-minimization-only` | Запустить процедуру минимизации корпуса без фаззинга, см [minimization](#manual-minimization)
`--fuzz-proof X` | Позволяет задать Х секунд, которое требуется дополнительно фаззить после последнего найденного кейса для корректного завершения фаззинга, см [fuzz-proof](#fuzz-proof)

## Ручное расширение корпуса {#corpus-extension}

Если вы каким-то образом уже намайнили корпус и хотите его подключить к автоматике, вам следует:

1. Залить его в Sandbox с помощью `ya upload --tar path-to-dir-with-corpus`
2. Добавить в `corpus.json` проекта в [arcadia/fuzzing](https://a.yandex-team.ru/arc/trunk/arcadia/fuzzing/). Если его нет - создать новый вида:

   ``` json
    {
      "corpus_parts": [
        <SANDBOX_RESOURCE_ID>
     ]
   }
   ```

3. Проверить, что `ya make` находит корпус и корректно его прогоняет:

   ``` bash
   ya make --sanitize=address --sanitize-coverage=trace-div,trace-gep -A -P --show-metrics
   ```

   Метрика `corpus_size` у теста `fuzz::test` должна отображать ожидаемое количество примеров из корпуса.

4. Зафиксировать изменение в `corpus.json` файле в репозитории.

## Пользовательская автоматизация в Sandbox для расширения корпуса {#user-corpus-extension}

Иногда может быть полезна возможность расширять корпус самостоятельно, добавляя интересные случаи в корпус до того как до него дойдёт фаззинг.
Для расширения корпуса из тасок следует пользоваться [projects/common/fuzzing](https://a.yandex-team.ru/arc/trunk/arcadia/search/garden/sandbox-tasks/projects/common/fuzzing/__init__.py).
[Пример](https://a.yandex-team.ru/arc/trunk/arcadia/search/garden/sandbox-tasks/projects/images/daemons/ImagesGenerateRimdaemonRequests/__init__.py?rev=2955593#L170) использования.
