为什么不呢?我们目前正尝试对某些非常大且非常慢的目标进行模糊测试,而 libFuzzer、AFL 之类的工具不一定能很好地扩展。关于我们的一个动机示例,请参阅 SiliFuzz。在开发 Centipede 的过程中,我们计划试验一些现有模糊测试引擎未曾尝试的大规模差分模糊测试新方法。
值得注意的特性:
开箱即用地支持基于 libFuzzer 的模糊测试目标。要使用你最喜欢的 LLVMFuzzerTestOneInput(),你只需使用 Centipede 的编译器和链接器选项来构建目标。
正在开发中。我们目前在一个小团队中针对少数目标测试 centipede。除非你是 Centipede 项目的成员,或者想帮助我们,否则你可能暂时还不想继续读下去。
扩展性。其目标是能够以极低的通信开销并发运行任意数量的任务。我们目前使用 100 个本地任务和 10k 个集群任务进行测试。
进程外运行。目标在单独的进程中运行。其中的任何崩溃都不会影响模糊测试器。Centipede 也可以进程内使用,但此模式并非主要目标。如果你的目标小而快,你可能仍然需要 libFuzzer。
通过单独的构建实现与 sanitizer 的集成。如果在模糊测试过程中你想借助 ASAN、MSAN 或 TSAN 查找 bug,你需要为每个 sanitizer 提供单独的二进制文件,同时为 Centipede 自身提供一个主二进制文件。主二进制文件不应使用任何 sanitizer。
内部接口的任何部分都不稳定。在此阶段任何内容都可能发生变化。
一种为目标生成无限输入流并编排执行的程序。
二进制文件、库、API,或者更确切地说,任何能够将字节作为输入并产生某种覆盖率数据作为输出的东西。libFuzzer 的目标可以是 Centipede 的目标。更多信息请阅读此处。
可以馈送给目标的一串字节。输入可以是任意字节包,也可以是某种结构化数据,例如序列化的 proto。
表示目标某种独特行为的数字。例如,特征 1234567 可能表示目标中编号为 987 的基本块已被执行 7 次。使用目标执行输入时,模糊测试器会收集执行期间观察到的特征。
与某个特定输入关联的一组特征。
关于目标执行给定输入时行为的一些信息。覆盖率通常表示为输入在目标中触发的特征集。
一个将字节作为输入并输出该输入的小型随机变异的函数。另请参阅:结构感知模糊测试。
一个知道如何将输入馈送到目标并返回覆盖率(即执行)的函数。
一个可定制的模糊测试引擎,允许用户替换变异器和执行器。
一个实现 Centipede 模糊测试器所期望的执行器接口的库。runner 知道如何运行经 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 - 你需要与模糊测试目标链接的库(runner)。$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
注意:你可能需要将 llvm-symbolizer 添加到你的 $PATH 中,Centipede 的某些功能才能正常工作。symbolizer 可以作为 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