cargo-fuzz - libFuzzer로 Rust 퍼징 치트시트
cargo-fuzz는 Rust 코드를 퍼징하는 표준 방법입니다. 이는 libFuzzer를 Cargo 원어민 워크플로우로 연결합니다: fuzz 대상을 임의 바이트를 취하는 일반적인 Rust 함수로 작성하고, cargo fuzz run을 실행하고, 커버리지 기반 퍼징은 panic, 산술 오버플로우, — unsafe 코드에서 — 진정한 메모리 오류를 사냥합니다. 안전한 Rust는 메모리 손상을 방지하지만 panic, 무한 루프, 또는 논리 버그를 방지하지 않으며 모든 unsafe 블록 또는 C 종속성이 메모리 문제의 전체 클래스를 재도입합니다.
설치
| 단계 | 명령어 |
|---|
| 설치 | cargo install cargo-fuzz |
| Nightly 필요 | rustup install nightly (libFuzzer는 nightly 필요) |
| 초기화 | cargo fuzz init (크레이트에서) |
| 확인 | cargo fuzz --version |
Fuzz 대상 생성
cargo fuzz init # fuzz/ 디렉터리 생성
cargo fuzz add parse_input # 새 대상 생성
// fuzz/fuzz_targets/parse_input.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = my_crate::parse(s); // 여기 panic은 발견됨
}
});
실행
| 명령어 | 설명 |
|---|
cargo fuzz run parse_input | 대상을 퍼징 |
cargo fuzz run parse_input -- -max_total_time=300 | 5분 실행 |
cargo fuzz run parse_input -j 8 | 8개 병렬 작업 |
cargo fuzz list | 대상 나열 |
cargo fuzz build | 실행하지 않고 빌드 |
임의로 구조화된 입력
원본 바이트는 대상이 즉시 거부하는 입력에 시간을 낭비합니다. arbitrary 크레이트는 대신 타입화된 값을 구축합니다:
use arbitrary::Arbitrary;
#[derive(Arbitrary, Debug)]
struct Config { name: String, retries: u8, enabled: bool }
fuzz_target!(|cfg: Config| {
let _ = my_crate::apply_config(cfg);
});
| 이점 | 이유 |
|---|
| 유효한 모양 입력 | 더 심층 논리에 도달 |
| 타입화된 대상 | 파서가 아닌 API 퍼징 |
Debug 출력 | 읽을 수 있는 재현 |
살균기
| 살균기 | 플래그 |
|---|
| AddressSanitizer (기본값) | -s address |
| LeakSanitizer | ASan에 포함됨 |
| MemorySanitizer | -s memory |
| ThreadSanitizer | -s thread |
| 없음 (빠름) | -s none |
# ASan으로 unsafe FFI 코드 퍼징
cargo fuzz run ffi_target -s address
코퍼스 및 커버리지
| 명령어 | 목적 |
|---|
fuzz/corpus/<target>/ | 흥미로운 입력이 축적되는 곳 |
cargo fuzz cmin <target> | 코퍼스 최소화 |
cargo fuzz tmin <target> <input> | 충돌 입력 최소화 |
cargo fuzz coverage <target> | 커버리지 데이터 생성 |
| 시드 코퍼스 | 부트스트랩하기 위해 유효한 예제 추가 |
시드 코퍼스를 실제 유효한 입력으로 채우는 것이 퍼징을 빠르게 생산적으로 만드는 가장 효과적인 단일 방법입니다.
충돌 재현
# 충돌은 fuzz/artifacts/<target>/에 착지합니다
cargo fuzz run parse_input fuzz/artifacts/parse_input/crash-abc123
# 최소한의 경우로 축소
cargo fuzz tmin parse_input fuzz/artifacts/parse_input/crash-abc123
최소화된 입력을 일반적인 #[test]로 변환하여 버그가 수정된 상태로 유지되도록 합니다.
Rust에서 퍼징할 것
| 좋은 대상 | 이유 |
|---|
| 파서 / deserializer | 신뢰할 수 없는 입력, 복잡한 상태 |
unsafe 블록 | 메모리 안전이 실제로 위험함 |
| FFI 경계 | 인터페이스 뒤의 C 코드 |
| 프로토콜/상태 머신 | 순서 버그 |
| 압축 / 인코딩 | 고전적인 버그 영역 |
| 덜 유용 | 이유 |
|---|
| 순수 안전 산술 | Panic은 보통 명백함 |
| 얇은 래퍼 | 탐색할 논리가 적음 |
cargo-fuzz vs 다른 퍼저
| 측면 | cargo-fuzz | AFL++ (afl.rs) | LibAFL | honggfuzz-rs |
|---|
| 엔진 | libFuzzer | AFL++ | 사용자 정의 | honggfuzz |
| 설정 | Rust에 가장 쉬움 | 중간 | 직접 구축 | 쉬움 |
| 구조화된 입력 | arbitrary | arbitrary | 사용자 정의 타입 | arbitrary |
| 최적 대상 | 표준 Rust 퍼징 | 영구/바이너리 모드 | 이상한 대상 | 하드웨어 피드백 |
비 Rust 대상은 AFL++ 또는 honggfuzz를 참조합니다; 사용자 정의 퍼저는 LibAFL을 참조합니다.
리소스