大语言模型拥有的强大能力可以用来辅助多种工作,但如何有效的辅助仍然需要人的精巧设计。分享一篇发表于2024年CCS会议的论文PromptFuzz,它利用模型提示生成模糊测试驱动代码,并将代码片段嵌入到LLVM框架中执行模糊测试。
论文摘要
制作高质量的模糊测试驱动程序不仅耗时而且还需要对被测目标有深入的了解,即使是最先进的自动化模糊测试驱动程序生成技术也未能达到预期。虽然用被测目标代码派生(OSS-Fuzz)的方式可以达到深度状态,但是程序逻辑的覆盖范围有限。解释性模糊测试(Hopper)可以探索多数接口调用,不过需要在较大的搜索空间进行多次尝试。
论文提出了 PromptFuzz ,一种覆盖引导的模糊器,它可以迭代生成模糊测试驱动程序来探索未被发现的库程序代码。通过使用大模型提示词探索被测程序的接口调用,本文提出了几种关键技术,包括:1)指导程序生成,2)错误程序验证,3)覆盖引导的提示变异,4)变量约束的模糊器调度。PromptFuzz 在 14 个真实的库程序上进行了评估,模糊测试驱动程序的分治覆盖率相比于 OSS-Fuzz 和 Hopper 分别高出 1.61 倍和 1.63 倍。此外,所提方案在 49 次崩溃中检测到了 33 个新的漏洞,其中 30 个漏洞已得到相应社区的确认。
1 背景介绍
模糊测试对软件的安全性和可靠性至关重要。OSS-Fuzz为开源软件部署了最先进的模糊测试器,截至2023年2月,已在850个项目中发现并解决了8900多个漏洞和28000个错误。开发者会选择合适的模糊测试器(Fuzzer )并编写高质量的模糊测试驱动程序(Fuzz Driver ),驱动程序会解析来自模糊测试器的输入并调用被测目标(Target or Library)的程序代码。然而,编写高质量的模糊测试驱动程序具有挑战性,因为它既耗时又需要对被测目标有深入的了解。手动编写的模糊测试驱动程序通常只调用了被测目标的一小部分功能,限制了模糊测试的能力。
与手动编写的模糊测试驱动程序相比,自动化技术通过从源代码或运行时反馈中学习被测目标的接口调用情况,从而派生出模糊测试驱动程序。FUDGE,FuzzGen,UTopia方案从源代码中采用静态分析的方式提取接口调用代码,而APICraft,WINNIE则从进程执行中动态跟踪记录接口的调用顺序。Hopper是最先进的模糊测试驱动程序生成解决方案(与本文同一团队的工作,发表于2023年CCS会议),它会将对被测目标的模糊测试问题转化为解释性模糊测试问题,从接口调用的动态反馈中学习有效的接口使用情况。尽管可以覆盖到大多数接口函数,但Hopper需要在广阔的搜索空间中进行多次尝试,才能找到有用且满足深度的接口调用序列。
大语言模型(LLM)在生成代码方面有出色的表现,可以在不依赖被测目标代码的情况下可以有效地探索接口使用情况。以GPT系列为例,它们在广泛的代码预料库上进行过训练,能够生成符合用户意图的代码。之前的工作也尝试使用LLM生成模糊测试驱动程序,但它们设计的指令仅限于特定场景,生成的驱动程序在接口调用上多样性较低,无法覆盖不常用代码或深度状态。本文引入了PromptFuzz,一种覆盖引导的模糊测试器,它会迭代地改变提示词以探索未发现的库程序代码。
2 基础概念
- 库程序模糊测试
库程序在软件开发中被广泛使用,因此针对它的模糊测试变得越来越重要。与命令行程序不同,库程序拥有多个访问入口点,即程序接口函数,这些入口有严格的格式约束规范。为了能够利用现有的模糊测试器,相应的模糊测试驱动程序被开发出来,驱动程序从模糊测试器接受随机字节,然后将这些字节转换成结构良好的接口调用参数,喂给被测目标执行模糊测试。
论文给出了一个模糊测试驱动程序的例子,该驱动程序嵌入在LLVM框架中,每次执行一个测试用例。驱动程序接受数据和大小两个参数,被测目标为视频解码库libvpx,驱动程序执行初始化和数据转换操作,调用库程序的接口函数进行视频解码。
c
#include <vpx/vp8dx.h>
#include <vpx/vp8cx.h>
#include <vpx/vpx_decoder.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Create the decoder configuration
vpx_codec_dec_cfg_t dec_cfg = {0};
...
// Initialize the decoder
vpx_codec_ctx_t decoder;
vpx_codec_iface_t *decoder_iface = vpx_codec_vp8_dx();
vpx_codec_err_t decoder_init_res = vpx_codec_dec_init_ver(&decoder, decoder_iface, &dec_cfg, 0, VPX_DECODER_ABI_VERSION);
if (decoder_init_res != VPX_CODEC_OK) {
return 0;
}
// Process the input data
vpx_codec_err_t decode_res = vpx_codec_decode(&decoder, data, size, NULL, 0);
if (decode_res != VPX_CODEC_OK) {
vpx_codec_destroy(&decoder);
return 0;
}
// Get the decoded frame
vpx_image_t *img = NULL;
vpx_codec_iter_t iter = NULL;
while ((img = vpx_codec_get_frame(&decoder, &iter))!= NULL) {
// Process the frame
vpx_img_flip(img);
...
}
// Cleanup
vpx_codec_destroy(&decoder);
return 0;
}
- 大语言模型
LLM是一种深度学习模型,具有非常复杂的架构和大量的参数,使得它们能够从大量文本数据中获取知识,GPT3,ChatGPT,GPT4是当前十分具有代表性的LLM。大语言模型被训练为预测下一个词,表示为 w n + 1 w_{n+1} wn+1,给定一个词序列 w 1 , w 2 , . . . , w n w_1,w_2,...,w_n w1,w2,...,wn,最大化语言模型的目标函数,如下列公式。
P ( w 1 , w 2 , . . . , w n ) = ∏ i = 1 n P ( w i ∣ w 1 , w 2 , . . . , w i − 1 ) P(w_1,w_2,...,w_n)=\prod^n_{i=1}P(w_i|w_1,w_2,...,w_{i-1}) P(w1,w2,...,wn)=i=1∏nP(wi∣w1,w2,...,wi−1)
在推理阶段,LLM利用广泛参数中学习到的模型权重,基于先前的token
w 1 , w 2 , . . . , w n w_1,w_2,...,w_n w1,w2,...,wn,自动回归地生成下一个token
w n + 1 w_{n+1} wn+1,用户提供的起始token
被称为提示词。为了确保LLM产生的输出与用户给定指令保持一致,一系列LLM已通过强化学习训练得到增强,例如ChatGPT和GPT4。
- 基于大语言模型的模糊测试驱动程序生成
最近,出现一些利用LLM来增强模糊测试的研究,其主要的挑战包括自动构造提示词和对模型输出的验证。在基于LLM的模糊测试驱动程序生成中,提示词通常由任务描述和上下文信息组成。为了尽可能提供信息和指导模型,任务描述应至少制定被测目标库程序,以及包含在其中的接口函数。在早期的尝试中,每个提示词仅分配一个接口函数作为目标,过于简单而且难以有效果。另一方面,LLM生成的代码无法直接用于模糊测试,因为它产生的代码很容易出错。依赖编译器或简单规则进行输出的验证,只能报告语法或浅层逻辑错误,当其用作模糊测试驱动程序时,这种缺陷代码会产生许多误报。
3 系统设计
PromptFuzz通过覆盖率引导的LLM提示词生成高质量的模糊测试驱动程序以检测库程序错误,它会改变LLM提示词以生成涵盖更广泛接口调用范围的驱动程序,首先随机选择一个库接口函数构造提示词,然后根据覆盖率反馈改变该提示词,直到模糊测试达到被测目标的收敛,工作流程如图1所示。
图1 PromptFuzz模糊测试驱动程序生成流程
3.1 指导程序生成
论文选择ChatGPT和GPT-4作为大语言模型来指导模糊测试驱动程序生成,尽管模型生成的程序并不总能够严格遵循指令,但它们有助于探索有效的库程序接口调用情况,可以用提示词来引导大语言模型生成符合预期的程序。PromptFuzz使用目标库程序和接口函数组合填充提示词模板,如图2所示包含以下组件。
- 任务描述。说明了LLM应生成的驱动程序代码,指定了库程序的哪些接口函数在
LLVMFuzzerTestOneInput
函数中是必需的。 - 库程序上下文。包括了库程序使用的头文件,接口函数签名,自定义类型等信息,通过整合对库程序上下文的理解,显著减少LLM产生幻觉的发生。
- 库程序说明。指导LLM生成符合库程序所需指定模式的代码,部分库程序的接口函数可能从文件,文件流或描述符读取输入,对其进行相应规范。
图2 提示词模板
3.2 错误程序验证
PromptFuzz消除错误驱动程序通过如下三个步骤。
- 删除C/C++编译器识别出语法错误的驱动程序。
- 剩余的驱动程序编译成可执行文件,结合多个运行时
sanitizers
,捕获和分析与预期行为模式的偏差。 - 使用提供的语料库对这些驱动程序进行模糊测试,删除检测到偏差的任何驱动程序。
在这部分模糊测试过程中,触发独特行为的输入将添加到语料库中,从而扩展进行更深入的运行时验证。PromptFuzz同时还会计算驱动程序执行的代码覆盖率,删除那些不符合代码覆盖标准的驱动程序,表明库程序的接口函数得到了充分的利用,错误驱动程序验证流程如图3所示。
图3 错误驱动程序验证流程
3.3 覆盖引导的提示变异
为了创建连续多轮次的提示词,PromptFuzz会改变前几轮提示词中的接口函数组合,生成不同的模糊测试驱动程序,同时以代码覆盖率作为反馈来生成有效的提示词。
- 能量分配
首先,PromptFuzz为每个接口函数分配相同的能量,在每次模糊测试迭代期间,更新访问过的分支并计算接口函数的分支覆盖率。
c o v ( i ) = 包含 i 的覆盖的分支数 包含 i 的所有的分支数 cov(i)=\frac{包含i的覆盖的分支数}{包含i的所有的分支数} cov(i)=包含i的所有的分支数包含i的覆盖的分支数
接着,按照AFLFast中的指数调度来更新其能量,令 s e e d ( i ) seed(i) seed(i)为调用接口函数 i i i的种子驱动程序数量, p r o m p t ( i ) prompt(i) prompt(i)表示为包含接口函数 i i i的提示词数量,计算能量如下。
e n e r g y ( i ) = 1 − c o v ( i ) ( 1 + s e e d ( i ) ) e × ( 1 + p r o m p t ( i ) ) e energy(i)=\frac{1-cov(i)}{(1+seed(i))^e \times (1+prompt(i))^e} energy(i)=(1+seed(i))e×(1+prompt(i))e1−cov(i)
执行越少次数的接口函数将被分配更高的能量,于是在未来的提示词中包含该接口函数的概率就越高。
- 变异策略
然后,PromptFuzz会改变提示词中的接口函数组合以指导模糊测试驱动程序生成,这些策略与传统的模糊测试器类似,例如在C组合中插入A函数Insert(C,A)
,替换C组合中的A函数为B函数Replace(C,A,B)
,合并C组合和S组合形成新的组合CrossOver(C,S)
。
在接口函数能量的指导下,PromptFuzz通过变异策略组合接口函数以生成之前未探索过的组合,用density
表示调用显式数据依赖的库程序接口函数的最大数量,用unique branches
表示驱动程序执行时触发的独立分支数量。计算quality(g)=density(g) x (1+unique_branches(g))
用以量化驱动程序的质量,以质量值更高为目标来指导变异策略
图4 接口函数组合变异算法
在每次模糊测试迭代过程中,PromptFuzz都会探索驱动程序种子集合并更新这些种子驱动程序的质量,使用库程序接口函数能量和种子驱动程序质量的反馈,应用如图4所示算法来选择下一次迭代中使用的新接口函数组合。
3.4 变量约束的模糊器调度
为了使得种子驱动程序能够执行模糊测试,PromptFuzz对其中的接口函数参数进行约束类型推断,将部分参数从常量转换为从模糊测试器输入的任意字节的变量,例如数组长度、文件名、格式化字符串等,一个简单的实例如图5所示。
图5 约束参数转换例子
最终,PromptFuzz将种子驱动程序整合到模糊测试器,根据其提供的几个特定字节来调度每个种子驱动程序,3.2节中进行错误程序验证的模糊测试语料将用来作为驱动程序的初始输入。此外,转换之前的参数常量值也将形成初始语料,配合以进行模糊测试器的执行。
4 实现验证
PromptFuzz用Rust语言实现了大约1万7千行代码,开源在仓库https://github.com/PromptFuzz/PromptFuzz中,其中使用了clang_ast
做抽象语法树的提取,额外实现了FSan
插件来进行文件描述检查。论文在14个广泛使用的开源项目上对PromptFuzz进行了评估,总体实验结果,发现的已知漏洞,消融实验和变异策略的比较,可以详细去看原论文,这里不再赘述。
学习笔记
这篇论文做了很好的尝试,利用大语言模型来生成针对被测目标的驱动程序,再使用传统的模糊测试器来执行测试,相关工作OSS-Fuzz也做过尝试。论文偏工程,有一定启发,方法比较杂,与近期部分LLM+Fuzz方向的研究类似,大语言模型用于辅助仍需要进行提纯,通过算法筛选和纠偏,但确实在自动化和发散性方面比较有效。最后,附上文献引用和DOI链接:
Lyu Y, Xie Y, Chen P, et al. Prompt Fuzzing for Fuzz Driver Generation[C]//Proceedings of the 2024 on ACM SIGSAC Conference on Computer and Communications Security. 2024: 3793-3807.