
Rust 매크로와 Cargo 하위 명령어로 afl.rs를 통한 퍼징을 자동화하는 도구로, 코퍼스 생성과 하네스 구현을 포함하며 Rust의 테스트 프레임워크와 통합됩니다.
test-fuzz는 [afl.rs]를 사용한 퍼징과 관련된 특정 작업을 자동화하는 Cargo 하위 명령이자 Rust 매크로 모음입니다. 여기에는 다음이 포함됩니다:
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. **퍼징 타깃을 식별하세요**:
- 대상 크레이트의 `Cargo.toml` 파일에 다음 `dependencies`를 추가하세요:
```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` 매크로를 붙이면 해당 함수가 퍼즈 타깃(fuzz target)임을 나타냅니다.
`test_fuzz` 매크로의 주요 효과는 다음과 같습니다.
- 대상이 호출될 때마다 인수를 직렬화하여 코퍼스(corpus) 파일에 기록하도록 대상에 계측(instrumentation)을 추가합니다. 이 계측은 `#[cfg(test)]`로 보호되므로 테스트 실행 중에만 코퍼스 파일이 생성됩니다(단, 아래 [`enable_in_production`] 참조).
- 표준 입력에서 인수를 읽고 역직렬화하여 대상에 적용하는 테스트를 추가합니다. 이 테스트는 [`cargo test-fuzz`]가 설정한 환경 변수를 확인하므로, 일반적인 `cargo test` 실행 중에는 표준 입력을 읽기 위해 블로킹하지 않습니다. 이름 충돌 가능성을 줄이기 위해 테스트는 모듈 안에 포함됩니다. 현재 모듈 이름은 `target_fuzz`이며, 여기서 `target`은 대상의 이름입니다(단, 아래 [`rename`] 참조).
#### 인자
##### `bounds = "where_predicates"`
인수를 직렬화/역직렬화하는 데 사용되는 구조체에 `where_predicates`(예: 트레이트 바운드)를 적용합니다. 예를 들어 대상의 인수 타입이 연관 타입(associated type)인 경우 필요할 수 있습니다. 예시는 이 저장소의 [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"`
대상의 인자를 직렬화할 때, `Y`의 `From<X>` 구현을 사용하여 타입 `X`의 값을 타입 `Y`로 변환하거나, 비표준 트레이트 `test_fuzz::FromRef<X>`에 대한 `Y`의 구현을 사용하여 타입 `&X`의 값을 타입 `Y`로 변환합니다. 역직렬화할 때는 비표준 트레이트 `test_fuzz::Into<X>`에 대한 `Y`의 구현을 사용하여 해당 값을 타입 `X`로 다시 변환합니다.
즉, `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]의 정의와 동일합니다. 표준이 아닌 트레이트를 사용하는 이유는 표준 트레이트의 포괄 구현(blanket implementations)에서 발생할 수 있는 충돌을 피하기 위해서입니다.
enable_in_production테스트를 실행 중이 아닐 때도 [TEST_FUZZ_WRITE] 환경 변수가 설정되어 있으면 코퍼스 파일을 생성합니다. 기본값은 [TEST_FUZZ_WRITE] 설정 여부와 관계없이 테스트 실행 시에만 코퍼스 파일을 생성하는 것입니다. 패키지 디렉터리 밖에서 타깃을 실행하는 경우 [TEST_FUZZ_MANIFEST_PATH]를 패키지의 Cargo.toml 파일 경로로 설정하세요.
경고: enable_in_production을 설정하면 서비스 거부(denial-of-service) 벡터가 발생할 수 있습니다. 예를 들어, 다양한 인수로 여러 번 호출되는 함수에 이 옵션을 설정하면 디스크가 가득 찰 수 있습니다. [TEST_FUZZ_WRITE] 검사는 이러한 가능성에 대한 일부 방어를 제공하기 위한 것입니다. 그럼에도 불구하고 이 옵션을 사용하기 전에 신중히 고려하십시오.
execute_with = "function"타깃을 직접 호출하는 대신:
FnOnce() -> R 유형의 클로저를 구성합니다. 여기서 R은 타깃의 반환 유형이며, 클로저를 호출하면 타깃이 호출됩니다.function을 호출합니다.이렇게 타깃을 호출하면 function이 호출 환경을 설정할 수 있습니다. 예를 들어 [Substrate externalities]를 퍼징할 때 유용할 수 있습니다.
no_auto_generate타깃에 대해 [auto-generate corpus files]를 시도하지 않습니다.
only_generic_args테스트 실행 중 타깃의 제네릭 인자를 기록하되, 코퍼스 파일을 생성하지 않고 퍼징 하네스도 구현하지 않습니다. 이는 타깃이 제네릭 함수인데 퍼징에 사용할 타입 매개변수가 무엇인지 불분명할 때 유용할 수 있습니다.
의도된 워크플로는 다음과 같습니다: only_generic_args를 활성화한 다음 cargo test를 실행하고 이어서 cargo test-fuzz --display generic-args를 실행합니다. 결과로 나온 제네릭 인자 중 하나가 generic_args의 parameters로 사용할 수 있을 수 있습니다. 마찬가지로 cargo test-fuzz --display impl-generic-args의 결과로 나온 제네릭 인자는 impl_generic_args의 parameters로 사용할 수 있을 수 있습니다.
그러나 테스트 중 특정 매개변수로 타깃이 호출되었다고 해서 해당 매개변수를 사용할 때 타깃의 인수가 직렬화/역직렬화 가능하다는 의미는 아닙니다. --display generic-args/--display impl-generic-args의 결과는 단지 참고용일 뿐입니다.
rename = "name"타깃을 둘러싼 범위에 모듈을 추가할 때 타깃의 이름이 name인 것처럼 취급합니다. test_fuzz 매크로의 확장은 둘러싼 범위에 모듈 정의를 추가합니다. 기본적으로 모듈 이름은 다음과 같이 지정됩니다:
impl 블록에 나타나지 않으면 모듈 이름은 target_fuzz__이며, 여기서 target은 타깃의 이름입니다.impl 블록에 나타나면 모듈 이름은 path_target_fuzz__이며, 여기서 path는 impl의 Self 유형 경로의 마지막 세그먼트입니다.그러나 이 옵션을 사용하면 모듈 이름이 대신 name_fuzz__가 됩니다. 예:```rust
#[test_fuzz(rename = "bar")]
fn foo() {}
// Without the use of rename, a name collision and compile error would result.
mod foo_fuzz__ {}
#### 함수 인자에 대한 Serde 필드 속성
`test_fuzz` 매크로는 [Serde 필드 속성]을 함수 인자에 적용할 수 있게 해줍니다. 이는 다루기 어려운 타입을 처리하는 또 다른 도구를 제공합니다.
다음은 예시입니다. `Context`는 `Mutex`를 포함하기 때문에 `serde::Serialize` 및 `serde::Deserialize` 트레이트를 파생할 수 없습니다. 그러나 `Context`는 `Default`를 구현합니다. 따라서 `Context` 인자에 `#[serde(skip)]`을 적용하면 직렬화할 때 해당 인자가 생략되고, 역직렬화할 때 기본값을 사용하게 됩니다.```rust
use std::sync::Mutex;
// Traits `serde::Serialize` and `serde::Deserialize` cannot be derived for `Context` because it
// contains a `Mutex`.
#[derive(Default)]
struct Context {
lock: Mutex<()>,
}
impl Clone for Context {
fn clone(&self) -> Self {
Self {
lock: Mutex::new(()),
}
}
}
#[test_fuzz::test_fuzz]
fn target(#[serde(skip)] context: Context, x: i32) {
assert!(x >= 0);
}
Serde 필드 속성이 인자에 적용되면 test_fuzz 매크로는 해당 인자에 대해 다른 [변환]을 수행하지 않습니다.
test_fuzz_impl 매크로[test_fuzz] 매크로가 impl 블록에서 사용될 때마다,
impl 앞에 반드시 test_fuzz_impl 매크로가 와야 합니다. 예시:```rust
#[test_fuzz_impl]
impl Foo {
#[test_fuzz]
fn bar(&self, x: &str) {
...
}
}
이 요구 사항의 이유는 다음과 같습니다. `test_fuzz` 매크로의 확장은 포함하는 범위에 모듈 정의를 추가합니다. 그러나 모듈 정의는 `impl` 블록 내부에 나타날 수 없습니다. `impl` 앞에 `test_fuzz_impl` 매크로를 배치하면 모듈이 `impl` 블록 외부에 추가됩니다.
다음과 같은 오류가 표시된다면 `test_fuzz_impl` 매크로 사용이 누락되었음을 의미할 가능성이 높습니다:```
error: module is not supported in `trait`s or `impl`s
test_fuzz_impl 현재 옵션이 없습니다.
cargo test-fuzz 명령cargo test-fuzz 명령은 퍼즈 타겟과 상호 작용하고 해당 코퍼스(corpora), 크래시(crashes), 행(hangs), 작업 큐(work queues)를 조작하는 데 사용됩니다. 호출 예는 다음과 같습니다:
foo의 코퍼스 표시 ```
cargo test-fuzz foo --display corpus
foo ```
cargo test-fuzz foo
foo에 대해 발견된 크래시 재생 ```
cargo test-fuzz foo --replay crashes
Usage: cargo test-fuzz [OPTIONS] [TARGETNAME] [-- ...]
Arguments: [TARGETNAME] String that fuzz target's name must contain [ARGS]... Arguments for the fuzzer