仅依靠大语言模型去辅助工作效果并不理想,结合领域相关知识或许能更有效。分享一篇即将要发表于 2025 年 ICSE 会议的论文 CKGFuzzer,它通过结合代码知识图谱,让大语言模型可以更加高效且准确地生成模糊驱动器。
论文摘要
大语言模型(Large Language Model,LLM)的编程能力引起了广泛的关注。与此同时,模糊测试在提升软件程序可靠性和检测漏洞方面发挥关键作用。将大语言模型引入传统模糊测试中,解决手动编写模糊驱动器所导致的效率低下问题,成为当前研究的前沿方向之一。
论文提出了一种基于代码知识图谱(Code Knowledge Graph)的自动化模糊测试方法 CKGFuzzer,它通过 LLM 驱动的智能代理系统实现,将模糊驱动器的创建视为代码生成任务,利用被测目标的代码知识图谱不断优化模糊驱动器和输入种子。实验结果表明,CKGFuzzer 在八个开源软件项目上的平均代码覆盖率比现有技术提高了 8.73%,同时将崩溃案例分析的人工工作量减少了 84.4%,并成功检测到 11 个真实漏洞(包括 9 个未被报告的漏洞)。
1 背景介绍
尽管传统的模糊测试工具(如 AFL 和 libFuzzer)已经非常成熟,但模糊驱动器的创建仍然严重依赖于手动编码,这限制了测试的效率和范围。现有的方法(如 PromptFuzz)通过不同的 API 变异组合利用 LLM 生成模糊驱动器。这些方法采用零样本或少样本学习技术(例如 Google OSS-Fuzz-Gen)来基于给定的 API 集合生成模糊驱动器。然而,这些方法尚未完全解决生成高质量模糊驱动器所面临的挑战,导致生成的驱动器在质量和有效性方面存在局限性。
模糊驱动器的生成与单元测试不同,它不仅需要理解单个函数,还需要理解多个 API 之间的交互,需要对系统代码行为有深入的洞察。另一个关键挑战在于模糊测试对输入种子的严重依赖。为了产生更有意义的输入种子,需要对模糊驱动器进行数据流分析。此外,崩溃可能源于错误的模糊驱动器、模糊驱动器中不正确的 API 使用,或者是系统更深层次的问题,这使得开发人员难以准确追溯根本原因。
为了应对这些挑战,论文将模糊驱动器生成视为代码生成任务。通过基于代码库构建知识图谱,论文利用 LLM 自动生成模糊驱动器并执行数据流分析以产生输入种子,从而实现完全自动化的模糊测试。为此,论文开发了基于代码知识图的 LLM 驱动的模糊驱动器代理系统 CKGFuzzer,指导 LLM 为模糊驱动器生成更高质量的 API 组合和基于覆盖引导的变异。
2 系统设计
CKGFuzzer 是一个将多智能体系统与代码知识图谱相结合的模糊驱动器生成框架,其目标是针对 API 组合生成高效的模糊驱动器,以提升模糊测试的质量和覆盖率。图 1 展示了 CKGFuzzer 的工作流程,由 3 个主要组件和一个独特的反馈循环机制组成。
- CKGFuzzer 解析被测目标及其库 API,提取并嵌入代码知识图谱,包括解析抽象语法树,提取数据结构、函数签名、调用关系等关键信息。
- CKGFuzzer 查询代码知识图谱的 API 组合,关注那些具有调用关系的 API,生成相应的模糊驱动器。
- CKGFuzzer 编译生成的模糊驱动器,并且通过一个动态更新的库使用情况,修复出现的编译错误。
- CKGFuzzer 执行编译成功的模糊驱动器,监控库文件的代码覆盖率,对未能覆盖的新路径 API 组合进行变异,迭代该过程并持续进行。
- CKGFuzzer 使用链式推理分析在模糊测试过程中产生的崩溃,参考包含了真实 CWE 相关的源码示例,来验证这些崩溃的有效性。
2.1 API 组合生成
CKGFuzzer 采用外部检索增强型流程,结合代码知识图和 LLM 生成最优的 API 组合,进而创建能够最大化代码覆盖率的模糊驱动器。查询过程分为三个关键阶段:索引(Indexing)、检索(Retrieval)和响应合成(Response Synthesis),如图 2 所示。
在索引阶段(行 1-行 2),代码知识图谱通过嵌入模型拆分成更小的块,每个分块代表图内容的一个子集,请求也按相同的嵌入模型进行转换。在检索阶段(行 3-行 4),通过向量检索匹配相关的分块,确保检索到最相关的信息以形成响应。在响应合成阶段(行 5-行 7),初始化分块查询答案,提示大模型逐步整合有用的剩余分块,迭代该过程直到所有检索的信息处理完成,最终输出一组与目标功能相关的 API 集合。
2.2 模糊驱动器生成
为了生成高质量的模糊驱动器,论文采用了一种结合角色提示(role-prompt)策略和记忆机制(memory mechanism)的方法来指导 LLM。这些提示是通过 API 组合的源代码和自然语言描述构建的,确保 LLM 生成的模糊驱动器不仅在语法上正确,而且在上下文上与 API 的功能一致。提示策略强调以下几个关键方面:
- 任务定义:LLM 的任务是生成一个测试所提供 API 组合的模糊驱动器。提示明确指出每个 API 必须在
LLVMFuzzerTestOneInput
函数中被调用,以确保对 API 集的全面测试覆盖。 - API 上下文:提示中包含 API 的源代码、头文件和自然语言总结。这为 LLM 提供了必要的上下文,以便正确使用 API,并根据它们的功能和约束管理输入和输出。
- 错误处理:为了避免模糊驱动器本身的不稳定性(这可能导致崩溃并降低整体模糊测试效率),提示中明确包含了健壮的错误处理和谨慎的内存管理指令。
2.3 动态程序修复
由于幻觉(hallucinations)和缺乏高质量的训练数据,LLM 生成的代码通常包含语法和语义错误,这可能会影响模糊驱动器的可靠性。观察到 CKGFuzzer 生成的一些模糊驱动器无法编译成功,这些失败主要是由于语法错误和对库 API 的错误使用。为了解决这一问题,我们开发了一种基于 LLM 的动态程序修复机制,该机制可以自动纠正这些错误并提高模糊驱动器的整体稳定性,如图 3 所示。
首先从 OSS-Fuzz 的模糊驱动器样本和库的头文件中初始化一个包含正确库 API 使用的知识库(行 2)。当 CKGFuzzer 遇到编译失败时,它会处理编译器错误信息,并构建一个查询以在外部知识库中搜索错误 API 或代码片段的正确用法(行 11-行 13)。所有成功编译的模糊驱动器都会被重新插入到知识库中,动态更新正确 API 使用模式的存储库(行 7-行 9)。CKGFuzzer 会持续查询知识库并应用修复,直到模糊驱动器成功编译,或者达到最大迭代次数(行 5 和行 15)。
2.4 模糊测试循环
CKGFuzzer 的模糊测试循环旨在通过迭代优化模糊测试的效果,通过初始化高质量的输入种子,并基于覆盖引导的变异策略来探索新的代码路径,两者的协同工作旨在最大化代码覆盖率并识别新的执行路径。
1)输入种子库初始化
首先,通过详细分析模糊驱动器的数据流,提取变量之间的值流关系。然后,提取模糊驱动器交互的 API 函数签名,以了解输入的结构和约束条件。接着,利用这些信息构建 LLM 的提示,指示其生成能够最大化代码覆盖率、针对边缘情况并探索边界条件的输入种子。最后,存储在输入种子库中的初始输入种子作为模糊测试的基础。
2)覆盖引导的变异
在模糊测试过程中,CKGFuzzer 监控每个模糊驱动器的代码覆盖率,并分析整个库的覆盖情况(行 3-行 8)。对于覆盖率低于库平均水平的文件,提取其定义的 API 函数,并根据文件的覆盖率生成一个优先级列表,称为低覆盖率 API 列表(行 9)。然后,CKGFuzzer 使用这个低覆盖率 API 列表指导 LLM 对当前模糊驱动器中的 API 组合进行变异和重构(行 12-行 16)。变异过程如图 4 所示。
2.5 崩溃分析
在模糊测试过程中,CKGFuzzer 配备了多种运行时错误检测工具,例如 AddressSanitizer(ASan)、Undefined-Behavior-Sanitizer(UBSan)和 MemorySanitizer(MSan)。然而,这些工具检测到的崩溃可能源自不同原因,包括模糊驱动器中 API 的误用或库 API 本身的漏洞。为了区分这些原因崩溃分析模块采用链式推理(chain-of-thought)策略,引导 LLM 通过结构化的逐步推理过程来诊断每个崩溃的原因。崩溃分析过程包括以下关键步骤:
- 源代码提取:分析从崩溃发生的具体代码区域开始。模块会隔离崩溃相关的特定代码片段,包括模糊驱动器的源代码以及可能影响崩溃的 API 调用。
- 错误模式假设:在提取相关代码后,LLM 系统地分析代码,寻找可能触发崩溃的常见编程错误模式,例如不安全的内存操作、错误的变量赋值或不当的控制流条件等。
- 基于 CWE 的模式匹配:在形成关于潜在错误模式的初步假设后,LLM 构建针对性的查询,以搜索基于 CWE(Common Weakness Enumeration)的知识库。
3 实验评估
CKGFuzzer 使用 Tree-sitter 解析源代码以高效地构建和更新语法树。利用 CodeQL 提取 API 调用关系和源代码细节, 选择 BAAI/bge-small-en-v1.5 模型将提取的代码和 API 调用表示在嵌入空间中。此外,CKGFuzzer 选择 DeepSeek-V2-Coder 模型来处理模糊驱动器生成和编译修复任务,使用 DeepSeek-V2-Chat 模型处理其他代理任务。论文选取了 8 个开源库作为测试对象,在 OSS-Fuzz 平台和 libFuzzer 作为底层模糊测试环境的基础上进行了实验.其他更多实验配置可阅读原始论文,这里总结实验的结论,CKGFuzzer项目已开源在 Github 平台。
RQ1. 与现有模糊测试工具的比较
CKGFuzzer 与两种现有的开源模糊测试工具进行了比较:基于覆盖率的模糊测试工具 OSS-Fuzz 和基于 LLM 的模糊测试工具 PromptFuzz。CKGFuzzer 在 8 个测试库中的 6 个实现了最高的分支覆盖率。
RQ2. 消融研究
使用代码知识图谱在生成高质量模糊驱动器方面更有效;与 CKGFuzzer 相比,没有修复和仅依赖 LLM 修复的变体在代码覆盖率方面表现较差;覆盖引导变异在大多数情况下是一种更有效的策略。
RQ3. 崩溃分析结果
CKGFuzzer 在模糊测试过程中共识别出 199 个独特崩溃,其中 168 个被崩溃分析模块判定为库 API 的误用所导致。
学习笔记
模糊测试本身也是相当偏工程的研究方向,这篇论文尽管在方法上没有太显眼的创新,但它整合利用了多种工具和技巧,且每个阶段基本上 All in LLM。比较关键的是,这个项目公开了源码,非常值得学习。
最后,附上文献引用信息及 DOI 链接:
Xu H, Ma W, Zhou T, et al. A Code Knowledge Graph-Enhanced System for LLM-Based Fuzz Driver Generation[J]. arXiv preprint arXiv:2411.11532, 2024.