
libFuzzer 타겟과 호환되는 분산 커버리지 기반 퍼징 엔진; 수천 개의 동시 작업으로 확장되며, 샌타타이저와 코퍼스 증류를 사용하여 버그를 발견합니다.
왜 아닐까? 우리는 현재 매우 크고 매우 느린 타깃들을 퍼징하려고 시도 중이며, 이러한 타깃에는 libFuzzer, AFL 등이 반드시 잘 확장되지는 않는다. 우리의 동기 부여 사례 중 하나는 SiliFuzz를 참조하라. Centipede를 작업하면서 우리는 기존 퍼징 엔진들이 시도하지 않는 대규모 차등(differential) 퍼징의 새로운 접근 방식을 실험할 계획이다.
주요 특징:
libFuzzer 기반 퍼즈 타깃을 별다른 설정 없이 지원. 여러분이 선호하는
LLVMFuzzerTestOneInput()을 사용하려면 Centipede의 컴파일러 및 링커 옵션으로
타깃을 빌드하기만 하면 된다.
작업 진행 중(Work-in-progress). 우리는 소규모 팀에서 몇 개의 타깃으로 Centipede를 테스트하고 있다. 여러분이 Centipede 프로젝트의 일원이 아니거나, 우리를 돕고 싶지 않다면, 아직 더 이상 읽지 않는 것이 좋을 것이다.
확장성(Scale). 의도는 매우 적은 통신 오버헤드로任意 개수의 잡을 동시에 실행할 수 있게 하는 것이다. 우리는 현재 로컬에서 100개의 잡, 클러스터에서 1만 개의 잡으로 테스트하고 있다.
프로세스 외부 실행(Out of process). 타깃은 별도의 프로세스에서 실행된다. 타깃에서 발생한 크래시는 퍼저에 영향을 주지 않는다. Centipede는 인프로세스(in-process) 모드로도 사용할 수 있지만, 이 모드는 주요 목표가 아니다. 타깃이 작고 빠르다면 여전히 libFuzzer를 사용하는 편이 좋을 것이다.
새니타이저와의 통합은 별도의 빌드를 통해 이루어진다. 퍼징 중에 ASAN, MSAN 또는 TSAN으로 버그를 찾고 싶다면, 각 새니타이저에 대한 별도의 바이너리와 Centipede 자체용 메인 바이너리 하나를 제공해야 한다. 메인 바이너리는 어떤 새니타이저도 사용해서는 안 된다.
내부 인터페이스의 어떤 부분도 안정적이지 않다. 이 단계에서는 무엇이든 변경될 수 있다.
타깃을 위한 무한한 입력 스트림을 생성하고 실행을 조율하는 프로그램.
바이너리, 라이브러리, API, 또는 입력으로 바이트를 소비하고 출력으로 일종의 커버리지 데이터를 생성할 수 있는 모든 것. libFuzzer의 타깃은 Centipede의 타깃이 될 수 있다. 자세한 내용은 여기 를 읽어보라.
타깃에 주입할 수 있는 바이트 시퀀스. 입력은 임의의 바이트 묶음이거나, 직렬화된 프로토(proto)와 같은 구조화된 데이터일 수 있다.
타깃의 고유한 동작을 나타내는 숫자. 예를 들어 피처 1234567은 타깃의 기본 블록 번호 987이 7번 실행되었다는 사실을 나타낼 수 있다. 퍼저는 입력을 타깃으로 실행할 때 실행 중 관찰된 피처들을 수집한다.
하나의 특정 입력과 연관된 피처들의 집합.
타깃이 주어진 입력을 실행할 때의 동작에 대한 일부 정보. 커버리지는 일반적으로 해당 입력이 타깃에서 트리거한 피처 집합으로 표현된다.
바이트를 입력으로 받아 입력의 작은 무작위 변이를 출력하는 함수. 참조: 구조 인식 퍼징(structure-aware fuzzing) .
입력을 타깃에 주입하고 그 대가로 커버리지를 얻는 방법을 아는 함수 (즉, 실행하는 함수).
사용자가 뮤테이터와 실행기를 교체할 수 있게 해주는 커스터마이즈 가능한 퍼징 엔진.
Centipede 퍼저가 기대하는 실행기 인터페이스를 구현하는 라이브러리. 러너는 sancov로 계측된 타깃을 실행하고, 결과 커버리지를 수집하며, 이를 Centipede에 다시 전달하는 방법을 안다. Centipede 퍼즈 타깃을 희망하는 대상은 이 라이브러리와 링크하여 Centipede로 실행할 수 있게 만들 수 있다.
입력들의 집합.
더 큰 코퍼스에서 원래 코퍼스와 동일한 커버리지 피처를 갖는 부분 집합을 선택하는 과정.
코퍼스의 부분 집합을 나타내는 파일과, 동일한 코퍼스 부분 집합에 대한 피처 집합을 나타내는 또 다른 파일.
샤드 B를 샤드 A에 병합한다는 것은: 샤드 B의 입력 중 샤드 A에 없는 피처를 가진 입력이 있으면 그 입력을 A에 추가하는 것을 의미한다.
단일 퍼저 프로세스. 하나의 잡은 하나의 샤드에만 쓰지만 여러 샤드를 읽을 수 있다.
퍼저가 생성하거나 소비하는 데이터를 포함하는 로컬 또는 원격 디렉터리.
git clone https://github.com/google/centipede.git
cd centipede
CENTIPEDE_SRC=`pwd`
BIN_DIR=$CENTIPEDE_SRC/bazel-bin
bazel build -c opt :all
이후 단계에서 필요한 것:
$BIN_DIR/centipede - 엔진(퍼저)의 바이너리.$BIN_DIR/libcentipede_runner.pic.a - 퍼즈 타깃(러너)과 링크해야 하는 라이브러리.$CENTIPEDE_SRC/clang-flags.txt - 타깃에 권장되는 clang 컴파일 플래그.이 파일들을 원래 위치에 두거나 다른 곳에 복사해도 된다.
타깃 빌드 예시 두 가지를 제공한다: 하나는 아주 작은 단일 파일 타깃이고 다른 하나는 libpng이다. 타깃을 빌드한 후에는 퍼즈 타깃 실행 단계로 진행하라.
이 예제는 Centipede 저장소에 포함된 간단한 예제 퍼즈 타깃 중 하나, 일명 퍼즐(puzzles) 을 사용한다.
참고: 아래 명령은 $CENTIPEDE_SRC/clang-flags.txt의 플래그를 사용한다. 다른 계측 플래그 세트를 선택해도 된다. clang-flags.txt는 단순한 기본 옵션만 제공한다.
FUZZ_TARGET=byte_cmp_4 # or any other source under $CENTIPEDE_SRC/puzzles
clang++ @$CENTIPEDE_SRC/clang-flags.txt -c $CENTIPEDE_SRC/puzzles/$FUZZ_TARGET.cc -o $BIN_DIR/$FUZZ_TARGET.o
이 단계는 방금 빌드한 퍼즈 타깃을 libcentipede_runner.pic.a 및 기타 필수 라이브러리와 링크한다.
clang++ $BIN_DIR/$FUZZ_TARGET.o $BIN_DIR/libcentipede_runner.pic.a \
-ldl -lrt -lpthread -o $BIN_DIR/$FUZZ_TARGET
실행 단계로 건너뛰라.
LIBPNG_BRANCH=v1.6.37 # You can experiment with other branches if you'd like
git clone --branch $LIBPNG_BRANCH --single-branch https://github.com/glennrp/libpng.git
cd libpng
CC=clang CFLAGS=@$CENTIPEDE_SRC/clang-flags.txt ./configure --disable-shared
make -j
FUZZ_TARGET=libpng_read_fuzzer
clang++ -include cstdlib \
./contrib/oss-fuzz/$FUZZ_TARGET.cc \
./.libs/libpng16.a \
$BIN_DIR/libcentipede_runner.pic.a \
-ldl -lrt -lpthread -lz \
-o $BIN_DIR/$FUZZ_TARGET
로컬에서 실행하면 전체 규모를 얻을 수는 없지만 퍼저 개발 단계에서는 유용할 수 있다. 네트워크 파일 시스템에 부담을 주지 않도록 실행 전에 퍼저와 타깃을 모두 로컬 디렉터리로 복사할 것을 권장한다.
WD=$HOME/centipede_run
mkdir -p $WD
참고: Centipede의 일부 기능이 작동하려면
llvm-symbolizer를
$PATH에 추가해야 할 수도 있다. 심볼라이저는 LLVM
배포판의 일부로 설치할 수 있다:
sudo apt install llvm
which llvm-symbolizer # normally /usr/bin/llvm-symbolizer
rm -rf $WD/*
$BIN_DIR/centipede --binary=$BIN_DIR/$FUZZ_TARGET --workdir=$WD --num_runs=100
작업 디렉터리에 무엇이 있는지 확인
tree $WD
...
├── <fuzz target name>-d9d90139ee2ccc687f7c9d5821bcc04b8a847df5
│ └── features.0
└── corpus.0
경고: --j 플래그에 시스템의 코어 수를 초과하는 값을 사용하지 마라.
rm -rf $WD/*
$BIN_DIR/centipede --binary=$BIN_DIR/$FUZZ_TARGET --workdir=$WD --num_runs=100 --j=5
작업 디렉터리에 무엇이 있는지 확인:
tree $WD
...
├── <fuzz target name>-d9d90139ee2ccc687f7c9d5821bcc04b8a847df5
│ ├── features.0
│ ├── features.1
│ ├── features.2
│ ├── features.3
│ └── features.4
├── corpus.0
├── corpus.1
├── corpus.2
├── corpus.3
└── corpus.4
각 Centipede 샤드는 일반적으로 전체 코퍼스가 포함하는 모든 피처를 다루지는 않는다. 코퍼스를 정제하려면 Centipede 프로세스가 모든 샤드를 읽어야 한다. 현재 정제는 다음과 같이 작동한다:
--distill_shards=N와 함께 실행한다. 그러면 첫 번째
N개의 잡이 N개의 독립적인 정제된 코퍼스 파일(잡당 하나)을 생성한다.
각 정제된 코퍼스는 전체 코퍼스와 동일한 피처를 가져야 하지만, 정제된 코퍼스들
사이의 입력은 매우 다를 수 있다.정제된 코퍼스를 libFuzzer 스타일 디렉터리(입력당 파일 하나가 있는 로컬 디렉터리)로
내보내야 하는 경우 --corpus_dir=DIR을 추가하라.
Centipede는 텍스트 파일 형태의 간단한 커버리지 리포트를 생성한다. 샤드 123은 실제
퍼징이 시작되기 전에 workdir/coverage-report-BINARY.000123.txt 파일을 생성한다.
즉, 이 리포트는 샤드 123이 코퍼스를 로드한 후 관찰한 커버리지를 반영한다.
리포트는 완전히 커버된 함수(모든 제어 흐름 엣지가 최소 한 번 관찰됨), 커버되지 않은 함수, 부분적으로 커버된 함수를 보여준다. 부분적으로 커버된 함수의 경우 리포트는 커버된 엣지와 커버되지 않은 엣지 모두에 대한 심볼 정보를 포함한다.
리포트는 대략 다음과 같은 형태이다:
FULL: FUNCTION_A a.cc:1:0
NONE: FUNCTION_BB bb.cc:1:0
PARTIAL: FUNCTION_CCC ccc.cc:1:0
+ FUNCTION_CCC ccc.cc:1:0
- FUNCTION_CCC ccc.cc:2:0
- FUNCTION_CCC ccc.cc:3:0
추후 결정(TBD)