论文名称 | 发表时间 | 发表期刊 | 期刊等级 | 研究单位 |
Fuzz4All: Universal Fuzzing with Large Language Models | 2024年 | arXiv | - | 伊利诺伊大学 |
0.摘要
研究背景 | 模糊测试再发现各种软件系统中的错误和漏洞方面取得了巨大的成功。以编程或形式语言作为输入的被测系统(SUT),例如编译器、运行时引擎、约束求解器以及具有可访问 API 的软件库尤为重要,因为它们是软件开发的基本构建块。 |
现存问题 | SUT系统的现有模糊器通常针对特定语言,因此不能轻松应用于其他语言,甚至同一语言的其他版本。此外,现有模糊器生成的输入通常仅限于输入语言的特定功能,因此很难揭示与其他功能或新功能相关的错误。 |
核心思想 | 利用大语言模型(LLM)作为输入生成和变异引擎,这使得该方法能够为任何实际相关的语言生成多样化且真实的输入。 |
研究内容 | 论文提出首个通用模糊测试系统 Fuzz4All,该系统针对许多不同的输入语言以及这些语言对应的 SUT 系统进行模糊测试。具体来说,论文提出一种新颖的自动提示技术,该技术创建非常适合模糊测试的 LLM 提示,以及一种新颖的 LLM 支持的模糊循环,它迭代更新提示以创建新的模糊输入。 |
实验结果 | 论文在 9 个接受测试的系统上评估Fuzz4All,这些系统采用六种语言(C、C++、Go、SMT2、Java 以及 Python)作为输入。实验表明,在所有六种语言中,通用模糊测试比现有的特定语言模糊测试实现了更高的覆盖范围。此外,Fuzz4All 在 GCC、Clang、Z3、CVC5、OpenJDK 以及 Qiskit 量子计算平台等广泛使用的系统中发现了 98 个错误,其中 64 个错误已被开发人员确定为以前未知的。 |
1. 引言
研究背景:模糊测试是一种生成输入的自动化测试方法,旨在暴露被测系统(SUT)的以外行为,例如崩溃。研究人员和从业者以及成功构建了实用的模糊测试工具,这些工具在发现现实系统中的大量错误和漏洞方面取得了巨大成功。一个特别重要的 SUT 系列是接受编程或形式语言输入的系统,例如编译器、运行时引擎以及约束求解器等。人们为此类系统提出了许多模糊器,因为它们是软件开发的基本构建块。例如,编译器和运行时引擎中的错误会影响所有相应的下游应用程序。
现存问题:首先,现有的模糊器与目标系统紧耦合,模拟器的横向扩展性差。其次,现有的模糊器无法随着目标系统的升级而升级,模拟器纵向扩展性差。最后,现有的模糊器生成的测试样本质量不高,模拟器的测试样例生成效率低。
- 问题一:传统的模拟器通常设计为针对特定语言或特定 SUT。然而,设计和实现模拟器非常耗时,例如 用于 C/C++ 编译的模糊器 Csmith 拥有超过 8 万行代码,用于 Linux 系统的模糊器 Syzkaller 包含数以万计的手工规则。由于每种目标语言各不相同,因此将一种输入语言实现模拟器的工作重用到另一种输入语言通常并非易事。此外,对一种 SUT 有效的模糊测试策略可能对另一种 SUT 根本不起作用。
- 问题二:现实世界的系统在不断发展,例如,通过向输入语言添加新功能。为特定版本的语言或 SUT 设计的传统模糊器可能会在新版本上失去有效性,并且无法轻松用于测试新实现的功能。例如,Csmith 仅支持 C++11 之前的一组有限功能。事实上,最近的工作表明 Csmith 未能在新版本的 GCC 和 Clang 编译器中发生任何新的错误,这表明新版本的编译器正在变得不受现有模糊测试器的影响。
- 问题三:即使在特定目标语言的范围内,基于生成和基于突变的模糊测试通常也无法覆盖大部分输入空间。基于生成的模糊器严重依赖输入语法来合成有效代码,并且还配备了语法规则来确保合成代码的有效性。为了生成大量有效的模糊测试输入或回避难以建模的语言特征,基于生成的模糊器通常使用完整语言语法的子集,这限制了它们只能测试所有语言特征的子集。同样,基于突变的模糊器受到其突变算子的限制,并且需要难以获得的高质量种子。
研究内容:论文提出第一个通用的模糊器 Fuzz4All,可以针对许多不太的输入语言以及这些语言的函数进行模糊测试。Fuzz4All 和目前的现存模糊器的有很大区别,例如 AFL 和 libFuzzer 使用极其简单的突变,不知道目标语言,因此很难产生有意义的编程语言模糊输入。Fuzz4All 的关键思想是利用大语言模型(LLM)作为输入生成引擎和变异引擎( LLM 接受了各种编程语言和其他形式语言的大量示例的预先训练,因此他们对这些语言的语法和语义有隐含的理解)。
具体实现:Fuzz4All的输入是用户提供的描述 SUT 的文档,以及可选的要关注的 SUT 的特定功能,例如以文档、示例代码以及正式规范的形式。然而,这些用户输入可能过于冗长,无法直接用作 LLM 的提示。为了解决上述问题,论文提出一个自动提示流程,自动将所有用户提供的输入提取为简洁有效的模糊测试提示(此提示是生成模糊输入的 LLM 初始输入)。由于使用相同的提示连续采样会导致许多类似的模糊输入,因此论文提出一个由 LLM 支持的模糊循环,它迭代更新提示以生成一组不太的模糊输入。为此,Fuzz4All 将先前迭代中生成的模糊输入与自然语言指令相结合,例如要求改变这些输入。最后,LLM 生成的模糊测试输入被传递到 SUT 以实现目标系统的模糊测试,例如检查系统崩溃。
Fuzz4All 解决了前面讨论的传统模糊器的局限性和挑战。(1)Fuzz4All 通过使用 LLM 作为生成引擎,可以应用广泛的 SUT 和输入语言,而不是为特定 SUT 精心设计单一用途的模糊器。(2)与针对特定版本的 SUT 或输入语言的现有模糊器相比,Fuzz4All 可以轻松地随目标一起发展。例如,要对新实现的功能进行模糊测试,用户只需要提供与该功能相关的文档或示例代码。(3)为了解决传统模糊器生成能力有限的问题,Fuzz4All 利用 LLM 在数十亿代码片段上进行预训练,使它们能够船舰可能遵循驶入语言的语法和语义约束的广泛示例。
实验结果:论文对 6 种输入语言(C、C++、SMT、Go、Java 以及 Python)和 9 种 SUT 进行了广泛的评估。对于每一个,论文将 Fuzz4All 与最先进的基于生成和基于突变的模拟器进行比较。实验结果表明,Fuzz4All 在所有语言中实现了最高的代码覆盖率,将之前最先进的覆盖率平均提高了 36.8% 。此外,论文还证明 Fuzz4All 支持一般模糊测试和针对 SUT 特定函数的模糊测试,用户可以通过提供足够的输入文档来决定。最后,Fuzz4All 在论文中检测出 98 个错误,其中 64个已被开发人员却认为以前未知的错误。
2. 背景知识
2.1 大语言模型(LLM)
大语言模型的引入:自然语言处理(NLP)的最新发展导致自然语言和代码任务广泛采用大语言模型(LLM),最先进的 LLM 基于 Transformer,可以分为 decoder-only LLM(例如 GPT3 和 StarCoder),encoder-only LLM(例如 BERT 和 CodeBERT),encoder-decoder LLM(例如 BART 和 CodeT5),最近,基于指令的 LLM (例如 ChatGPT 和 GPT4 )和使用人类反馈的强化学习(RFHF)进行微调的 LLM 被证明可以理解并遵循复杂的指令。
大语言模型的使用:LLM 通过会经过微调或提示来执行特定任务。微调(fine-tuned)是指,通过对特定任务数据集的进一步训练,微调更新模型权重(合适的数据集可能不可用,随着 LLM 规模的不断增长,微调 LLM 的成本越来越高)。提示(prompt)是指,为 LLM 提供任务描述或者解决问题的示例,引导模型完成任务(选择提示的过程也成为提示工程)。最近,研究人员提出了自动提示,这是一种使用 LLM 梯度来选择软提示(连续向量嵌入)和硬提示(自然语言文本)的自动过程。最近,研究人员通过计算有效性的代理得分来取代基于梯度的方法。
2.2 模糊测试(Fuzzing and Testing)
模糊测试的引入:模糊测试旨在生成导致 SUT 出现意外行为的输入。传统的模糊器可以分为基于生成的模糊器和基于突变的模糊器。基于生成的模糊器使用预定义的语法和目标语言语义的内置知识创建完整的代码片段。CSmith 和 YARPGen 硬编码语言规范,以确保生成的代码片段分别测试 C 和 C++编译器的有效性。jsfunfuzz 将语言语法与历史错误触发代码片段相结合,以生成新的输入来测试JavaScript引擎。基于生成的模型还被用来测试OpenCL、JVM、CUDA、深度学习编译器、数据记录引擎和交互式调试器。基于突变的模糊器迭代地对种子执行转换以生成新的模糊输入。除了基本突变之外,研究人员还开发了复杂的转换,旨在确保类型一致性,添加历史错误触发代码片段和覆盖反馈。
针对特定 SUT 或语言的模糊测试:与上述针对特定 SUT 或语言的模糊测试不同,另一条研究方向是通用模糊测试。AFL 和 libFuzzer 是通用模糊测试器,它们使用具有适应度函数的遗传算法来优先考虑模糊输入,以实现新的覆盖范围的进一步突变。这些突变不知道 SUT ,而是专注于字节级转换(无法生成编程语言形式的有效输入)。最近的工作添加了基于正则表达式的变异运算符来匹配常见的编程语句,这些变异算子的简单性限制了此类模糊器覆盖新代码的能力。PolyGlot 是另一种与语言无关的模糊器,它首先使用特定于语言的语法将种子程序解析为统一的中间表示,然后使用一组变异运算符来生成新程序。尽管很有希望,但 PolyGlot 仍然使用有限的突变集,并且无法达到为特定语言设计的模糊器相同的覆盖水平。
2.3 大语言模型模糊测试
基于大语言模型模糊测试引入:为了补充传统的模糊测试技术并将模糊测试应用于新兴领域,人们提出基于学习的模糊器。先前基于学习的技术主要集中于训练神经网络以生成模糊输入。TreeFuzz 将训练语料库解析为树结构,并通过树遍历,学习合成新模糊输入的概率生成模型。深度学习模型已用于模糊 PDF 解析、OpenCL、C、网络协议以及JavaScript。最近,研究人员还直接利用 LLM 来对特定的库进行模糊测试,TitanFuzz 使用 Codex 生成种子程序,使用 inCoder 为模糊深度学习库执行基于模板的变异。
基于大语言模型的通用模糊测试:与之前基于学习和 LLM 的模糊器不同,Fuzz4All 可以轻松应用于多种编程语言。先前的工作训练特定于语言的模型或特定于语言的解析。即使是最近基于 LLM 的方法 TitanFuzz 也是转为深度学习库而设计的,具有手工制作的提示和突变模式,因此无法轻松扩展到其他 SUT。
除了模糊测试之外,LLM 还被应用于单元测试生成的相关问题。CodaMosa 将传统的基于搜索的软件测试与查询 Codex 交织在一起,以便在达到覆盖稳定水平时生成新的单元测试。TestPilot 提示 Codex 提供方法源代码和示例用法,以生成单元测试并修复错误生成的测试。这些基于 LLM 的测试生成器需要特定类型的输入,并且仅适用于单元测试。与这些工作相比,Fuzz4All 可以采用任意格式的输入,适用于通用模糊测试和特定目标模糊测试。此外,此类单元测试生成器通常需要手动检查和完成测试,因为它们受到自动生成的测试预言机的限制,即使是最先进的 MML 也不能总是可靠的生成测试预言机。相反,Fuzz4All利用广泛使用的模糊测试,实现了测试的完全自动化。
3. Fuzz4All 方法
论文提出一种通用模糊器 Fuzz4All,该模糊器利用 LLM 来支持任何接受编程语言输入的 SUT 的一般性和针对性的模糊测试。Fuzz4All 建立在两个模型之上,一个是减少给定用户输入的蒸馏 LLM ,另一个是创建模糊输入的生成 LLM,以平衡不同 LLM 提供的成本和收益之间的权衡。由于蒸馏 LLM 需要理解和蒸馏任意用户的输入,因此论文使用具有强大自然语言理解能力的高端大型基础模型(GPT4)。同时,为了执行高效的模糊测试,Fuzz4All 使用较小的模型作为生成 LLM(StarCode)。
具体来说,(1)Fuzz4All 接受描述要生产的模糊输入的任意用户输入,例如 SUT 的文档、示例代码片段或规范。(2)由于用户输入可能很长、冗余且部分不相关,因此需要将初始输入提炼为简介但信息丰富的模糊测试提示。为此,Fuzz4All 通过使用大型、先进的蒸馏 LLM 对多个不同的候选提示进行采样来执行自动提示步骤。其中,每个候选提示都会传递到生成式的 LLM 以生成代码片段(模糊测试输入)。(3)Fuzz4All 选择产生最高质量模糊测试输入的提示。(4)Fuzz4All 通过自动提示选择的最佳提示作为生成 LLM 的初始输入提示,对生成 LLM 较小连续采样以生成模糊输入。(5)为了避免生成许多类似的模糊输入,Fuzz4All 在每次迭代中不断更新输入提示。具体来说,该方法选择先前生成的输入作为示例(作为输入模板)。(6)Fuzz4All 还在初始提示中发夹了一条生成指令,该指令知道模型生成新的模糊输入。重复此过程,同时不断地将生成的模糊输入传递到 SUT 中,并根据用户定义的预言机检查其行为。
3.1 自动化生成提示词
自动化生成提示词概述:自动化生成提示词通过蒸馏 LLM 从用户一般性输入中提炼适合模糊测试的提示词,通过该步骤用户可以将针对 SUT 的一般性描述(技术文档、示例代码、规范)作为模糊测试系统的输入。与要求输入遵循特定格式的传统模糊器不同,Fuzz4All可以直接理解用户输入中的自然语言描述或代码示例。然而,用户输入中的某些信息可能是冗余或不相关的,直接使用用户输入作为生成 LLM 的提示可能是无效的。因此,自动提示的目标是生成一个精炼的输入提示,以实现有效的基于 LLM 的模糊测试。
自动化生成提示词算法:算法 1 详细介绍了 Fuzz4All 的自动提示步骤,算法的输入是用户输入和要生产的候选提示词数量,输出是选择用于模糊测试活动的输入提示词。算法首先通过贪婪采样(temp = 0)生成高置信度的合理提示词,之后以更高的温度(temp = 1)生成更多样化的提示词,最后通过小规模的模糊测试对每个候选提示词进行评估。算法中使用的提示词为,““Please summarize the above information in a concise manner to describe the usage and functionality of the target”。
自动化生成提示词示例:下图显示了提示词自动生成算法生成的提示词示例。具体来说,该示例用于 C++ 编译器的模糊测试,同时特别关注 std::expected (C++ 23 中引入的新功能)。作为用户输入,论文将原始 cppreference 文档传递给 Fuzz4All。通过自动提示算法生成的提示词提供了目标特征更精炼的自然语言描述(由 3262 个字符精炼到 1410 个字符)。
3.2 提示词指导的模糊测试
提示词指导的模糊测试概述:通过上一步可以实现 Fuzz4All 的输入提示词的生成,模糊测试循环的目标是使用生成 LLM 生成不同的模糊测试输入。然而,由于 LLM 的概念性质,使用相同输入进行多次采样会产生相同或相似的代码片段。为了避免重复模糊测试输入的生成,论文结合已有的魔术测试示例结合自然语言指令对输入提示词进行持续更新。
模糊循环的高级思想是通过从先前的迭代中选择示例模糊输入,并指定生成策略来不断赠券原始输入提示。使用示例的目的是指导生成 LLM 生成与示例代码存在一定相关性的代码片段。具体来说,生成策略的设计受到传统模糊器的启发,模仿它们合成新模糊器输入(基于生成的模糊器)和生成先前生成的输入的变体(基于突变的模糊器)。在模糊测试循环的每次新迭代之前,Fuzz4All 都会在输入提示中附加示例和生成策略,使生成 LLM能够不断构建新的模糊测试输入。
提示词指导的模糊测试算法:算法 2 描述了模糊测试循环,算法的输入是初始输入提示词和模糊预算,算法的输出是由用户定义的预言机识别的一组错误。具体来说,算法首先初始化生成策略(生成新的测试用例、对已有的测试用例进行变异、根据语义等效性生成测试用例,第二行),这些策略将用于在模糊测试循环期间修改输入提示词。下图列出了论文的三种生成策略及其相应的说明。对于生成 LLM 的第一次调用,该算法还没有任何模糊测试输入的示例,因此,它将生成新的测试用例指令附加到输入提示,该指令引导模型生成第一批模糊测试(第三行)。
接下来,算法进入模糊测试循环(5-9行),该循环不断更新提示已创建新的模糊测试输入。具体来说,算法在上一批生成的对 SUT 有效的测试样例中随机选择一个测试样例作为示例(第 6 行),此外算法还会随机选择一种测试样例生成策略(第 7 行),完成上述步骤后算法会在初始输入提示词、示例测试样例、测试样例生成策略的基础上生成新的输入提示词,并实现新一轮测试样例的生成。
重复主模糊测试循环,指导算法耗尽模糊测试预算。对于每个创建的模糊测试输入,Fuzz4All 将输入传递给 SUT。如果用户定义的预言机识别出意外行为,例如崩溃,则算法会将报告添加到测试到的错误集中。
提示词指导的模糊测试示例:上图说明了论文模糊测试循环如何使用输入示例和生成策略来创建不同的模糊测试输入。在本例中,论文对 SMT 求解器进行模糊测试,其中输入是用 SMT2 语言编写的逻辑公式。最初由于没有示例,因此该算法使用生成新策略来合成新的模糊输入。接下来,以生成的有效模糊输入为例,算法查询模型以基于现有的变异策略创建新的输入,其目的是对所选示例进行变异。我们观察到,新的模糊输入通过交换变量的类型以及添加一些计算来巧妙地修改以前的输入。在下一次模糊化迭代中,算法选择先前生成的模糊化输入作为示例,并使用语义等价生成策略,其目的是创建一个不修改给定示例语义的输入。这次,我们观察到新的模糊测试输入只是向所选示例添加了语法标签。事实上,示例中显示的生成策略组合有助于 Fuzz4All 生成模糊输入,从而导致 SMT 求解器意外崩溃。这次崩溃暴露了 Fuzz4All 在我们的评估过程中检测到的现实世界错误之一,该错误已被开发人员确认并修复。
4. 实验设计
论文根据以下研究问题评估 Fuzz4All:
- RQ1:Fuzz4All 与现有模糊器相比如何?
- RQ2:Fuzz4All 在执行由针对性的模糊测试方面有多有效?
- RQ3:不同的组件如何提高 Fuzz4All 的有效性?
- RQ4:Fuzz4All 发现了哪些现实世界的BUG?
4.1 具体实施
Fuzz4All 主要使用 Python 实现。Fuzz4All 的自动提示和模糊循环组件仅包括 872个 LoC(传统模糊器 Csmith 需要超过 80k 个 LoC)。Fuzz4All 使用 GPT-4 作为蒸馏 LLM 来执行自动提示,因为该模型对于各种基于 NLP 的推理任务来说是最先进的。具体来说,论文对 4 个候选提示词进行采样,每个提示词生成 30 个测试样例,并使用基于有效性的评分函数进行评估。对于模糊测试循环,论文使用 StarCoder 模型的 Hugging Face 实现作为生成 LLM,该模型针对 80 多种语言的超过一万亿个代码标记进行了训练。生成测试样例时,论文默认使用温度为 1 、批量大小为 30、最大输出长度为 1024,使用 top-p 为 1 的核采样。
4.2 被测平台与基线方法
为了证明 Fuzz4All 的通用性,论文在 6 种输入语言和 9 种SUT上对其进行评估,如下表所示。论文将每种语言的一个 SUT 的覆盖率与下表最后一列种显示的用于覆盖率测试的 SUT 版本进行比较。除了覆盖率测试实验之外,论文还对每个目标的夜间版本进行了模糊测试。除非另有说明,论文使用以外的编译器崩溃作为预言机,并且如果变异成功则认为模糊测试有效。每个基线模糊器都以默认设置运行。对于需要输入种子的基线模糊器,论文使用其赋值存储库中提供的默认种子语料库。
C/C++ 编译器:针对流行的 GCC 和 Clang 编译器,并默认提供标准 C 库文档作为 Fuzz4All 的用户输入。我们的基线包括 Csmith(一种经典的基于生成的 C 编译器模糊器)和 GrayC(一种最近的基于突变的模糊器)。对于 C++,我们通过提供 C++23 标准文档作为 Fuzz4All 的输入来瞄准新的 C++23 功能。我们的基线是 YARPGen ,这是一种基于生成的模糊器,它使用 C++ 中的新语言功能和生成策略来扩展 Csmith,以触发不同的编译器优化。
SMT 求解器:在 Z3 和 CVC5 上运行 Fuzz4All,并使用常用的开发人员设置(例如调试和断言)。 Fuzz4All 使用 SMT2 语言的概述文档和 SMT 求解器作为默认输入来生成 SMT 公式作为模糊测试输入。如果 SMT 求解器返回 SAT 或 UNSAT 且没有任何错误,则模糊测试输入被视为有效。我们的基线是最先进的 TypeFuzz ,它根据新生成的相同类型的表达式来改变现有的 SMT 表达式。
Go 工具链:在最新版本的 Go 上运行 Fuzz4All。默认情况下,我们使用 Go 标准库文档作为 Fuzz4All 的输入。作为基线,我们使用 go-fuzz ,这是一种专为 Go 设计的覆盖引导、基于突变的模糊器,它使用手写模板为各种 Go 标准库生成输入。
Java 编译器:在 OpenJDK Java 编译器 javac 上评估 Fuzz4All,该编译器将源代码编译为字节码,实验的默认输入是最新的标准 Java API 文档页面。论文与 Hephaestus 进行比较,该模糊器是一种最新的组合生成和基于突变的模糊器,专为 JVM 编译器设计并针对与类型相关的错误。
量子计算平台:Qiskit 是一种流行的量子计算框架 。 Qiskit 构建在 Python 之上,即输入程序和编译都是在 Python 代码中定义的。因此,为 Qiskit 创建有效输入意味着以有意义的方式使用 Qiskit Python API,例如创建量子电路。传统综合工具处理动态类型通用语言(如 Python) 具有挑战性,更不用说额外的 API 约束和许多错误的量子特定性质,这使得模糊 Qiskit 变得特别困难挑战。我们的基线是 MorphQ ,这是一种最近的模糊器,它使用基于模板和基于语法的方法来生成有效的量子程序,然后应用变质变换。
与其他在文件中接收模糊测试输入的 SUT 不同,要调用 Qiskit,我们必须运行生成的 Python 程序本身。作为预言机,我们在生成的 Python 文件末尾添加语句,这些语句通过 Python 内置的内省 API 收集所有 QuantumCircuit 对象,然后在每个电路上应用两个预言机。这两个预言是直接借用以前的工作进行公平比较的。第一个预言机通过具有不同优化级别的转译调用来编译电路并报告任何崩溃。第二个预言机将电路转换为其较低级别的 QASM 表示形式,然后将其读回,报告任何崩溃。
4.3 实验设置和评价指标
模糊测试:对于研究问题 1,论文使用 24 小时的模糊预算(包括自动提示),这在之前的工作中常用。为了考虑方差,论文对 Fuzz4All 和基线重复实验五次。由于实验成本较高,对于后续的研究问题,论文使用 10,000 个生成的模糊输入的模糊预算,并重复四次进行消融研究。
实验环境:实验在具有 256 GB RAM 的 64 核工作站上进行,运行 Ubuntu 20.04.5 LTS,具有 4 个 NVIDIA RTX A6000 GPU(每次模糊测试仅使用一个 GPU)。
评价指标:论文广泛采用的代码覆盖率度量来评估模糊工具。为了保持统一,论文报告了评估中研究的每个目标的线路覆盖范围。根据之前的工作,论文使用 Mann-Whitney U 检验来计算统计显着性,并用 * 表示显着性 (p < 0.05) 覆盖结果。论文还测量输入的有效性(有效百分比),作为生成的有效且唯一的模糊输入的百分比。由于 Fuzz4All 支持一般模糊测试和目标模糊测试,为了评估目标模糊测试的有效性,论文报告命中率,即使用特定目标特征的模糊测试输入的百分比(使用简单的正则表达式检查)。最后,论文还报告了模糊测试最重要的指标和目标:Fuzz4All 为论文的九个 SUT 中的每一个检测到的错误数量。
5. 实验结果
5.1 RQ1:与现有模糊器进行对比
覆盖率随时间变化的分析:下图显示了 Fuzz4All 与基线相比的 24 小时覆盖率趋势,其中实线显示平均覆盖率,面积表示五次运行的最小值和最大值。可以发现,在模糊测试活动结束时,Fuzz4All 在所有目标上实现了最高的覆盖率,与表现最佳的基线相比,平均提高了 36.8%。与基于生成的模糊器(即 YARPGen 和 MorphQ)相比,Fuzz4All 几乎能够立即实现更高的覆盖率,这证明了与传统程序生成技术相比,LLM 在生成多样化代码片段方面的强大生成能力。虽然基于突变的模糊器(即 go-fuzz 和 GrayC)能够通过使用高质量种子在一开始就实现更高的覆盖率,但通过突变获得的覆盖率会迅速下降,而 Fuzz4All 能够缓慢但肯定地覆盖更多代码。
与基线模糊器不同,基线模糊器会在 24 小时结束时达到覆盖稳定水平,Fuzz4All 会不断寻找覆盖新代码的输入,甚至在模糊测试活动接近结束时也是如此。回想一下,在 Fuzz4All 模糊测试循环的每次迭代期间,原始输入提示都会使用新示例和生成策略进行更新,从而推动 LLM 生成新的模糊测试输入。这使得 Fuzz4All 即使在经过很长一段时间的模糊测试之后也能有效地生成新的、多样化的模糊输入,从而导致覆盖范围持续增加。
测试样例的有效性、数量以及覆盖率:论文检查了所研究的 SUT 中生成的模糊输入的数量及其有效性。下表中,“# programs”列代表测试样例生成的数量,“% valid”代表有效测试用例所占的百分比,“Coverage”代表每个模糊器获得的最终覆盖率以及相对改进。由下表可知,除了 Hephaestus 之外,几乎所有传统的模糊测试工具都可以实现非常高的测试用例有效性,Hephaestus 会故意生成无效代码(专注于不正确的类型)来检查错误编译错误。相比之下,Fuzz4All 生成的有效模糊输入的百分比较低(与基线工具相比,平均减少了 56.0%)。此外,基线工具生成的模糊输入的原始数量也高得多。通过使用 LLM 作为生成引擎,Fuzz4All 受到 GPU 推理的瓶颈,与传统模糊器相比,模糊测试输入减少了 43.0%。
尽管 Fuzz4All 输入的有效性和数量较低,但与传统模糊测试工具相比,Fuzz4All 生成的程序更加多样化,所获得的高覆盖率(平均增加 36.8%)证明了这一点。 此外,即使是接近有效的无效代码片段也可用于模糊测试,因为它们允许查找 SUT 验证逻辑中的错误。 Fuzz4All 在不同的 SUT 上实现了广泛的有效性和模糊输入数量。 由于每次模糊测试迭代后调用 SUT 的成本不同,不同目标的模糊测试输入数量也不同。 关于有效性,与特定领域语言(例如用于 SMT 求解器的 SMT2 语言)相比,通用编程语言(例如 C)的有效性相对较低。 更严格的语言,例如 Go,不允许任何已声明但未使用的变量,其有效率甚至更低。 我们还观察到模糊量子计算平台的有效性较低。 由于量子计算是一个新兴领域,拥有自己的一套库 API,因此 LLM 在训练期间可能不会像更成熟的语言那样看到那么多量子程序的例子。 尽管如此,Fuzz4All 仍然能够利用用户提供的文档来生成有趣的模糊输入,这些输入使用量子库 API,并与最先进的模糊器相比,实现了令人印象深刻的覆盖率提高 (+75.6%)。
5.2 RQ2:有针对性模糊测试的有效性
对于每个目标 SUT 和语言,论文针对 3 个不同的示例功能,并将它们与一般用户输入的设置进行比较,如 RQ1 所使用的。 这些功能是内置库或函数/API(Go、C++ 和 Qiskit)、语言关键字(C 和 Java)和理论(SMT)。 目标模糊测试运行的用户输入是论文关注的特定功能的文档。 下表显示了目标模糊测试的结果以及 RQ1 中使用的默认一般模糊测试的结果。 每一列代表一次有针对性的模糊测试运行,论文专注于一个功能。 每个单元格中的值显示特定模糊测试运行的特征命中率。 论文还包括获得的覆盖结果。
论文观察到,针对特定特征会产生大量直接使用该特征的模糊输入,平均命中率为 83.0%。 这一结果表明,Fuzz4All 确实通过使用描述特定功能的输入提示提示生成 LLM 来执行有针对性的模糊测试。 此外,论文观察到对相关特征进行模糊测试可以导致较高的跨特征命中率(即特征 X 在特征 Y 的模糊测试中的命中率)。 例如,C 关键字 typedef 和 union 都与类型操作相关,因此与 goto 等不相关的功能相比,它们的跨功能命中率较高。 如表 3 所示,一般的模糊测试方法虽然实现了最高的整体代码覆盖率,但在针对特定功能时效率极低(与 Fuzz4All 的目标模糊测试相比,命中率平均降低了 96.0%)。 例如,在 Qiskit 中,一般模糊测试活动的三个目标特征的命中率为 0%。 这可以通过以下事实来解释:这些功能是最近添加到 Qiskit 中的,尚未广泛使用,因此在 LLM 训练数据中极为罕见。 然而,通过在目标模糊测试活动期间提供合适的用户输入,Fuzz4All 可以成功生成使用这些新功能的模糊输入。 Fuzz4All 的这种能力对于想要测试 SUT 的新颖功能或组件的开发人员来说非常有价值。
5.3 RQ3:消融实验
为了研究 Fuzz4All 的每个组件如何对整体模糊测试有效性做出贡献,论文基于 Fuzz4All 的两个关键组件进行了消融研究:提示词自动生成,为生成 LLM 提供的初始输入提示类型;模糊测试循环,使用选定的示例和生成策略。 我们研究了两个关键组件中每一个的三种变体。 下表显示了论文研究的变体的覆盖率和有效性。
提示词自动化生成:论文研究了为生成 LLM 提供的不同初始输入的影响。 为了减少附加因素的影响,论文的生成策略仅使用 generate-new 并研究三种变体:无输入不使用任何初始提示、直接使用原始用户输入作为初始提示、提示词自动生成。无输入变体:不提供任何初始提示,LLM只能生成有效率较高的简单代码片段,但对 SUT 的覆盖效果较差。 原始提示变体:覆盖率得到一定提升,但直接使用用户提供的输入可能包含与模糊测试无关的信息,从而导致较低的有效性(因为生成的 LLM 可能很难理解原始文档)和较低的覆盖率。提示词自动生成:将用户输入提炼为简洁但信息丰富的提示(自动提示)来进一步提高代码覆盖率和有效性。
模糊测试循环:论文通过保持初始提示相同(通过使用默认自动提示)来检查模糊循环设置的不同变体,w/o 示例在模糊测试循环期间不选择示例(即,它连续地从相同的初始提示)、w/example 选择一个示例(仅使用生成新指令)、Fuzz4All 使用所有生成策略的完整方法。实验结果表明,通过仅从相同的输入(没有示例)进行采样,LLM 通常会重复生成相同或相似的模糊输入。平均而言,在无示例中,生成的模糊输入中有 8.0% 是重复的,而使用完整的 Fuzz4All 方法时,这一比例仅为 4.7%。在输入提示中添加示例(带示例)可以避免从同一分布中采样,并提高覆盖率和有效性。最后,完整的 Fuzz4All 方法在所有 SUT 中实现了最高的覆盖率。与带有示例的变体(第二好的)相比,完整的 Fuzz4All 添加了额外的生成策略、语义等效和变异现有策略,为生成 LLM 提供了有用的指令。
5.4 RQ4:现实世界 BUG 检测
下表总结了 Fuzz4All 在 9 个 SUT 上发现的错误。 Fuzz4All 总共检测到 98 个错误,其中 64 个错误已被开发人员确认为以前未知的。 这些结果不仅证明了 Fuzz4All 在发现大量错误方面的实际有效性,而且还证明了 Fuzz4All 跨语言和 SUT 的通用性。
6. 总结
论文推出了 Fuzz4All,这是一种通用模糊器,利用 LLM 来支持采用多种编程语言的任意 SUT 的一般和有针对性的模糊测试。 Fuzz4All 使用新颖的自动提示阶段来生成输入提示,简洁地总结用户提供的输入。 在其模糊测试循环中,Fuzz4All 使用代码示例和旨在生成不同模糊输入的生成策略迭代更新初始输入提示。 对六种不同语言的九种不同 SUT 的评估结果表明,与最先进的工具相比,Fuzz4All 能够显着提高覆盖率。 此外,Fuzz4All 能够检测到 98 个错误,其中 64 个错误已被开发人员确认为以前未知的。