bazel-contrib/rules_fuzzing
GitHub: bazel-contrib/rules_fuzzing
Bazel 的 Starlark 扩展规则集,用于在 C++ 和 Java 项目中定义和运行模糊测试,自动发现安全性与稳定性缺陷。
Stars: 91 | Forks: 25
# Bazel 模糊测试规则
此代码库包含 [Bazel](https://bazel.build/) [Starlark 扩展](https://docs.bazel.build/versions/master/skylark/concepts.html),用于在 Bazel 项目中定义模糊测试。
[Fuzzing](https://en.wikipedia.org/wiki/Fuzzing) 是一种发现软件安全性和稳定性 bug 的有效技术。Fuzzing 通过使用自动生成的数据调用被测代码(例如,库 API)并观察其执行情况来发现不正确的行为,例如内存损坏或不变量失败。在[此处](https://github.com/google/fuzzing)阅读更多关于 fuzzing 的信息、其他示例、最佳实践和其他资源。
该规则库目前支持 C++ 和 Java 模糊测试。未来可能会增加对其他语言的支持。
## 功能一览
* C++ 和 Java fuzzing,开箱即支持多种 fuzzing 引擎:
* C++: [libFuzzer][libfuzzer-doc] 和 [Honggfuzz][honggfuzz-doc]
* Java: [Jazzer][jazzer-doc]
* 多种 sanitizer 配置:
* [Address Sanitizer][asan-doc]
* [Memory Sanitizer][msan-doc]
* [Undefined Behavior Sanitizer][ubsan-doc]
* 语料库和字典。
* 通过简单的“bazel run/test”命令即可构建和运行模糊测试。
* 无需了解每个 fuzzing 引擎的细节。
* 无需显式管理其语料库或字典。
* 开箱即用的 [OSS-Fuzz](https://github.com/google/oss-fuzz) 支持,大幅简化了[项目集成工作][bazel-oss-fuzz]。
* 回归测试支持,在持续集成中非常有用。
* 自定义选项:
* 定义额外的 fuzzing 引擎。
* 自定义模糊测试规则的行为。
## 快速入门
本节将引导您完成在 Bazel 项目中设置 fuzzing 并编写第一个模糊测试的步骤。我们假设您的机器上[已安装](https://docs.bazel.build/versions/main/install.html) Bazel。
### 前置条件
模糊测试规则已在 Bazel 4.0.0 或更高版本上进行了测试。通过运行 `bazel --version` 检查您的 Bazel 版本。
libFuzzer 引擎至少需要 Clang 6.0。Honggfuzz 同时适用于 clang 和 gcc(8 或更高版本),并且需要 `libunwind-dev` 和 `libblocksruntime-dev` 包:
```
$ sudo apt-get install libunwind-dev libblocksruntime-dev
```
Java 模糊测试需要 Clang 和 LLD 链接器:
```
$ sudo apt-get install clang lld
```
### 配置 WORKSPACE
将以下内容添加到您的 `WORKSPACE` 文件中:
```
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_fuzzing",
sha256 = "23bb074064c6f488d12044934ab1b0631e8e6898d5cf2f6bde087adb01111573",
strip_prefix = "rules_fuzzing-0.3.1",
urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.1.zip"],
)
load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
rules_fuzzing_dependencies()
load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
rules_fuzzing_init()
load("@fuzzing_py_deps//:requirements.bzl", "install_deps")
install_deps()
```
### 配置 .bazelrc 文件
最好为开发过程中将使用的模糊测试配置创建命令简写。在我们的示例中,让我们为 libFuzzer + Address Sanitizer 创建一个配置。在您的 `.bazelrc` 文件中,添加以下内容:
```
# 强制在 C++ 构建中使用 Clang。
build --action_env=CC=clang
build --action_env=CXX=clang++
# 定义 --config=asan-libfuzzer 配置。
build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
```
fuzzing 引擎和 sanitizer 的其他组合示例可以在[用户指南](/docs/guide.md#configuring-the-bazelrc-file)中找到。
### 定义 C++ 模糊测试
C++ 模糊测试使用 [`cc_fuzz_test` 规则](/docs/cc-fuzzing-rules.md#cc_fuzz_test)指定。在最基本的形式中,模糊测试需要一个实现 fuzz driver 入口点的源文件。
让我们创建一个表现出缓冲区溢出的模糊测试。在您的工作区根目录下创建一个 `fuzz_test.cc` 文件,如下所示:
```
#include
#include
#include
void TriggerBufferOverflow(const uint8_t *data, size_t size) {
if (size >= 3 && data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' &&
data[size] == 'Z') {
fprintf(stderr, "BUFFER OVERFLOW!\n");
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
TriggerBufferOverflow(data, size);
return 0;
}
```
现在让我们在 `BUILD` 文件中定义其构建目标:
```
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
cc_fuzz_test(
name = "fuzz_test",
srcs = ["fuzz_test.cc"],
)
```
### 运行模糊测试
您现在可以构建并运行模糊测试了。对于定义的每个模糊测试 ``,框架会自动生成一个启动器工具 `_run`,它将根据指定的配置构建并运行模糊测试:
```
$ bazel run --config=asan-libfuzzer //:fuzz_test_run
```
我们的 libFuzzer 测试将开始运行,并立即发现代码中的缓冲区溢出问题:
```
INFO: Seed: 2957541205
INFO: Loaded 1 modules (8 inline 8-bit counters): 8 [0x5aab10, 0x5aab18),
INFO: Loaded 1 PC tables (8 PCs): 8 [0x5aab18,0x5aab98),
INFO: 755 files found in /tmp/fuzzing/corpus
INFO: 0 files found in fuzz_test_corpus
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 35982 bytes
INFO: seed corpus: files: 755 min: 1b max: 35982b total: 252654b rss: 35Mb
#756 INITED cov: 6 ft: 7 corp: 4/10b exec/s: 0 rss: 47Mb
=================================================================
==724294==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000047a74 at pc 0x0000005512d9 bp 0x7fff3049d270 sp 0x7fff3049d268
```
崩溃产物保存在 `/tmp/fuzzing/artifacts` 下,可供进一步检查。
### Java 模糊测试
您可以通过 [Jazzer][jazzer-doc] fuzzing 引擎编写 `java_fuzz_test`。
为了使用 Jazzer,建议像上面 C++ libFuzzer 那样定义一个 `.bazelrc` 配置:
```
# 强制在所有构建中使用 Clang(Jazzer 至少需要 Clang 9)。
build --action_env=CC=clang
build --action_env=CXX=clang++
# 为不带 sanitizer 的 Jazzer 定义 --config=jazzer(仅限 Java)。
build:jazzer --@rules_fuzzing//fuzzing:java_engine=@rules_fuzzing//fuzzing/engines:jazzer
build:jazzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=jazzer
build:jazzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=none
# 为 Jazzer + ASAN 定义 --config=asan-jazzer。
build:asan-jazzer --@rules_fuzzing//fuzzing:java_engine=@rules_fuzzing//fuzzing/engines:jazzer
build:asan-jazzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=jazzer
build:asan-jazzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
```
Java 模糊测试使用 [`java_fuzz_test` 规则](/docs/java-fuzzing-rules.md#java_fuzz_test)指定。在最基本的形式中,Java 模糊测试由一个包含类的 `.java` 文件组成,该类定义了一个 `public static fuzzerTestOneInput(byte[] input)` 函数。
在您的工作区根目录下创建 `src/com/example/JavaFuzzTest.java` 文件,如下所示:
```
package com.example;
public class JavaFuzzTest {
public static void fuzzerTestOneInput(byte[] data) {
if (data.length >= 3 && data[0] == 'F' && data[1] == 'U' &&
data[2] == 'Z' && data[data.length] == 'Z') {
throw new IllegalStateException(
"ArrayIndexOutOfBoundException thrown above");
}
}
}
```
现在您应该在 `BUILD` 文件中定义相应的目标,它看起来非常像常规的 `java_binary`:
```
load("@rules_fuzzing//fuzzing:java_defs.bzl", "java_fuzz_test")
java_fuzz_test(
name = "JavaFuzzTest",
srcs = ["src/com/example/JavaFuzzTest.java"],
# target_class is not needed if using the Maven directory layout.
# target_class = "com.example.JavaFuzzTest",
)
```
现在,您可以通过运行以下命令来使用 Jazzer 引擎启动 fuzzer:
```
$ bazel run --config=jazzer //:JavaFuzzTest_run
```
Jazzer 很快就会触发 `ArrayIndexOutOfBoundsException`:
```
INFO: Instrumented com.example.JavaFuzzTest (took 98 ms, size +96%)
INFO: Seed: 4010526312
INFO: Loaded 1 modules (512 inline 8-bit counters): 512 [0x7fae23acd800, 0x7fae23acda00),
INFO: Loaded 1 PC tables (512 PCs): 512 [0x7fae226c9800,0x7fae226cb800),
INFO: 16 files found in /tmp/fuzzing/corpus
INFO: 0 files found in test/JavaFuzzTest_corpus
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: seed corpus: files: 16 min: 1b max: 19b total: 210b rss: 199Mb
#18 INITED cov: 3 ft: 3 corp: 2/5b exec/s: 0 rss: 200Mb
...
#6665 REDUCE cov: 5 ft: 5 corp: 4/10b lim: 63 exec/s: 0 rss: 202Mb L: 3/3 MS: 3 ChangeBit-ChangeBit-EraseBytes-
== Java Exception: java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at com.example.JavaFuzzTest.fuzzerTestOneInput(JavaFuzzTest.java:5)
```
### OSS-Fuzz 集成
一旦您编写并测试了模糊测试,您就应该在持续的 fuzzing 基础设施上运行它,以便它开始生成测试并在您的代码中发现新的崩溃。
C++ 和 Java 模糊测试规则为 [OSS-Fuzz](https://github.com/google/oss-fuzz)(Google 为开源项目提供的免费持续 fuzzing 基础设施)提供了开箱即用的支持。请阅读其 [Bazel 项目指南][bazel-oss-fuzz] 获取详细说明。
## 接下来做什么?
恭喜,您已经使用 Bazel 规则构建并运行了您的第一个模糊测试!
查看 [`examples/`](examples/) 目录,其中展示了其他功能。请阅读[用户指南](/docs/guide.md)获取详细的使用说明。
标签:Bazel, C++, JS文件枚举, Starlark, 数据擦除, 软件质量