
Verteilte coverage-gesteuerte Fuzzing-Engine, kompatibel mit libFuzzer-Targets; skaliert auf Tausende gleichzeitiger Jobs, nutzt Sanitizer und Korpus-Destillation, um Bugs aufzudecken.
Warum nicht? Wir versuchen derzeit, einige sehr große und sehr langsame Targets zu fuzzen, für die libFuzzer, AFL und Ähnliches nicht unbedingt gut skalieren. Ein Beispiel, das uns motiviert, ist SiliFuzz. Während wir an Centipede arbeiten, planen wir, neue Ansätze für differenzielles Fuzzing in großem Maßstab auszuprobieren, die bestehende Fuzzing-Engines nicht versuchen.
Besondere Merkmale:
Out-of-the-box-Unterstützung für libFuzzer-basierte Fuzz-Targets. Um dein bevorzugtes LLVMFuzzerTestOneInput() zu verwenden, musst du dein Target nur mit Centipedes Compiler- und Linker-Optionen bauen.
Work-in-progress. Wir testen Centipede in einem kleinen Team mit einigen Targets. Wenn du nicht Teil des Centipede-Projekts bist oder uns helfen möchtest, willst du wahrscheinlich noch nicht weiterlesen.
Skalierung. Die Absicht ist, eine beliebige Anzahl von Jobs gleichzeitig ausführen zu können, mit sehr geringem Kommunikationsaufwand. Wir testen derzeit mit 100 lokalen Jobs und mit 10k Jobs in einem Cluster.
Out-of-process. Das Target läuft in einem separaten Prozess. Abstürze darin beeinträchtigen den Fuzzer nicht. Centipede kann auch prozessintern verwendet werden, aber dieser Modus ist nicht das Hauptziel. Wenn dein Target klein und schnell ist, willst du wahrscheinlich weiterhin libFuzzer verwenden.
Die Integration mit den Sanitizern wird über separate Builds erreicht. Wenn du während des Fuzzings Fehler mit ASAN, MSAN oder TSAN finden möchtest, musst du separate Binärdateien für jeden Sanitizer sowie eine Haupt-Binärdatei für Centipede selbst bereitstellen. Die Haupt-Binärdatei sollte keinen der Sanitizer verwenden.
Kein Teil der internen Schnittstelle ist stabil. In dieser Phase kann sich alles ändern.
Ein Programm, das einen unendlichen Strom von Eingaben für ein Target erzeugt und die Ausführung orchestriert.
Ein Binary, eine Bibliothek, eine API oder eigentlich alles, das Bytes als Eingabe konsumieren und irgendeine Art von Coverage-Daten als Ausgabe erzeugen kann. Ein libFuzzer-Target kann als Centipede-Target verwendet werden. Mehr dazu hier.
Eine Bytefolge, die einem Target zugeführt werden kann. Die Eingabe kann eine beliebige Ansammlung von Bytes oder strukturierte Daten sein, z. B. ein serialisiertes Proto.
Eine Zahl, die ein eindeutiges Verhalten des Targets repräsentiert. Zum Beispiel kann ein Feature 1234567 die Tatsache darstellen, dass der Basisblock 987 im Target siebenmal ausgeführt wurde. Wenn eine Eingabe mit dem Target ausgeführt wird, sammelt der Fuzzer die Features, die während der Ausführung beobachtet wurden.
Eine Menge von Features, die mit einer bestimmten Eingabe verknüpft ist.
Informationen über das Verhalten des Targets, wenn es eine bestimmte Eingabe ausführt. Coverage wird üblicherweise als Feature-Set dargestellt, das die Eingabe im Target ausgelöst hat.
Eine Funktion, die Bytes als Eingabe nimmt und eine kleine zufällige Mutation der Eingabe ausgibt. Siehe auch: strukturbewusstes Fuzzing.
Eine Funktion, die weiß, wie man eine Eingabe in ein Target einspeist und dafür Coverage zurückbekommt (d. h. ausführt).
Eine anpassbare Fuzzing-Engine, die dem Benutzer erlaubt, den Mutator und den Executor zu ersetzen.
Eine Bibliothek, die das Executor-Interface implementiert, das der Centipede-Fuzzer erwartet. Der Runner weiß, wie man ein sancov-instrumentiertes Target ausführt, die resultierende Coverage sammelt und sie an Centipede zurückgibt. Potenzielle Centipede-Fuzz-Targets können mit dieser Bibliothek gelinkt werden, um sie durch Centipede ausführbar zu machen.
Eine Menge von Eingaben.
Ein Prozess, bei dem eine Teilmenge aus einem größeren Corpus ausgewählt wird, sodass die Teilmenge dieselben Coverage-Features wie das ursprüngliche Corpus aufweist.
Eine Datei, die eine Teilmenge des Corpus darstellt, und eine weitere Datei, die Feature-Sets für dieselbe Teilmenge des Corpus darstellt.
Shard B in Shard A zusammenzuführen bedeutet: Für jede Eingabe in Shard B, die Features enthält, die in Shard A fehlen, diese Eingabe zu A hinzuzufügen.
Ein einzelner Fuzzer-Prozess. Ein Job schreibt nur in einen Shard, kann aber mehrere Shards lesen.
Ein lokales oder entferntes Verzeichnis, das Daten enthält, die von einem Fuzzer erzeugt oder verbraucht werden.
git clone https://github.com/google/centipede.git
cd centipede
CENTIPEDE_SRC=`pwd`
BIN_DIR=$CENTIPEDE_SRC/bazel-bin
bazel build -c opt :all
Was du für die folgenden Schritte benötigst:
$BIN_DIR/centipede - die Binärdatei der Engine (des Fuzzers).$BIN_DIR/libcentipede_runner.pic.a - die Bibliothek, die du mit deinem Fuzz-Target linken musst (der Runner).$CENTIPEDE_SRC/clang-flags.txt - empfohlene Clang-Compiler-Flags für das Target.Du kannst diese Dateien an ihrem Platz lassen oder sie irgendwohin kopieren.
Wir stellen zwei Beispiele für das Bauen des Targets bereit: ein winziges Ein-Datei-Target und libpng. Sobald du dein Target gebaut hast, fahre mit dem Schritt zum Ausführen des Fuzz-Targets fort.
Dieses Beispiel verwendet eines der einfachen Beispiel-Fuzz-Targets, auch Puzzles genannt, die im Centipede-Repository enthalten sind.
HINWEIS: Die folgenden Befehle verwenden die Flags aus $CENTIPEDE_SRC/clang-flags.txt. Du kannst auch einen anderen Satz von Instrumentierungs-Flags wählen: clang-flags.txt bietet nur eine einfache Standardoption.
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
Dieser Schritt linkt das gerade gebaute Fuzz-Target mit libcentipede_runner.pic.a und anderen erforderlichen Bibliotheken.
clang++ $BIN_DIR/$FUZZ_TARGET.o $BIN_DIR/libcentipede_runner.pic.a \
-ldl -lrt -lpthread -o $BIN_DIR/$FUZZ_TARGET
Springe zum Ausführungsschritt.
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
Das lokale Ausführen erreicht zwar nicht den vollen Maßstab, kann aber während der Entwicklungsphase des Fuzzers nützlich sein. Wir empfehlen, sowohl den Fuzzer als auch das Target vor dem Ausführen in ein lokales Verzeichnis zu kopieren, um ein Netzwerkdateisystem nicht zu belasten.
WD=$HOME/centipede_run
mkdir -p $WD
HINWEIS: Möglicherweise musst du llvm-symbolizer zu deinem $PATH hinzufügen, damit einige der Centipede-Funktionen funktionieren. Der Symbolizer kann als Teil der LLVM-Distribution installiert werden:
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
Sieh dir an, was sich im Arbeitsverzeichnis befindet
tree $WD
...
├── <fuzz target name>-d9d90139ee2ccc687f7c9d5821bcc04b8a847df5
│ └── features.0
└── corpus.0
WARNUNG: Überschreite für das --j-Flag nicht die Anzahl der Kerne deines Rechners.
rm -rf $WD/*
$BIN_DIR/centipede --binary=$BIN_DIR/$FUZZ_TARGET --workdir=$WD --num_runs=100 --j=5
Sieh dir an, was sich im Arbeitsverzeichnis befindet:
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
Jeder Centipede-Shard deckt in der Regel nicht alle Features ab, die das gesamte Corpus abdeckt. Um das Corpus zu destillieren, muss ein Centipede-Prozess alle Shards lesen. Derzeit funktioniert die Destillation wie folgt:
--distill_shards=N. Dadurch erzeugen die ersten N Jobs N unabhängige destillierte Corpus-Dateien (eine pro Job). Jedes der destillierten Corpora sollte dieselben Features aufweisen wie das vollständige Corpus, aber die Eingaben können zwischen diesen destillierten Corpora sehr unterschiedlich sein.Wenn du das destillierte Corpus zusätzlich in ein Verzeichnis im libFuzzer-Stil exportieren musst (lokales Verzeichnis mit einer Datei pro Eingabe), füge --corpus_dir=DIR hinzu.
Centipede erzeugt einen einfachen Coverage-Bericht in Form einer Textdatei. Der Shard 123 erzeugt eine Datei workdir/coverage-report-BINARY.000123.txt, bevor das eigentliche Fuzzing beginnt; der Bericht spiegelt also die Coverage wider, die vom Shard 123 nach dem Laden des Corpus beobachtet wurde.
Der Bericht zeigt Funktionen, die vollständig abgedeckt sind (alle Kontrollflusskanten werden mindestens einmal beobachtet), nicht abgedeckt oder teilweise abgedeckt sind. Für teilweise abgedeckte Funktionen enthält der Bericht symbolische Informationen für alle abgedeckten und nicht abgedeckten Kanten.
Der Bericht sieht ungefähr so aus:
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