- 测试
- 应用安全
- 质量保障
【免费下载链接】oss-fuzz
OSS-Fuzz - continuous fuzzing for open source software.
本篇技术指南聚焦 OSS-Fuzz 生态中一项关键的自动化能力:借助 LLM(大语言模型)驱动的 Agent 自动生成 OSS-Fuzz 所需的构建脚本,并结合 CLI 工具实现"输入一个 Git 仓库、输出一个完整 OSS-Fuzz 项目"的端到端工作流。读完本文,你将掌握 Agent 化构建生成的核心算法与四个关键函数、oss-fuzz-generator generate-full命令的完整使用方式,以及如何解读三个真实项目(libcypher-parser、Yams、Moment)生成的构建脚本,并理解该方案在 225 个仓库实测中的能力边界与已知局限。
背景:自动化 OSS-Fuzz 接入的两个核心难题
将任意开源项目接入 OSS-Fuzz 并非易事。OSS-Fuzz 要求每个项目提供特定格式的构建脚本,该脚本必须运行在 OSS-Fuzz 提供的构建环境(即 base-builder 镜像)中。由此,自动化 OSS-Fuzz 接入的问题天然被拆分成两个部分:
- 创建构建脚本:为目标项目及其相关的 fuzzing harness(模糊测试驱动)编写可在 OSS-Fuzz 构建环境中运行的脚本;
- 创建 fuzzing harness:编写真正调用目标项目 API 的模糊测试入口函数。
任何试图自动化 OSS-Fuzz 接入的解决方案,都必须同时解决上述两个问题,且要能覆盖风格迥异的开源项目。
这项工作的关键目标是支撑一个完整的自动化工作流:输入一个尚未接入 OSS-Fuzz 的项目仓库(如 GitHub 仓库),输出一个可用的 OSS-Fuzz 项目——包含可工作的构建脚本和一个或多个 fuzzing harness。更进一步,这个工作流必须易于访问和部署,让开源维护者能快速利用其能力。
作为参考,一个标准的 OSS-Fuzz 项目在本仓库中的目录结构通常由三个核心文件组成,这也是自动化流程需要产出的目标产物:
project.yaml:项目元数据(主页、主仓库地址、语言、fuzzing 引擎列表等),例如 projects/example/project.yaml;Dockerfile:基于gcr.io/oss-fuzz-base/base-builder构建环境,负责拉取源码并拷贝构建脚本,例如 projects/example/Dockerfile;build.sh:在$SRC、$OUT等环境变量约定下编译 fuzz target 并拷贝到$OUT,例如 projects/example/build.sh。
在此之前,OSS-Fuzz-Gen 的精力主要集中在为已有 OSS-Fuzz 项目生成 fuzzing harness。此前一篇博客文章虽然记录了一种端到端的 OSS-Fuzz 接入方法,但其构建脚本生成基于模板策略,在应对多样化项目时存在明显局限。为此,本文介绍两项改进:
- 一种Agent 化的 LLM 构建脚本生成方法;
- 一个易于访问和运行的 CLI 工具。
总览与示例运行
方案的核心目标,是仅通过输入一个或多个 Git 仓库,就自动化地完整生成 OSS-Fuzz 项目,并输出一个或多个 OSS-Fuzz 接入集成。这些能力被打包为一个 Python 包并以 CLI 形式暴露,安装和运行都很简单。
以下示例为https://github.com/zserge/jsmn生成包含 fuzzing harness 的完整 OSS-Fuzz 项目:
# Prepare virtual environment python3.11 -m virtualenv .venv . .venv/bin/activate # Clone OSS-Fuzz-gen git clone https://github.com/google/oss-fuzz-gen cd oss-fuzz-gen # Install OSS-Fuzz-gen python3 -m pip install . # Generate fuzzers for https://github.com/zserge/jsmn echo "https://github.com/zserge/jsmn" > input.txt # Run the generation # Setup Vertex AI access: 参见 OSS-Fuzz-Gen 仓库 USAGE.md 中的 LLM access 章节 oss-fuzz-generator generate-full -i input.txt -m vertex_ai_gemini-2-flash-chat --agent -w work-1 # List the files of generated project $ ls final-oss-fuzz-projects/jsmn-agent/ build.sh Dockerfile empty-fuzzer.0.c empty-fuzzer.1.c project.yaml整个流程的第一步是安装 Python 包。目前的做法是先克隆 OSS-Fuzz-Gen 仓库,再用python -m pip install .安装。安装后的包内置了一个oss-fuzz-generatorCLI 工具,对外暴露 OSS-Fuzz 项目生成能力。随后只需配置好 LLM 运行环境(如 Vertex AI),然后调用generate-full命令即可。该命令会一次性完成构建脚本生成、fuzzing harness 生成,并将所有成功的 fuzzing harness 合并进一个 OSS-Fuzz 项目。
具体来说,oss-fuzz-generator generate-full会针对input.txt中列出的每个仓库依次执行三个步骤:
- 生成一个能够编译该项目 fuzzers 的构建脚本;
- 如果第 1 步成功,则为项目生成fuzzing harness;
- 将成功的 fuzzing harness合并为一个完整的 OSS-Fuzz 项目。
本文后续将聚焦第 1 步——Agent 化的构建脚本生成。
从生成的产物可以看到,Agent 产出的项目结构与 OSS-Fuzz 仓库中真实项目的形态完全一致:build.sh、Dockerfile、project.yaml加 fuzzing harness 源文件。你可以对照本仓库中的 projects/example 目录理解这些产物的标准形态,例如 projects/example/build.sh 展示了find批量拷贝 fuzzer 到$OUT的写法,而 projects/example/my-api-repo/do_stuff_fuzzer.cpp 展示了一个标准LLVMFuzzerTestOneInput入口的实现。
Agent 化构建脚本生成的算法原理
Agent 化构建脚本生成依赖三个核心组件:
- 初始提示词(initial prompt):向 LLM 概述为任意仓库编写构建脚本的总体任务与约束;
- Agent 执行器:负责与 LLM 通信,并在构建脚本将要运行的运行时环境中执行 LLM 提供的任意命令,使 LLM 能够探索运行时环境;
- 构建与运行回灌流程:负责运行生成的构建脚本、执行生成的 fuzzer,用真实运行结果引导 LLM 的下一步输出。
Agent 化构建生成的总体算法如下:
initial_prompt = prepare_initial_prompt(target_repository) prompt = prepare_initial_prompt(target_repository) llm_client = llm_start_chat() while should_keep_going(): llm_response = llm_client.chat(prompt) res = parse_llm_response(llm_response) if res.is_commands() { output = execute_commands(res.get_commands()); } else if (res.has_build_script()) { output = build_and_run_fuzzer(res.get_build_script(), res.get_fuzz_harness()); if (output.has_successful_build_script()) { // Success in harness generation return output } } else { // Failure happened in parsing LLM output exit() } // Prepare a next prompt for the LLM to chat. prompt = prepare_next_prompt(output);当算法执行到return output这一行时,即认为构建脚本生成成功。这一行只有在 LLM 创建的构建脚本附带一个 fuzz harness,并且该 harness 能够成功编译、链接到目标仓库代码时才会到达。
算法中有四个关键函数:
parse_llm_response:接收 LLM 返回的原始文本,将其转换为两类结果之一:(1) 需要在构建 fuzzer 的运行时环境中执行的一组命令;(2) 一个构建脚本及其携带的 fuzzing harness 源码。prepare_initial_prompt会事先指示 LLM 以某种标准格式输出(例如用 XML 标签包裹输出),以便可靠解析;execute_commands:当parse_llm_response判定 LLM 返回的是命令列表时,此函数在构建脚本将要运行的运行时环境中执行这些命令。核心价值在于让 LLM 能够探索、理解并测试运行时环境;命令执行结果会返回给 LLM,由于 Agent 运行在循环中,LLM 可以持续下发命令、解读输出并据此行动;build_and_run_fuzzer:当parse_llm_response判定 LLM 返回的是构建脚本(可能附带 fuzzing harness,也可能为空)时,此函数在运行时环境中构建这些产物。构建输出会被分析:若成功构建出 harness,则流程结束;若构建失败,则构建脚本的执行输出会被保存并最终回传给 LLM;prepare_next_prompt:当本轮迭代未产生成功构建脚本时(可能是 LLM 返回了命令列表,也可能是构建脚本构建失败),该函数用execute_commands和build_and_run_fuzzer的输出构造下一轮提示词。提示词以轻量文本描述包裹运行输出,作为下一轮 Agent 迭代的输入传给llm_client.chat。
算法成功时输出的构建脚本,将成为 OSS-Fuzz 项目的基础,随后即可复用 OSS-Fuzz-Gen 的核心能力为该项目生成 fuzzing harness。
从算法可以看出,Agent 的核心优势在于它拥有执行权限——可以在真实运行时环境中运行命令、安装依赖、修改源码,观察输出,然后不断迭代。这与纯模板化方案有本质区别:模板只能覆盖已知模式,而 Agent 可以针对每个项目的特殊情况"临场发挥"。
实测评估:225 个仓库、88 个有效集成
为验证该方案,研究团队在一批 C/C++ 编写的 GitHub 仓库上运行了这套流程,共分析225 个仓库,产出了88 个带有效构建脚本的 OSS-Fuzz 集成。随后,团队还使用 OSS-Fuzz-Gen 的 harness 生成能力在这些项目上运行,以确认构建脚本的有效性。
实测发现,该方案有许多长处:它克服了众多项目设置的障碍,能够创建模板化方案无法生成的构建脚本。下面通过三个真实样例来展示其能力边界。
案例一:libcypher-parser
目标仓库:https://github.com/cleishm/libcypher-parser
生成的构建脚本如下:
# Build script for libcypher-parser with fuzzing harness # Install necessary dependencies apt-get update && apt-get install -y make autoconf automake libtool curl # Install autoconf 2.71 cd /tmp curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.71.tar.gz tar -xzf autoconf-2.71.tar.gz cd autoconf-2.71 ./configure make make install # Install leg tool cd /tmp curl -L -O https://www.piumarta.com/software/peg/peg-0.1.18.tar.gz tar -xzf peg-0.1.18.tar.gz cd peg-0.1.18 make make install # Build libcypher-parser cd $SRC/libcypher-parser ./autogen.sh ./configure --enable-static --disable-shared make # Copy the static library to the output directory cp lib/src/.libs/libcypher-parser.a $OUT/ # Compile the fuzzing harness for fuzzer in $(find $SRC -maxdepth 1 -name 'empty-fuzzer.*'); do fuzzer_basename=$(basename $fuzzer .c) $CC $CFLAGS -I$SRC/libcypher-parser/lib/src $fuzzer -o $OUT/${fuzzer_basename} \ -L$OUT -lcypher-parser -Wl,--whole-archive $LIB_FUZZING_ENGINE -Wl,--no-whole-archive done总体而言,这是一个相当复杂的构建:它安装了与运行时环境自带版本不同的 autoconf,还额外安装了一个名为 "peg" 的工具。为了识别出 "leg"/"peg" 工具的必要性,Agent 必须先尝试在缺少该工具的情况下构建项目,并解读构建输出——因为缺少 peg 时,配置 libcypher-parser 会报出如下错误:
... checking for leg... no configure: error: leg is required: please install http://piumarta.com/software/peg/ ERROR:__main__:Building fuzzers failed.这正是execute_commands+ 循环迭代机制价值的直接体现:Agent 通过"构建 → 读报错 → 安装依赖 → 重新构建"的闭环,自行推断出了依赖关系。脚本还展示了两个值得注意的细节:
$CC、$CFLAGS、$LIB_FUZZING_ENGINE是 OSS-Fuzz 构建环境注入的关键变量,$LIB_FUZZING_ENGINE提供 fuzzing 引擎的链接库;- 脚本用
for循环遍历$SRC下所有empty-fuzzer.*文件并逐一编译,从而支持任意数量的 fuzzing harness——这是后文将提到的设计约束之一。
案例二:Yams
目标仓库:https://github.com/jpsim/Yams
生成的构建脚本如下:
#!/bin/bash # Set up build environment cd $SRC/Yams/Sources/CYaml mkdir -p build cd build cmake .. -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX make # Compile the fuzzing harness $CC $CFLAGS -I$SRC/Yams/Sources/CYaml/include -I$SRC $SRC/empty-fuzzer.c -o $OUT/empty-fuzzer -L. -Wl,--whole-archive libCYaml.a -Wl,--no-whole-archive $LIB_FUZZING_ENGINE这是一个简单的 cmake 构建脚本,但它在 cmake 命令中传入了-DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX,确保目标项目使用 OSS-Fuzz 运行时环境指定的编译器(即带 sanitizer 和覆盖率插桩的 clang)进行构建——这是 OSS-Fuzz 构建的关键要求,否则插桩不会生效。
有趣的是,这里 fuzz harness 的编译链接没有使用循环。这在原理上可行(项目能构建并链接一个 fuzzing harness),但存在局限:它无法构建和链接任意数量的 fuzzing harness。
案例三:Moment
目标仓库:https://github.com/kmussel/Moment
这是一个已停止维护约 10 年的库,构建这样一个老旧项目具有特殊挑战。生成的构建脚本如下:
#!/bin/bash # Install necessary tools apt-get update && apt-get install -y bison flex # Set source and output directories SRC_DIR="$SRC/Moment" OUT_DIR="$OUT" # Preprocess parseIt.ym to remove the Foundation import sed '/#import <Foundation\/Foundation.h>/d' $SRC_DIR/parseIt.ym > $SRC_DIR/parseIt_preprocessed.ym # Use bison to generate parseIt.c and y.tab.h bison -d -o $SRC_DIR/parseIt.c $SRC_DIR/parseIt_preprocessed.ym # Rename the generated header to y.tab.h mv $SRC_DIR/parseIt.h $SRC_DIR/y.tab.h # Use flex to generate tokeIt.c flex -o $SRC_DIR/tokeIt.c $SRC_DIR/tokeIt.l # Compile the source files into object files $CC $CFLAGS -c $SRC_DIR/TimeParser.c -o TimeParser.o $CC $CFLAGS -c $SRC_DIR/parseIt.c -o parseIt.o $CC $CFLAGS -I$SRC_DIR -c $SRC_DIR/tokeIt.c -o tokeIt.o # Archive the object files into a static library llvm-ar rcs libmoment.a TimeParser.o parseIt.o tokeIt.o # Compile the fuzzing harness and link with the static library $CC $CFLAGS -I$SRC_DIR $SRC/empty-fuzzer.c -o $OUT_DIR/empty-fuzzer -L. libmoment.a $LIB_FUZZING_ENGINE与 libcypher-parser 类似,这个构建脚本令人印象深刻的地方在于其复杂性:下载并安装自定义软件包(bison、flex),并将这些工具作为构建过程的一部分使用。脚本还用sed修改了目标项目中的源文件(移除不适用于当前环境的#import <Foundation/Foundation.h>),并运行 bison 和 flex 生成编译所需的代码。此外,该项目自身没有任何构建系统文件(如Makefile),因此 Agent 退而求其次,直接逐个编译源文件并用llvm-ar打包静态库——这种"无构建系统也能接"的能力是模板方案很难覆盖的。
值得一提的是,OSS-Fuzz 运行环境中的$SRC、$OUT、$CC、$CFLAGS、$LIB_FUZZING_ENGINE等环境变量是整个构建脚本契约的核心,本仓库的 projects/example/build.sh 和 projects/example/Dockerfile 中可以看到这些约定的标准用法。新项目接入的完整流程与规范,可参考仓库根目录的 new_project_guide.md。
局限性与未来工作
在实测评估过程中,研究团队观察到若干局限和可改进之处。
构建脚本"成功"却绕过了目标源码
团队观察到若干案例:Agent 生成的构建脚本完全绕过了目标源码的编译,最终只是构建了一个空 fuzzing harness。问题在于,当前方案不会对生成的 harness 做后处理分析,以验证目标源码是否真正进入了最终二进制文件。这意味着当流程推进到 harness 生成阶段时,由于构建脚本没有涉及任何目标源码的编译,无法支撑后续工作流。虽然这种情况比较少见,但必须的解决方案是:对生成的构建脚本做更严格的后处理完整性校验,或者在端到端工作流中提供后续修正能力。
只能构建单个 fuzzing harness 的脚本
方案会指示 LLM 生成能够构建任意数量fuzzing harness 的构建脚本,以便复用该脚本编译 OSS-Fuzz-Gen 在 harness 生成阶段产出的所有 harness(很可能不止一个)。但实测发现,这一约束并不总是被遵守,部分构建脚本最终只能构建单个 fuzzing harness(例如构建指令中缺少循环)。此时用户需要手动修改构建脚本,使其能够构建任意数量的 harness,才能支撑最终需要两个及以上 harness 的 OSS-Fuzz 接入。
融入目标代码库的构建系统
当前方案产出的构建脚本,通常显式使用CC/CXX环境变量将 fuzzing harness 链接到脚本前面阶段构建的静态库上,即由"一组命令 + 一份 harness 源码"组成。另一种思路是把 fuzzer 的构建融入目标仓库自身的构建系统(如扩展其 Makefile),这样虽然没有实质性的功能差异,但对开发者更友好、更易维护。
失败生成的诊断与结论
当构建脚本生成失败时,目前没有任何关于失败原因的解释。一个自然的扩展方向是引入另一个 Agent 或类似机制,专门分析构建失败的原因。例如,区分失败原因是硬性原因(如目标代码在相关构建运行时中根本无法编译),还是 LLM 能力不足没能找到合适的方案。如果是硬性原因,反而是一个有价值的结论——可以明确告知用户目标项目不兼容 OSS-Fuzz(例如 Windows-only 项目)。
结论
本文介绍了 OSS-Fuzz-Gen 的一项新能力:通过Agent 化构建脚本生成,从零开始产出 OSS-Fuzz 项目集成。该能力以 CLI 工具形式提供,只需一条命令即可生成一个 OSS-Fuzz 项目。研究团队在 225 个 C/C++ 项目上进行了实测,得到 88 个有效的 OSS-Fuzz 构建脚本,并通过若干案例展示了能力亮点,同时识别了局限与未来方向。
整体来看,这套 Agent 化方案把"接入 OSS-Fuzz"从一件依赖人工经验的手工活,变成了可规模化、可复现的自动化流程。它与 OSS-Fuzz 仓库中的其他自动化探索一脉相承——例如引入 Agent 能力的博客文章展示了 CLI Agent 如何在 OSS-Fuzz 仓库中直接完成新项目接入、覆盖率改进、构建修复等任务;LLM 合成 harness 的博客文章则记录了更早期的端到端接入方案。构建脚本生成与 harness 生成这两条技术线互相配合,正逐步逼近"任何项目都能一键接入 OSS-Fuzz"的目标。
如果你希望在自己的项目上尝试这套工具,只需按文中示例安装 OSS-Fuzz-Gen、配置 LLM 访问,然后运行oss-fuzz-generator generate-full。如果遇到构建生成不工作的情况,建议将该信息反馈给 OSS-Fuzz-Gen 项目维护者,帮助持续改进这一自动化能力。
- 测试
- 应用安全
- 质量保障
【免费下载链接】oss-fuzz
OSS-Fuzz - continuous fuzzing for open source software.
相关推荐
OSS-Fuzz 中的 LLM 驱动 Fuzz Harness 合成:为未接入项目自动生成 OSS-Fuzz 集成
OSS Fuzz 中的 LLM 驱动 Fuzz Harness 合成:为未接入项目自动生成 OSS Fuzz 集成 OSS Fuzz 团队在其 OSS Fuzz
测试应用安全质量保障OSS-Fuzz Java/JVM 项目接入指南:基于 Jazzer 的 fuzz target 编写与构建
OSS Fuzz Java/JVM 项目接入指南:基于 Jazzer 的 fuzz target 编写与构建 OSS Fuzz 对 Java 及任何运行在 JV
测试应用安全质量保障OSS-Fuzz Go 项目集成指南:从 go-fuzz 到原生 Go 1.18 Fuzzing 的完整接入流程
OSS Fuzz Go 项目集成指南:从 go fuzz 到原生 Go 1.18 Fuzzing 的完整接入流程 本文是 OSS Fuzz 新项目接入指南( d
测试应用安全质量保障
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考