理解基于大模型的模糊测试驱动生成技术

互联不一般哥 2024-05-05 10:14:57

Understanding Large Language Model Based Fuzz Driver Generation

Cen Zhang Mingqiang Bai ¶ Yaowen Zheng Yeting Li ¶ ‡ Xiaofei Xie £ Yuekang Li §

Wei Ma † Limin Sun ¶ Yang Liu

Nanyang Technological University ¶ Institute of Information Engineering, CAS

School of Cyber Security, University of Chinese Academy of Sciences

£ Singapore Management University § University of New South Wales

引用

Zhang C, Bai M, Zheng Y, et al. Understanding large language model based fuzz driver generation[J]. 2023.

论文:https://arxiv.org/abs/2307.12469

仓库:https://sites.google.com/view/llm4fdg/home

摘要

基于大模型的模糊测试驱动生成技术相比于传统的基于程序分析的生成技术能够更好地利用各个维度的API使用信息,生成更加准确、健壮并且对人类友好的模糊测试驱动代码。然而,目前人们对这种基于大模型的驱动生成技术仍然缺乏基本的认知。

为了填补这一空白,作者进行了相关研究,探讨了使用大模型生成高质量模糊驱动存在的基本问题。作者构建了一个半自动框架对大模型的生成能力和效果进行测验,这个框架包含了从30个流行的C项目中收集的86个驱动生成问题,以及一套用于验证驱动程序有效性的标准。最终,作者总共生成和评估了189628个模糊测试驱动程序。作者的研究结果表明,基于大模型的模糊测试驱动技术具有很好的实用性,但是也存在一些缺陷,比如不擅长生成具有复杂API使用细节的测试驱动,以及大模型在语义正确性的自动识别和测试预言生成等方面仍有很大的提升空间。

1 引言

模糊测试(Fuzzing)现在已经成为发现零日漏洞的基本方法。作为模糊测试直接执行的程序,模糊测试驱动(Fuzz Driver)是对库API进行模糊测试的必不可少的一个组件。本质上,模糊测试驱动是一段代码,负责接受来自模糊器(Fuzzer)的变异输入,并调用待测API。一个合格的驱动应该包含正确且健壮的API使用,以防驱动本身的问题导致的假阳性或者假阴性结果影响测试。由于这些严格的要求,模糊测试驱动的编写往往交由领域专家编写,是一项耗时耗力的工作。

生成式大语言模型(Generative Large Language Models)最近因其在代码生成任务中的优秀表现获得了极大的关注。大语言模型是在大量文本和代码上训练出的语言模型,人们可以使用自然语言对其进行提问并获得对应的自然语言回答。使用大语言模型进行模糊测试驱动的生成被认为是一个很有发展前景的研究方向,主要原因有以下三点:1)大语言模型的一个基本应用场景就是推断API使用方法,这和模糊测试驱动生成的任务目标一致;2)大语言模型作为模糊测试驱动生成平台能够提供更加轻量和通用的服务,使用者只需提供自然语言文本即可,无需对现有代码进行程序分析;3)大语言模型生成的代码更易于人们的理解。目前已有多项研究探索使用大语言模型进行模糊驱动的生成,然而它们中却没有一个提供了对本研究方向的基本认知和理解的阐述。

图1: 查询策略分类

为了弥补这一空缺,作者进行了相关研究,希望能够探索出怎样使用大语言模型才能针对更多的项目生成更多的可用模糊测试驱动。作者首先对询问方式从信息类型和对话类型两个维度进行了分类,结果如图1所示。基本的询问策略(Basic Strategy)只在提示词中附带有基本的API信息,例如头文件名、函数声明等,同时只使用单轮的对话。加强式策略(Enhanced Strategy)则在提示词中携带有更多的信息,例如API文档、示例代码等,以及使用交互式的多轮迭代对话逐渐提高生成驱动的质量。

为了评估生成的模糊测试驱动的有效性,作者开发了一个半自动评估框架,其中包含有一个测验和一组有效性评判标准。测验中收集了来自30个OSS-Fuzz上C项目的86个问题,每一个问题都是一个待生成驱动的API。验证生成驱动的有效性主要分为两部分:1)检查驱动能否支撑短时间的模糊测试;2)能否通过人工编写的对驱动语义有效性验证的测试。最终,本框架对189628个生成的模糊测试驱动进行了评估。

