- 测试
- 应用安全
- 后端
【免费下载链接】clusterfuzz
Scalable fuzzing infrastructure.
导读
在 ClusterFuzz 的 bot 文件系统布局中,bot/inputs/fuzzer-testcases/是一个专门存放模糊测试(fuzzing)任务运行期间所生成测试用例(testcase)的临时工作目录。本文以 fuzzer-testcases/README.md 为主线,深入讲解该目录的两大设计要点:任务结束后自动清空的占位目录语义,以及通过 tmpfs 挂载将高频读写文件驻留内存、以换取更高性能和更低磁盘成本的优化手段。读完本文,你将掌握该目录在 ClusterFuzz 环境变量体系(FUZZ_INPUTS/FUZZ_INPUTS_DISK)中的位置、它与磁盘版测试用例目录的分工逻辑,以及生产环境中完整的 tmpfs 挂载配置方式。
一、占位目录:fuzzer 生成测试用例的临时工作区
bot/inputs/fuzzer-testcases/README.md用一句话定义了该目录的本质:
Placeholder for testcases generated by a fuzzer. This gets cleared after a task is finished.
即它是"由 fuzzer 生成的测试用例"的占位目录(placeholder),且生命周期严格受限——一个任务(task)结束后,目录内容会被清空。这意味着该目录不是持久化存储,而是 bot 在单次模糊测试任务运行期间的临时工作区:fuzzer 在内存(或磁盘)中产出新输入,测试用例经复现、分析、归档之后,临时文件即失去价值并被回收。
从源码结构看,这一"临时工作区 + 任务结束即清理"的语义在 bot 任务执行体系中是普遍约定:
- fuzz_task.py 在启动模糊测试任务时,将
self.testcase_directory直接指向FUZZ_INPUTS(即本目录),后续 fuzzer 产出的所有测试用例都落在这里; - corpus_pruning_task.py 在语料剪枝(corpus pruning)任务中,会把已上传测试用例的
absolute_path属性指向FUZZ_INPUTS下的testcase文件,而不是本地隔离(quarantine)目录,以保证后续处理路径一致; - 各任务运行完毕、进入下一轮任务前,系统会统一清空测试用例目录(详见下文第三节)。
因此,任何部署者都不应把重要数据放入该目录,也不应在任务之间假定目录内容仍然存在。
二、环境变量映射:FUZZ_INPUTS 与 bot 目录布局
该目录在代码中通过环境变量FUZZ_INPUTS被引用。ClusterFuzz 在 bot 启动时统一初始化一套"输入目录"布局,相关逻辑位于 environment.py 的set_bot_environment():
inputs_dir = os.path.join(bot_dir, 'inputs') os.environ['INPUT_DIR'] = inputs_dir os.environ['CRASH_STACKTRACES_DIR'] = os.path.join(inputs_dir, 'crash-stacks') os.environ['FUZZERS_DIR'] = os.path.join(inputs_dir, 'fuzzers') os.environ['DATA_BUNDLES_DIR'] = os.path.join(inputs_dir, 'data-bundles') os.environ['FUZZ_INPUTS'] = os.path.join(inputs_dir, 'fuzzer-testcases') os.environ['FUZZ_INPUTS_MEMORY'] = os.environ['FUZZ_INPUTS'] os.environ['FUZZ_INPUTS_DISK'] = os.path.join(inputs_dir, 'disk') os.environ['FUZZ_DATA'] = os.path.join(inputs_dir, 'fuzzer-common-data-bundles') os.environ['IMAGES_DIR'] = os.path.join(inputs_dir, 'images') os.environ['SYMBOLS_DIR'] = os.path.join(inputs_dir, 'symbols') os.environ['USER_PROFILE_ROOT_DIR'] = os.path.join(inputs_dir, 'user-profile-dirs')要点归纳如下:
| 环境变量 | 对应目录(相对 bot 根目录) | 用途 |
|---|---|---|
FUZZ_INPUTS | bot/inputs/fuzzer-testcases | 内存版测试用例目录(本文主题) |
FUZZ_INPUTS_MEMORY | 与FUZZ_INPUTS相同 | 显式别名,强调其内存驻留属性 |
FUZZ_INPUTS_DISK | bot/inputs/disk | 磁盘版测试用例目录,用于放不下内存的大文件 |
其中FUZZ_INPUTS_MEMORY与FUZZ_INPUTS被设置为同一路径,这种"双名一址"的写法在语义上明确区分了"内存目录"与"磁盘目录",方便代码在引用时表达意图。磁盘版目录bot/inputs/disk的说明见 disk/README.md,其中同样声明"任务结束后会被清空",并补充说明它是"始终位于磁盘上、用于存放无法放入内存的大测试用例"的目录。
三、tmpfs 内存化:性能与磁盘成本的双重优化
fuzzer-testcases/README.md的第二段给出了该目录最关键的部署建议:
For better performance and lower disk usage costs, this directory can be mounted as tmpfs and reside in memory.
fuzzing 任务对测试用例目录的读写极为频繁(每个新变异输入都要写入、复现时还要反复读取),如果这些 I/O 都落盘,既拖慢任务速度,又会持续磨损/占用磁盘空间。将目录挂载为 tmpfs 后,文件内容直接驻留内存,读写不再经过磁盘,同时 tmpfs 本身不占用持久化存储空间,从而同时优化了性能和磁盘成本。
3.1 生产环境中的挂载实现
ClusterFuzz 的官方 Docker 基镜像启动脚本 setup_clusterfuzz.sh 给出了完整的落地方案。脚本在DISABLE_MOUNTS环境变量未设置时执行一组挂载:
# Some configurations (e.g. hosts) run many instances of ClusterFuzz. Don't # set up mounts in this case. if [[ -z "$DISABLE_MOUNTS" ]]; then # Setup Tmpfs dirs for frequently accessed files to save disk I/O. mount -t tmpfs -o size=250M,mode=777 tmpfs $INSTALL_DIRECTORY/clusterfuzz/bot/inputs/fuzzer-testcases/ mount -t tmpfs -o size=10M,mode=777 tmpfs $INSTALL_DIRECTORY/clusterfuzz/bot/logs/ mount -t tmpfs -o size=90M,mode=777 tmpfs $BOT_TMPDIR # Setup mount to limit disk space for fuzzer testcases disk directory. FUZZER_TESTCASES_DISK_FILE=$INSTALL_DIRECTORY/fuzzer-testcases.mnt fallocate -l 8GiB $FUZZER_TESTCASES_DISK_FILE mkfs.ext4 -F $FUZZER_TESTCASES_DISK_FILE # mkfs.ext4 seems to remove the previous allocation, so do it again. fallocate -l 8GiB $FUZZER_TESTCASES_DISK_FILE mount -o loop $FUZZER_TESTCASES_DISK_FILE $INSTALL_DIRECTORY/clusterfuzz/bot/inputs/disk fi其中与本文主题直接相关的关键参数:
- 挂载点:
$INSTALL_DIRECTORY/clusterfuzz/bot/inputs/fuzzer-testcases/,即 bot 安装目录下的对应路径; - 文件系统类型:
tmpfs,内容保存在内存(及可能的 swap)中; size=250M:为该 tmpfs 设定 250 MiB 容量上限。这是有意的容量约束——fuzzer 产出的测试用例总量被限制在这个范围内,避免无限增长耗尽内存;mode=777:允许 bot 进程以任意用户身份在该目录下创建/读写文件,适配 bot 以非特权用户运行的实际场景。
与之一同挂载的还有bot/logs/(10M)和$BOT_TMPDIR(90M),三者共同构成"高频临时数据放内存"的优化策略。DISABLE_MOUNTS则用于某些特殊部署(例如一台宿主机运行多个 ClusterFuzz 实例)时关闭这些挂载,避免每个实例都抢占内存。
3.2 配套的磁盘目录与容量上限
值得注意:脚本同时创建了磁盘版目录bot/inputs/disk,方式不是直接挂载磁盘,而是先fallocate分配一个 8 GiB 的稀疏文件,格式化为 ext4 后以 loop 设备挂载。这样做的目的是给 fuzzer 测试用例的磁盘目录设置硬性容量上限(8 GiB),防止失控的用例把宿主磁盘写满。由于mkfs.ext4会清除之前的分配,脚本在格式化后再次fallocate补回文件大小。
内存目录(250M)与磁盘目录(8G)的容量差异,正是后续"大测试用例走磁盘"分流机制的物理基础。
四、内存目录与磁盘目录的分工:has_large_testcases 机制
既然内存 tmpfs 只有 250M,那些体积超过内存容量的测试用例(例如大型语料库、超大输入文件)显然不能写入fuzzer-testcases。ClusterFuzz 在 fuzzer 实体上提供了has_large_testcases标志位来显式声明这一点,见 data_types.py:
# If this flag is set, fuzzer generates the testcase in the larger directory # on disk |FUZZ_INPUTS_DISK|, rather than smaller tmpfs one (FUZZ_INPUTS). has_large_testcases = ndb.BooleanProperty(default=False)该标志位在任务设置阶段被消费。在 setup.py 的 fuzzer/数据包更新逻辑中:
# If the fuzzer generates large testcases or a large number of small ones # that don't fit on tmpfs, then use the larger disk directory. if fuzzer.has_large_testcases: testcase_disk_directory = environment.get_value('FUZZ_INPUTS_DISK') environment.set_value('FUZZ_INPUTS', testcase_disk_directory)即:当 fuzzer 声明has_large_testcases=True时,FUZZ_INPUTS会被临时改写为FUZZ_INPUTS_DISK(磁盘目录),从而让所有下游逻辑自动把测试用例写入磁盘而非内存。这与 unpack_task.py 中的注释互为印证:
# We can't use FUZZ_INPUTS directory since it is constrained # by tmpfs limits. testcases_directory = environment.get_value('FUZZ_INPUTS_DISK')解包(unpack)任务需要把上传的测试用例压缩包解压到本地,动辄数百 MB,受 tmpfs 容量限制,因此必须落到FUZZ_INPUTS_DISK。
另一方面,bot/inputs/disk/README.md还提到"它是提供语料库(corpus)的 fuzzer 的默认目录"——语料库通常体量大、可复用,天然适合磁盘而非内存。
五、任务结束后的清理:clear_testcase_directories
README 强调"任务结束后目录被清空",其实现位于 shell.py 的clear_testcase_directories():
def clear_testcase_directories(): """Clears the testcase directories.""" remove_directory(environment.get_value('FUZZ_INPUTS'), recreate=True) remove_directory(environment.get_value('FUZZ_INPUTS_DISK'), recreate=True) if environment.is_android(): from clusterfuzz._internal.platforms import android android.device.clear_testcase_directory() if environment.is_trusted_host(): from clusterfuzz._internal.bot.untrusted_runner import file_host file_host.clear_testcase_directories()该函数在每轮任务结束后被调用,统一处理四类清理目标:
- 内存目录
FUZZ_INPUTS:删除全部内容并重新创建目录本身(recreate=True),确保下一个任务拿到干净的目录; - 磁盘目录
FUZZ_INPUTS_DISK:同样清空重建; - Android 设备目录:若运行在 Android 平台上,同步清理设备端的测试用例目录(对应 device.py 中读取的
FUZZ_INPUTS); - 受信任宿主机(trusted host):在 untrusted runner 架构下,还需通过
file_host.clear_testcase_directories()清空 worker 侧对应目录,相关集成测试见 untrusted_runner_integration_test.py。
这一"清空重建"而非"只删文件"的做法,保证了即使 tmpfs 挂载点或目录权限发生变化,目录本身也始终以正确权限就位。
六、更多使用场景:从 NFS 减压到机器人专属用例
除了作为 fuzzer 产出的临时落点,FUZZ_INPUTS还被多个子系统复用,体现了该目录"高频读写、就近存放"的设计价值:
- 避免 NFS 高负载:utils.py 的
get_bot_testcases_file_path()把"bot 专属测试用例文件"(格式为.<bot_name>_testcases)放在FUZZ_INPUTS下,注释明确说明这是为了在input_directory是云存储数据包(data bundle)时,避免把高频读写压力打到 NFS 服务器上。函数还做了降级处理:本地测试(local fuzzer testing)时FUZZ_INPUTS可能未定义,此时退回使用input_directory。 - HTTP 资源依赖的本地映射:testcase_manager.py 在解析 Android 测试用例的远程依赖时,会将设备目录
DEVICE_TESTCASES_DIR替换为本地FUZZ_INPUTS,确保file://依赖能解析到本机实际文件。 - 测试用例路径解析:setup.py 在解析 testcase 绝对路径时,默认以
FUZZ_INPUTS作为相对路径的基目录,将相对路径拼接为完整路径。
在 untrusted runner(非可信执行)架构中,FUZZ_INPUTS还被用作 host 与 worker 之间文件 rebase 的基准目录,参见 file_host.py。
七、小结
bot/inputs/fuzzer-testcases是 ClusterFuzz bot 上"小而关键"的临时目录:作为 fuzzer 生成测试用例的占位目录,它通过环境变量FUZZ_INPUTS贯穿任务执行、依赖解析与清理全流程;通过 tmpfs 内存挂载(250M 容量上限)换来高频 I/O 的高性能与零磁盘占用;通过与FUZZ_INPUTS_DISK(8G loop 挂载的磁盘目录)的has_large_testcases分流机制,保证大用例不撑爆内存;任务结束后由clear_testcase_directories()统一清空重建。理解这一目录的设计与配置,是部署、调优 ClusterFuzz bot 存储与 I/O 行为的基础,也是排查"测试用例目录空间不足""任务间数据残留"等问题的第一现场。
- 测试
- 应用安全
- 后端
【免费下载链接】clusterfuzz
Scalable fuzzing infrastructure.
相关推荐
pipreqs测试数据设计:_data目录用例解析
pipreqs测试数据设计:_data目录用例解析 在Python项目开发中,依赖管理是确保项目可移植性和稳定性的关键环节。pipreqs作为一款自动生成req
开发工具CLIClusterFuzz Bot 日志体系完全指南:bot/logs 目录日志文件的作用、配置与排查实践
ClusterFuzz Bot 日志体系完全指南:bot/logs 目录日志文件的作用、配置与排查实践 导读 ClusterFuzz 的可扩展模糊测试(scal
应用安全后端开发工具Pytest 测试隔离实战:用 tmp_path 与 monkeypatch 探究临时目录结构
Pytest 测试隔离实战:用 tmp_path 与 monkeypatch 探究临时目录结构 在测试会读写文件系统的 CLI 程序时,最怕的就是单个测试用例相
文档教程知识库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考