
Rust-макросы и подкоманда Cargo для автоматизации фаззинга с помощью afl.rs, включая генерацию корпуса и реализацию харнеса, интегрированные с тестовым фреймворком Rust.
test-fuzz — это подкоманда Cargo и набор макросов Rust для автоматизации задач, связанных с фаззингом с помощью [afl.rs], включая:
test-fuzz выполняет эти задачи (частично) с помощью средств тестирования Rust. Например, для генерации фаззинг-корпуса test-fuzz записывает аргументы целевой функции при каждом её вызове во время выполнения cargo test. Аналогично, test-fuzz реализует фаззинг-харнес как дополнительный тест в бинарном файле, созданном cargo-test. Именно такая тесная интеграция со средствами тестирования Rust объясняет название test-fuzz.
Содержание
test_fuzz]test_fuzz_impl]cargo test-fuzz]test-fuzz]Установите cargo-test-fuzz и [afl.rs] следующей командой:```sh
cargo install cargo-test-fuzz cargo-afl
## Обзор
Фаззинг с помощью `test-fuzz` — это, по сути, три шага:\*
1. **Определите цель фаззинга**:
- Добавьте следующие `dependencies` в файл `Cargo.toml` целевого крейта:
```toml
serde = "*"
test-fuzz = "*"
```
- Поместите перед целевой функцией макрос [`test_fuzz`]:
```rust
#[test_fuzz::test_fuzz]
fn foo(...) {
...
}
```
2. **Создайте корпус**, запустив `cargo test`: ```
cargo test
cargo test-fuzz]: ```
cargo test-fuzz foo
* Дополнительный предварительный шаг может потребоваться после перезагрузки:```sh cargo afl system-config
Обратите внимание, что приведённая выше команда запускает `sudo` внутри себя. Поэтому у вас может быть запрошен пароль.
## Компоненты
### Макрос `test_fuzz`
Размещение макроса `test_fuzz` перед функцией означает, что функция является фаззинг-целью.
Основные эффекты макроса `test_fuzz`:
- Добавляет инструментирование в цель, чтобы сериализовать её аргументы и записывать их в файл корпуса при каждом вызове цели. Инструментирование защищено `#[cfg(test)]`, поэтому файлы корпуса создаются только при запуске тестов (однако см. [`enable_in_production`] ниже).
- Добавляет тест для чтения и десериализации аргументов из стандартного ввода и применения к ним цели. Тест проверяет переменную окружения, устанавливаемую [`cargo test-fuzz`], чтобы при обычном запуске `cargo test` тест не блокировался попыткой чтения из стандартного ввода. Тест помещается в модуль, чтобы снизить вероятность конфликта имён. В настоящее время модуль называется `target_fuzz`, где `target` — имя цели (однако см. [`rename`] ниже).
#### Аргументы
##### `bounds = "where_predicates"`
Накладывает `where_predicates` (например, ограничения трейтов) на структуру, используемую для сериализации/десериализации аргументов. Это может быть необходимо, например, если тип аргумента цели является ассоциированным типом. Пример см. в [associated_type.rs] в этом репозитории.
##### `generic_args = "parameters"`
Используйте `parameters` в качестве параметров типа цели при фаззинге. Пример:```rust
#[test_fuzz(generic_args = "String")]
fn foo<T: Clone + Debug + Serialize>(x: &T) {
...
}
Примечание: аргументы цели должны быть сериализуемыми для каждой инстанциации её параметров типов. Но аргументы цели должны быть десериализуемыми только тогда, когда цель инстанцируется с parameters.
impl_generic_args = "parameters"Используйте parameters в качестве параметров типа Self цели при фаззинге. Пример:```rust
#[test_fuzz_impl]
impl<T: Clone + Debug + Serialize> for Foo {
#[test_fuzz(impl_generic_args = "String")]
fn bar(&self, x: &T) {
...
}
}
Примечание: аргументы цели должны быть сериализуемыми для **каждой** инстанциации её параметров типа `Self`. Но аргументы цели должны быть десериализуемыми только когда `Self` цели инстанцирован с `parameters`.
##### `convert = "X, Y"`
При сериализации аргументов цели преобразуйте значения типа `X` в тип `Y`, используя реализацию `From<X>` в `Y`, или значения типа `&X` в тип `Y` с использованием реализации нестандартного трейта `test_fuzz::FromRef<X>` в `Y`. При десериализации преобразуйте эти значения обратно в тип `X`, используя реализацию нестандартного трейта `test_fuzz::Into<X>` в `Y`.
То есть использование `convert = "X, Y"` должно сопровождаться определёнными реализациями. Если `X` реализует [`Clone`], то `Y` может реализовать следующее:```rust
impl From<X> for Y {
fn from(x: X) -> Self {
...
}
}
Если X не реализует [Clone], то Y должен реализовать следующее:```rust
impl test_fuzz::FromRef for Y {
fn from_ref(x: &X) -> Self {
...
}
}
Кроме того, `Y` должен реализовывать следующее (независимо от того, реализует ли `X` [`Clone`]):```rust
impl test_fuzz::Into<X> for Y {
fn into(self) -> X {
...
}
}
Определение test_fuzz::Into идентично определению [std::convert::Into]. Причина использования нестандартного трейта — избежать конфликтов, которые могут возникнуть из-за бланкетных реализаций стандартных трейтов.
enable_in_productionГенерируйте файлы корпуса при запуске вне тестов, если установлена переменная окружения [TEST_FUZZ_WRITE]. По умолчанию файлы корпуса генерируются только при запуске тестов, независимо от того, установлена ли [TEST_FUZZ_WRITE]. При запуске целевой функции за пределами каталога её пакета установите [TEST_FUZZ_MANIFEST_PATH] в путь к файлу Cargo.toml пакета.
ПРЕДУПРЕЖДЕНИЕ: Установка enable_in_production может привести к появлению вектора отказа в обслуживании. Например, установка этой опции для функции, вызываемой много раз с разными аргументами, может заполнить диск. Проверка [TEST_FUZZ_WRITE] предназначена для обеспечения некоторой защиты от такой возможности. Тем не менее, прежде чем использовать эту опцию, внимательно её обдумайте.
execute_with = "function"Вместо прямого вызова целевой функции:
FnOnce() -> R, где R — возвращаемый тип целевой функции, так чтобы вызов замыкания вызывал целевую функцию;function с этим замыканием.Вызов целевой функции таким образом позволяет function настроить окружение вызова. Это может быть полезно, например, для фаззинга [экстерналий Substrate].
no_auto_generateНе пытайтесь [автоматически генерировать файлы корпуса] для целевой функции.
only_generic_argsЗаписывайте обобщённые аргументы целевой функции при запуске тестов, но не генерируйте файлы корпуса и не реализуйте фаззинг-харнес. Это может быть полезно, когда целевая функция является обобщённой, но неясно, какие параметры типа следует использовать для фаззинга.
Предполагаемый рабочий процесс: включите only_generic_args, затем выполните cargo test, а затем cargo test-fuzz --display generic-args. Один из полученных обобщённых аргументов может подойти в качестве parameters для generic_args. Аналогично, обобщённые аргументы, полученные в результате cargo test-fuzz --display impl-generic-args, могут подойти в качестве parameters для impl_generic_args.
Обратите внимание, однако, что сам по себе факт вызова целевой функции с определёнными параметрами во время тестов не означает, что аргументы целевой функции сериализуемы/десериализуемы при использовании этих параметров. Результаты --display generic-args/--display impl-generic-args носят лишь ориентировочный характер.
rename = "name"Считайте, что целевая функция называется name, при добавлении модуля в охватывающую область видимости. Раскрытие макроса test_fuzz добавляет определение модуля в охватывающую область видимости. По умолчанию модуль называется следующим образом: