大语言模型拥有的强大能力可以用来辅助多种工作,但如何有效的辅助仍然需要人的精巧设计。分享一篇发表于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,驱动程序执行初始化和数据转换操作,调用库程序的接口函数进行视频解码。
#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)