一.论文主旨:
这篇论文介绍了一种名为 TitanFuzz 的新方法,该方法是首个利用大型语言模型(LLM)生成输入程序,对深度学习(DL)库(如 TensorFlow/PyTorch)进行模糊测试。
解决了局限性:在深度学习(DL)库的模糊测试
领域,直接生成满足输入语言(例如Python)语法/语义
和张量计算的DL API输入/形状约束
的深度学习程序具有挑战性。
深度学习API还可能包含复杂的输入条件约束
,难以在没有人工干预的情况下生成符合条件的输入用例。
补充:背景知识
1.关于模糊测试的定义:
模糊测试是⼀种⾃动化的测试技术,它会根据⼀定的规则⾃动或半⾃动地⽣成随机数据,然后将这些产⽣的随机数据输⼊到动态运⾏的被测程序⼊⼝,同时监控被测程序是否有异常情况出现,如系统崩溃、断⾔失败等来发现软件的缺陷。简而言之,就是一种自动生成测试用例去测试软件有没有漏洞的测试方法。
2.我之前阅读过的模糊测试方法都没有用到人工智能大模型。
从模糊测试到智能模糊测试,其技术发展经历⼀段历程。早期的模糊测试 技术主要以⿊盒模糊测试技术为主,它具备简单、直接、粗暴的特点,但效率⼗分低下。随着⼈⼯智能技术的发展2014年后智能模糊测试进⼊⾼速 发展的阶段,结合机器学习的灰盒模糊测试技术成为了主流。灰盒模糊测试通过源代码插桩等技术捕捉程序控制流信息,并通过控制流信息的变化 来选取优异种⼦进⾏变异,因此测试效果要远⾼于⿊盒模糊测试。
3.模糊测试⽤例的变异,是建⽴在⼀定的规则上的随机(不是大猩猩乱点手机屏幕的那种纯随机⾃动化测试),⽤例⽣成有如下两种算法:
二. 论文结论
·TitanFuzz的代码覆盖率分别比传统的模糊测试高30.38%和50.84%。
·TitanFuzz能够检测到65个Bug,其中44个已经被确认为以前未知的Bug。
由此得出结论:
TitanFuzz是一种有潜力的工具,LLM可以直接执行基于生成和基于突变的模糊测试
三.相关知识介绍
1.关于LLM
LLM架构:大型预训练语言模型通常采用Transformer架构,分为编码器和解码器。编码器用于生成输入的编码表示,而解码器用于生成输出令牌。
预训练和下游任务:LLM在数十亿可用文本上进行预训练,然后可以应用于各种自然语言处理(NLP)任务。通过提示工程,LLM可以在不需要专门数据集微调的情况下直接用于特定数据集的下游任务,以提高性能。
LLM类型:根据模型架构和预训练任务,LLM可以分为三种类型:
(1)仅解码器模型(使用解码器预测下一个 token 的概率,可以以自回归方式执行自动补全)
·特点:解码器被用于生成输出令牌,通常根据所有先前的令牌来预测下一个令牌的概率。
·应用:这些模型适用于生成式任务,例如在给定左侧上下文(例如一些起始代码或自然语言描述)的情况下自动完成完整的代码片段。它们可以执行自动补全,生成连贯的文本或代码。
(2)仅编码器模型(通过 Masked Language Model (MLM) 目标训练,其中训练期间有一部分标记的 token 被遮盖,目标是恢复这些被遮盖的 token 的真实值,基于前后文。 )
·特点:只使用编码器组件,旨在提供输入的表示,而不生成输出令牌。
·应用:这些模型通常在预训练过程中使用蒙面语言模型(MLM)目标,其中一定比例的标记被蒙面,任务是根据前后的上下文恢复这些蒙面标记的真实值。它们适用于填充任务,例如插入基于双向上下文的最自然的代码,使代码连贯性更高。
(3)编码器-解码器模型(通常使用 Masked Span Prediction (MSP) 目标训练。MSP 用单个遮盖跨度替换选择的一系列标记,然后要求模型在训练期间恢复整个序列。在推理期间,这些模型可以直接在中间填充代码。训练编码器和解码器组件可能耗时较长,因此最近,研究人员提出了 InCoder [17] 模型,它只使用解码器组件,但可以通过因果语言模型训练目标在中间填充文本/代码。 InCoder 使用与 MSP 相似的训练方法,将处理后的训练数据中的随机序列替换为遮盖跨度标记。用这种方式训练 InCoder,该模型还可以实现填充,并实现 state-of-the-art 结果。 )。
·特点:这些模型通常使用掩码跨度预测(MSP)目标进行训练,其中一系列令牌被替换为掩码跨度令牌,然后模型需要在训练期间恢复整个序列。
·应用:编码器-解码器模型可以用于生成式任务,类似于仅解码器模型,同时也适用于填充任务,因为它们可以使用前后的上下文来填充中间的代码。这些模型在多个领域,包括自然语言处理和代码生成任务中表现出色。这些模型在不同的任务上表现出色,如代码完成、代码合成和自动程序修复。
生成和填充任务:LLM可以执行两种主要类型的代码生成任务,包括生成式任务和填充任务。生成式任务涉及在给定左侧上下文的情况下自动完成完整的代码片段,通常使用仅解码器模型。填充任务旨在插入基于双向上下文的最自然的代码,可以使用不同类型的模型来完成,例如仅编码器、编码器-解码器和InCoder。
突变和程序生成:文本中提到了使用填充模型来执行突变任务,即通过替换输入程序的一小部分来生成更多样化的程序。这有助于生成多样性的代码。
四.过往技术及其局限性:
在以往的研究中,关于模糊深度学习库的工作主要可以分为两个类别:
第一类:API级模糊测试
API级模糊测试侧重于通过为每个目标API生成各种不同的输入,以测试单个库API,从而发现潜在的崩溃或结果不一致的问题。
缺点:
·缺乏多样化的API序列:目前的API级模糊测试方法主要关注对每个深度学习库的单个API进行模糊测试[16,72]。这些技术试图通过简单的变异规则为特定的目标API创建许多不同的输入。然而,这些输入通常由单个代码行构建,最多是一个库API创建的输入,例如,随机初始化具有特定数据类型和形状的张量,因此无法揭示由链式API序列引起的错误。
·无法生成任意代码:某些方法,如FreeFuzz[72],首先通过挖掘开源代码片段来收集目标API的有效参数空间,例如输入张量的类型和形状。在模糊循环期间,FreeFuzz将对这些有效输入进行小的突变以生成新的输入,例如,更改数据类型,如从float32到float16。
然而,这种方法受到跟踪参数空间和预定义突变规则的限制,无法生成任意代码。以上问题表明,现有的API级模糊测试方法在测试深度学习库时存在一些限制,无法有效地揭示由多个API序列调用引起的错误。因此,需要一种新的方法来克服这些问题,以提高深度学习库的测试质量。
第二类:模型级模糊测试(model-level)
模型级模糊测试技术的主要目标是生成各种不同的深度学习模型,然后比较这些模型在不同后端(例如Keras)上的输出,以发现潜在的错误。
缺点:
·缺乏多样化的API序列:模型级模糊测试器只能涵盖一组有限模式的API,无法充分覆盖许多可能导致错误的多样化API序列。举例来说,某些工具如LEMON[71]的层(layer)添加规则无法应用于具有不同输入和输出形状的层,或者Muffin[25]需要手动注释考虑的深度学习API的输入/输出限制,并使用额外的整形(reshaping)操作来确保有效的连接。
·无法生成任意代码:类似于上述问题,模型级模糊测试工具,如Muffin[25],需要手动生成不同的模型,为每个被操作的深度学习API和预定义的代码结构注释规范,以维持模型的有效性。因此,这些先前的深度学习库模糊测试工具无法完全探索使用深度学习库API时存在的庞大搜索空间。
这些问题表明,现有的模型级模糊测试方法在测试深度学习库时也存在一些限制,无法充分覆盖多样性的API序列和无法生成任意代码,从而限制了其测试能力。因此,需要开发新的方法以解决这些问题,以提高深度学习库的测试质量。
本文模型的优化:
LLMFuzz 能够通过使用生成式(Codex)和填充式(InCoder)LLM 生成任意代码,从而实现最先进的 API 级结果。
五.本文所做的贡献
1.本文通过直接使用llm(语言大模型作为生成引擎 ,为模糊DL库打开了一个新的维度 :证明现代大型llm可以直接执行基于生成(generation-based)类似于gpt使用的是单向transformer的解码器的和基于突变(mutation-based)(有点类似于bert使用双向transformer的编码器)的模糊测试,并适用于挑战传统方法的领域(如DL )。
2.technique:
实现了TitanFuzz,一 个全自动的DL库模糊器:它首先使用生成LLM (Codex)来合成高质量的种子输入,然后将填充LLM (InCoder)与进化算法相结合 ,以指导生成更多独特的库API使用和有效 /多样化的DL程序 。
3.完成了实验评估:
对两个最流行的深度学习库:PyTorch和TensorFlow进行了广泛的评估 。
六.实验方法 TitanFuzz
第一大部分:生成种子
1.生成初始种子程序:首先,使用生成式LLM(使用 Codex)产生模糊测试的初始种子程序。
通过为目标API提供逐步提示,生成直接使用目标API的代码片段。
第二大部分:种子演化
2.自动变异程序(使用进化模糊算法,迭代生成新的代码片段)
使用多个变异算子和LLMs(使用 InCoder 填充模型进行代码填充)。来自动变异种子程序,生成新的测试程序。
3.设计适应函数:设计一个适应函数,根据数据依赖深度和唯一库API的数量对种子或变异测试程序进行优先级排序。这有助于“TitanFuzz”能够发现仅在研究复杂API关系时才能发现的Bug。
第三大部分:差分测试
4.差分测试:最后,对生成的测试程序在不同的后端执行差分测试,以检测Bug。
6.1 生成种子
这一方法旨在为生成式语言模型提供明确的上下文,以更准确地生成代码或执行任务,确保生成的代码符合任务要求和目标API的定义。这有助于提高生成式语言模型的性能和可用性。
图5中展示了提示(prompt)和模型输出的示例。这些提示的构建过程如下:
在提示(prompt)中,任务描述被包装在一个文档字符串下面。
·提示中包括了目标库(如TensorFlow)和目标API定义(如tf.nn.conv2d(…))。
·API定义是通过爬虫从官方文档中自动获取的,以确保准确性和一致性。
·提示还设计了一个分步指令,如图中的Task 1, 2, 3 等,以提高模型的性能。
·这些任务按顺序执行,包括(1)导入目标库;(2)生成输入数据;(3)调用目标API。
·构建的提示被用作Codex的初始输入,原始种子程序则通过从Codex中采样自动完成来获得。
6.2 种子演化
演化算法图:
6.2.1 算子定义
不同于我之前看的论文,突变算子直接进行增,删,改,这里的每一个突变算子会产生一个独特的标记,来屏蔽当前种子程序的片段。在之后会使用填充式模型incoder,填充被屏蔽的地方,来达到突变的目的。
四种算子图:
作者定义了四种变异算子,它们是参数(argument)、前缀(prefix)、后缀(suffix)和方法(method)。这些变异操作符的作用如下:
参数(argument):这个变异操作符将选择目标API调用中的参数,并替换它们为掩码标记()。通过这种方式,可以在代码中替换参数,改变API调用的输入数据。
前缀(prefix):前缀变异操作符将在目标API调用之前插入代码片段,并用掩码标记替换一部分目标API调用的前缀。这可以在调用API之前执行其他操作,以改变程序行为。
后缀(suffix):后缀变异操作符与前缀操作符相似,但是它在目标API调用之后插入代码片段,并用掩码标记替换一部分目标API调用的后缀。这可以在API调用后执行其他操作。
方法(method):方法变异操作符替换目标API调用为一个新的API调用,这个新API调用通常是与目标API相似,但具有不同的行为。这可以改变程序中的API调用,从而引入不同的行为。
6.2.2 算子选择
暂时略(算法的理解有问题)
6.2.3 适应度分数(针对的不是算子,是输入程序(模糊)的筛选)
静态分析被用来计算每个测试程序的适应度分数,以帮助选择最有前途的变异程序。适应度计算的目标是根据以下特征为生成的变异程序赋予适应度分数:
·数据流图的深度:静态分析代码片段内变量的数据流,以构建一个数据流图,其中每个边代表两个操作之间的数据依赖关系。数据流图的深度(D)定义为图的任何路径中的最大边数。较深的数据流图表示更复杂的数据流关系
·API调用的数量:计算每个代码片段中存在的唯一库API调用的数量(U)。由于生成的代码片段可能包含重复的代码行,还计算和惩罚使用相同输入(R)多次调用库API的数量。
根据TitanFuzz的适应度计算公式,这个模糊测试工具更倾向于生成包含长链API序列和更多独特API调用的代码片段。这样可以增加覆盖不同API之间的交互,有望引发更有趣的程序行为或错误。这种策略有助于提高测试的全面性和深度。
然而,仅追求长链API序列和更多独特API调用可能导致测试效率下降,因为会涵盖越来越长的API序列,还可能导致重复调用API,增加测试的计算负担。
为解决这个问题,TitanFuzz采取进一步的措施,惩罚重复API调用的序列(R)。这意味着如果同一API在代码片段中多次重复调用,那么这些额外的重复调用会减少代码片段的适应度分数,从而鼓励生成更多多样化的API调用,提高测试效率和效力。这种权衡和惩罚机制有助于保持模糊测试过程的效率,并确保生成的代码片段既具有高适应度,又不过分依赖重复调用API。
6.2.4 Oracle测试
TitanFuzz利用差分测试Oracle在两个独立的后端(CPU和GPU)上运行生成的代码片段,以检测潜在的错误。主要关注以下两种错误类型:
Wrong-Computation(错误计算):这种错误类型涉及比较两个执行后端上所有中间变量的值,并在值显著不同时找到错误计算。由于某些计算的非确定性特性可能导致CPU和GPU上的结果略有不同,为了区分真正的错误和非关键差异,TitanFuzz使用容差阈值来检查值是否显著不同。计算值的差异可能表明库API的不同后端实现或不同API之间的交互存在潜在的语义错误。
Crash(崩溃):在程序执行期间,TitanFuzz还会检测到意外的崩溃,例如分段错误、中止、INTERNAL_ASSERT_FAILED错误等。这些崩溃表明无法检查或处理无效输入或极端情况,可能导致安全风险。
通过在不同后端上运行生成的代码片段,并使用差分测试Oracle来检测上述错误类型,TitanFuzz能够发现潜在的问题和漏洞,从而提高深度学习库的质量和稳定性。
Oracle测试在更全面地探索深度学习库API潜在bug方面起着关键作用。它通过在两个不同的后端上运行生成的代码片段来检测bug,分别是在CPU和GPU上执行代码,并记录所有中间变量,具体实现原理如下: 1. **错误计算的检测**:Oracle测试通过比较两个执行后端中所有中间变量的数值来找出错误计算。如果数值显著不同,就会发现错误计算。由于某些计算的非确定性性质导致它们在CPU或GPU上会产生略微不同的结果,因此Oracle测试引入了容差阈值,用于检查数值之间是否存在显著差异。计算值之间的差异可能表明后端实现中某个库API的语义bug或者不同API之间的交互中存在问题。 2. **崩溃的检测**:在程序执行过程中,Oracle测试还会检测意外崩溃,例如段错误、中止、内部断言失败错误等。这些崩溃表明未能检查或处理无效输入或边缘情况,可能会导致安全风险。 总之,Oracle测试通过在不同的执行环境中运行代码片段,并比较输出结果,以检测可能存在的错误计算和程序崩溃,这样能更全面地探索深度学习库API潜在的bug,提升软件质量和安全性。
6.3 实验评价
RQ1: Comparison with Prior Work
跟现有的优秀的同样针对DL libraries的模糊器相比,TitanFuzz的覆盖率大大提高
而且可以发现,单独用TitanFuzz生成种子,用其他的模糊器比如:DeepREL(这个实验把DeepREL作为一种基准),也能大大增加覆盖率和效率。这说明就算单独用大模型来生成种子的思路也非常好。
RQ2: Evaluation of Key Components
突变操作符的消融实验,表明每一个突变操作符都是有积极作用的
表3展示了当从TitanFuzz中移除每种类型的突变运算符时的结果。"列有效"表示仅考虑唯一的有效程序,而"列都"包括了运行时错误的程序。观察到,当使用完整的突变运算符集时,获得了最高数量的唯一程序和代码覆盖率。这表明每个突变运算符都对生成更多独特的程序和覆盖额外的代码行具有积极作用。
RQ3: Detected Bugs
表明TitanFuzz能发现更多的未知错误:TitanFuzz共检测到65个错误,其中55个已经确认,其中包括20个崩溃错误和35个错误计算错误。这些检测到的错误为深度学习库的质量提供了宝贵的反馈,有助于改进和修复潜在问题,从而提高库的稳定性和可靠性。 TitanFuzz的能力在检测深度学习库中的错误方面表现出色。