评估结果表明,基于大语言模型的模糊测试驱动技术有很高的实用性。各种策略综合起来共解决了55个(64%)测验中的问题,加上人工的修改可以解决78个 (91%)问题。根据不同的询问策略评估,基本策略已经可以表现出不错的结果(53%, 46/86),特别是考虑到其技术复杂性较低且提供的信息有限。带有代码示例的询问(82%, 72/86)和迭代式的询问(91%, 78/86)则表现出更好的成绩。另外,与OSS-Fuzz上的模糊驱动相比,大语言模型生成的模糊驱动可以达到类似的测试结果。但是,大语言模型在加强语义正确性和测试预言生成等方面仍有提升空间。

总的来说,本文贡献如下:

作者对基于大语言模型的模糊驱动生成的有效性进行了首次深入研究,展示了该方向的实用性和挑战;作者设计并实施了五种驱动生成策略,进行大规模评估,系统分析其有效性;作者构建了第一个模糊驱动评估框架,可以大规模评估生成的模糊驱动;作者将生成的驱动与工业使用的驱动进行了比较,并总结了未来的提升方向。

2 本文方法

图2展示了研究的概述。为了评估不同生成策略的有效性,作者建立了一个评估框架,其中包含一系列生成问题和有效性标准。每个问题都要求大语言模型根据给定的API生成一个模糊驱动程序,并根据标准评估所生成的驱动程序的有效性。

在此基础上,作者研究了从基本到增强的五种查询策略的有效性(前两个RQ)。在RQ1中,作者设计并探索了利用基本API信息并与大语言模型进行简单交互的基本策略的有效性;在RQ2中,作者研究了利用扩展API使用信息和交互式查询的增强策略。作者进行了必要的深入分析,以解释这些策略的优点和限制。最后,在RQ3中,作者比较了大语言模型生成的模糊驱动程序与OSS-Fuzz驱动程序,以了解它们的位置、特征和限制。

图2: 研究概览

2.1 测试设计

测验中的问题设计为针对给定 API 生成模糊驱动程序。然而,并非所有 API 都适合作为问题。简单地将所有 API 都设为问题会导致创建无意义或令人困惑的问题,从而影响评估结果。具体来说,一些 API,例如 void libxxx_init(void),是无意义的模糊目标,因为这些 API 后面执行的代码不受任何输入数据的影响。有些 API 只能作为辅助 API,而不是主要的模糊目标,这取决于它们的功能性质。例如,给定两个 API:object *parse_from_str(char *input) 和 void free_object(object *obj),将变异的数据输入到 input 中显然比向 obj 参数提供一个无意义的指针更好。然而,在模糊前者时调用后者的 API 是有意义的,因为:1. 当前者未正确初始化object * 时,后者可能会揭示隐藏的错误;2. 它释放了本次模糊中分配的资源,从而避免了错误地报告内存泄漏问题。

为了确保测验中的问题合格且具有代表性,作者从 OSS-Fuzz 项目中收集了现有模糊驱动程序的核心 API。一个驱动程序的核心 API 根据以下标准确定:1. 它们是作者在驱动文件名或代码注释中明确指出的目标 API,例如 dns_name_fromwire 是驱动程序 dns_name_fromwire.c 的核心 API;2. 否则,作者选择基本的 API 作为核心,而不是辅助的 API。例如,在解析和使用/释放 API 之间,作者选择前者作为核心。对于同时模糊多个 API 的复合驱动程序,作者从中识别出多个核心 API。

2.2 建立有效性标准

图3: 一个模糊测试驱动的有效性验证过程

一个有效的模糊测试驱动程序应该在有效利用API的同时不产生误报。确定驱动程序的有效性是具有挑战性的,因为它涉及到对误报(由驱动程序代码引起的错误)和负面情况(无效使用)的语义分类。为了精确验证,作者采用了半自动化的方法。如图4所示,它包含四个检查器。前两个是自动的和通用的检查,用于判断给定生成的驱动程序是否能够成功进行短期模糊测试。如果编译失败或短期模糊测试(使用空的初始种子进行一分钟)报告任何错误或未有覆盖率进展,则该驱动程序无效。显然,通用检查是一种粗略的度量,可能导致误验证。剩下的两个检查器旨在细化验证。通过对OSS-Fuzz提供的驱动程序进行模糊测试,作者收集了能够快速发现的真实错误的特征。作者在第三个检查器中筛选了这些错误。最后,通过手动检查API的使用情况,作者总结了API的语义约束,并编写了检查这些生成的驱动程序的测试。例如,假设一个API要求通过文件传递变异输入,则语义测试是使用钩子API传递并检查传递的文件名是否指向包含变异输入的现有文件。

2.3 建立评估框架

根据测验和标准,作者建立了一个用于规模化自动化驱动程序评估的框架。整体上,该框架由Python/HTML/JSON、YAML和Bash脚本共计9,342/1,542/3,857行编写而成。它接受由查询策略生成的提示作为输入,并输出经过分类的验证结果。为了正确评估大语言模型生成的驱动程序,作者在提示中添加了格式控制声明,并且如果答案同时包含代码和文本,则提取其中的代码部分。此外,为了专注于驱动程序代码的评估,作者提前准备了编译环境,并且每个驱动程序都在一个新的、隔离的容器中进行验证。经过验证的结果根据检查器clang 和libfuzzer的结果的字符串模式进行分类。这一分类逻辑是通过手动审查和修正不断迭代发展而来的。

3 实验评估

作者针对三个研究问题进行了实验设计和结果分析:

RQ1:使用基础的询问策略能够生成有效的模糊测试驱动吗,具体效果如何?

RQ2:加强版的询问策略能够提高生成的效果吗?特点是什么?

RQ3:相比于OSS-Fuzz中的驱动,大语言模型生成的模糊测试驱动效果怎样?

模型建立

表1: 被评估的大模型

表1列出了主要用于评估的大语言模型和参数。temperature和top_p均设为默认值1。超出token限制的提示将根据策略进行调整。对于提供角色参数的模型,系统角色设定为“You are a security auditor who writes fuzz drivers for library APIs”。除非在策略中有特别提及,否则以下句子将被插入到提示的开头,以帮助规范化输出格式:“The following is a fuzz driver written in C language, complete the implementation. Output the continued code in reply only.\n\n”。

RQ1基础策略的生成效果

询问策略设计。表2详细介绍了设计的两种策略:NAIVE-K和BACTX-K。这两者之间的关键区别在于它们的提示模板。具体而言,NAIVE-K直接要求大语言模型仅基于指定的函数名称来实现模糊驱动程序,而BACTX-K则提供了API的基本描述。在BACTX-K的提示中,首先使用#include语句指示任务范围,然后提供函数声明,最后请求实现。声明是从头文件的抽象语法树(AST)中提取的,包括签名和参数变量名称。策略名称中的后缀K表示查询将重复K次,直到解决给定的问题。这是出于以下观察:大语言模型的回复具有随机性,它们甚至可以为两个相同的查询提供非常不同的答案。因此,有必要将其包括在内以更全面地理解策略的有效性。在作者的研究中,最大的K设置为40。

表2: 两种基础的询问策略

测验表现。图4a展示了每轮中正确问题的数量,图4b则详细描述了堆叠数量。在两个图中,线型用于区分模型,即实线代表gpt4,虚线代表gpt3.5;而线的颜色则表示提示模板的类型,即黑色代表BACTX-K,红色代表NAIVE-K。显而易见,gpt4-BACTX-K表现出比其他三种策略更优异的性能,而gpt3.5-NAIVE-K的表现最低。这是直观的,因为gpt4-BACTX-K配置了一个预期更为强大的模型,并生成了更为详细的问题提示。总的来说,大语言模型模型、提示模板以及重复查询程度是三个独立的关键因素。作者还通过比较单独解决的问题集合来分析问题级别的性能。结论是,尽管大多数策略都有它们单独解决的问题,但在大多数项目中,gpt4-BACTX-K表现优于其他策略。

总之,大语言模型的类型、提示模板和重复查询次数对策略的整体性能都有显著影响。具体来说,重复查询的好处大致遵循80/20规则。通过40次重复,最佳基础策略gpt4-BACTX-K可以正确回答40.70%的问题。

图4: 基础策略回答正确的问题(K ∈ [1,40])

具体问题和表现的关系。尽管已经讨论了影响整体表现的关键因素,但生成效果与具体问题之间的关系仍不明确。在驱动程序生成过程中,大语言模型的性能会下降,特别是当它们需要考虑更多的API特定用法时。为了确证这一观察结果,作者首先解释了大语言模型在生成过程中可能面临的挑战,然后引入了一个评分系统,用于量化API特定用法的程度,以证明它们之间的关联。最终作者发现:当 API 使用的复杂性增加时,基于 大语言模型 的生成效果会显著下降。

失败结果分析。作者针对失败的结果进行了分析,试图解答大语言模型模糊测试驱动生成失败的原因。具体来说,作者收集了针对gpt3.5/4-BACTX-K的未解决问题集合(58个问题),以揭示当前生成中的主要障碍。总共分析了9,107个无效驱动程序。其中2,734个驱动程序的运行时错误是手动分析的,而编译和链接错误则根据编译器提供的错误信息进行分类。最终作者发现,大多数失败的生成都是在API特定使用上犯的错误,涉及各种维度,包括目标API及其依赖的API,从语法细节到语义约束不等。所涉及的使用广度给进一步改进带来了巨大挑战。

RQ2 加强策略的生成效果

表3: 两种加强的查询策略

带有扩展信息的查询。作者研究了两种扩展信息的有效性:API文档和示例代码片段。表3详细描述了两种策略,这些策略是从BACTX-K扩展而来的,通过向提示模板中添加额外的使用信息来实现。值得注意的是,对于DOCTX-K,并非所有API都有相关文档(作者的问卷中有49/86个问题没有)。其中20个问题的文档是从头文件中自动提取的,而其余29个问题是从项目网站、存储库和开发者参考资料等来源手动收集的。对于UGCTX-K,API的示例代码片段收集如下:❶通过SourceGraph cli 检索包含使用代码的文件;❷识别并排除作为模糊驱动程序的文件;❸通过ANTLR提取直接调用目标API的函数作为示例代码片段;❹通过比较它们的Jaccard相似性(≥95%)对片段进行去重。在生成提示时,UGCTX-K将从其收集中随机选择一个片段。对于太长而无法包含在提示中的片段,它们将按行截断,以满足令牌长度的限制。

最终结果表明,在提示中添加API文档可以改善整体性能。然而,由于其包含的使用描述有限,改善效果并不显著。另一方面,示例代码片段可以通过提供对代码使用的直接案例,极大地增强模型的生成效果。此外,“测试/示例文件”和“目标/变体项目的代码文件”都是高质量的信息来源。

迭代式查询。算法1展示了设计的迭代查询策略的一般工作流程。其主要思想是通过不断改进生成的驱动程序来达到最终停止条件。从本质上讲,这是一个搜索问题,其中在查询生成过程中所做的选择(第2行,第10行)以及搜索深度(第11行)共同构成搜索空间。对于初始查询,它可以是BACTX-K、DOCTX-K和UGCTX-K中的任一个。

对于固定查询,设计了七个固定模板,用于修复从驱动程序中观察到的不同错误。错误根据涵盖编译、链接和运行时模糊错误法进行分类。表4显示了一般修复模板。在模板中,"${ERR_XXX}"是特定错误类型的内容。"${SUPPLEMENTAL_INFO}"将包含额外的API使用信息。对于语法错误,很容易定位造成该错误的API,并提供正确的使用方法。对于语义错误,通过识别从错误行开始找到的第一个API来定位导致错误的API。停止条件要么是达到了最大迭代轮次,要么是大语言模型的回复无法修复(不包含代码或损坏的代码)。作者开发了两种策略BA-ITER-K和EX-ITER-K(统称为ITER-K)。前者仅使用API和错误的基本信息来生成查询,而后者可以使用任何可访问的信息。EX-ITER-K随机选择选择项。这两种策略都使用了最大搜索深度=20和最大K=40进行评估。

表3: 修复提示词的模板

图5和6提供了针对迭代式查询的实验比较结果。总体而言,ITER-K策略在性能上具有明显优势。这种策略几乎解决了UGCTX-K解决的所有问题,并且还额外单独地解决了九个问题,只有六个问题未解决。此外,其问题答对率始终高于对应策略的答对率(红线高于黑线)。值得一提的是,大多数情况下ITER-K的查询成功率低于UGCTX-K,表明其在生成过程中具有更高的平均搜索成本。

图5:迭代查询的比较图

图6:ITER-K (K=20)和UGCTX-K (K=40)的集合图

实验结果表明,迭代策略所需的查询数量平均为非迭代策略的3-5倍。迭代式查询策略相较于非迭代式策略,在为更多目标生成有效的模糊驱动器方面具有优势。这一优势源于其能够在生成过程中利用多样化的使用信息,并逐步解决问题的能力。然而,这种优势的获取是以较高的搜索成本和增加的复杂性为代价的。

RQ3 对比OSS-Fuzz中的模糊测试驱动

实验设计。作者对大语言模型生成的模糊测试驱动与OSS-Fuzz中的驱动进行了比较,从而获得更多实用性见解。需要注意的是,OSS-Fuzz的模糊测试驱动在工业中实际用于持续模糊测试,其中大多数是经过多年手动编写和改进的。作者将gpt4-ITER-K生成的所有模糊测试驱动与OSS-Fuzz上的进行了比较,涵盖了78个问题。作者将一个问题的多个模糊测试驱动合并为一个,以便进行比较。这是通过添加一个包装片段来完成的,该片段将种子调度与从合并驱动程序中选择的执行逻辑连接起来。具体来说,它使用一个开关结构来确定根据输入数据的一部分执行哪种逻辑。在每次模糊测试迭代期间,只执行一个合并驱动程序的逻辑。此外,一些复合的OSS-Fuzz驱动程序旨在模糊测试多个API。为了进行清晰的比较,作者将涉及一个复合驱动程序的所有问题的驱动程序合并为一个。总共准备了57个gpt4-ITER-K和OSS-Fuzz的模糊测试驱动程序。

代码度量:API使用和测试预言。对于API的使用的评估是通过模糊测试驱动中使用的独特项目API的数量来度量的。总的来说,49%(17/35)的gpt-BACTX-K生成的模糊测试驱动使用的项目API少于OSSFuzz的,而gpt4-ITER-K的比例为14%。通过手动调查这些模糊测试驱动,作者发现在没有明确提示的情况下,大语言模型在驱动程序生成时会保守地使用API。例如,对于gpt4-BACTX-K,如果没有明确的提示,它倾向于仅提供必要的用法,例如参数初始化,几乎不会扩展API的使用。这是一个合理的策略,因为过于积极地扩展API会增加生成无效驱动程序的风险。gpt4-ITER-K提供的示例缓解了这种情况。至于OSS-Fuzz驱动程序,API使用的多样性因来源于不同贡献者而各不相同。一些驱动程序由最少需要的部分组成,而另一些则广泛探索了目标的更多功能。一个有趣的发现是,一些OSS-Fuzz上的模糊测试驱动是从测试文件修改而来,而不是从头编写的,这与使用示例查询大语言模型的过程非常相似。

作者还对模糊测试驱动中的测试预言进行了统计。结果非常清晰:在所有的78个问题中,OSS-Fuzz上的模糊测试驱动中的15个问题包含至少一个能够检测到语义错误的预言,而大语言模型生成的驱动程序则没有。所使用的语义预言可以归为以下几类:❶ 检查API的返回值或输出内容是否符合预期;❷ 检查项目内部状态是否具有预期值;❸ 比较多个API的输出是否符合特定关系。

总而言之,大语言模型更倾向于生成使用API最少的模糊测试驱动。尽管示例代码可以帮助大语言模型生成比OSS-Fuzz上方模糊测试驱动包含更多使用情况的模糊测试驱动,但大语言模型生成驱动的API使用丰富度和测试预言生成仍有很大的提升空间。

模糊测试度量:覆盖率和崩溃。总体而言,在大多数问题中,由大语言模型生成的模糊测试驱动程序在覆盖率和独特崩溃数量的指标上表现出相似或更好的性能。需要注意的是,由于作者的评估框架提供的语义检查器已经对生成的模糊驱动程序进行了过滤,因此不存在虚假阳性。如果仅采用完全自动化的验证过程,即删除图3中的最后两个检查器,则将导致23个问题的模糊结果混乱,出现大量虚假阳性,需要进行大量手动后处理工作。大语言模型生成的模糊测试驱动在模糊测试方面可能比OSS-Fuzz上的驱动程序有更好的结果。在大规模应用中,如何实际选择有效的模糊测试驱动程序是一个主要挑战。

转述:杨鼎

0 阅读:0

互联不一般哥

简介:感谢大家的关注