深度学习模型在漏洞检测中的实证研究

互联不一般哥 2024-04-13 21:36:51

An Empirical Study of Deep Learning Models for Vulnerability Detection

Benjamin Steenhoek1, Md Mahbubur Rahman1, Richard Jiles1, Wei Le1

1Iowa State University, Ames, Iowa, USA

引用

Steenhoek B, Rahman M M, Jiles R, et al. An empirical study of deep learning models for vulnerability detection[C]//2023 IEEE/ACM 45th International Conference on Software Engineering (ICSE). IEEE, 2023: 2237-2248.

论文:https://ieeexplore.ieee.org/abstract/document/10172583

仓库:https://doi.org/10.6084/m9.figshare.20791240

摘要

代码的深度学习(DL)模型在漏洞检测方面取得了巨大进展。本文调查并复现了在两个广泛使用的漏洞检测数据集上的9个最新(SOTA)深度学习模型:Devig和MSR。本文研究了三个领域内的6个研究问题,分别是模型能力、训练数据和模型解释,并实验性地展示了模型不同运行之间的变异性和不同模型输出之间的低一致性。经调查针对特定类型漏洞训练的模型与一次性训练所有漏洞的模型,探索了DL可能认为“难以”处理的程序类型和训练数据大小和训练数据组成与模型性能的关系。最后,本文研究了模型解释,并分析了模型用于做出预测的重要特征。

1 引言

近年来,深度学习漏洞检测工具已经取得了令人期待的成果。最新(SOTA)模型报告了0.9的F1分数,并且表现优于静态分析器。这些结果令人兴奋,因为深度学习可能为软件保障带来变革性的改变。因此,IBM、谷歌和亚马逊等行业公司对此非常感兴趣,并且已经大量投资开发此类工具和数据集。

尽管有希望,深度学习漏洞检测还没有达到计算机视觉和自然语言处理的水平。我们的大部分研究集中在尝试新兴的深度学习模型,并使其适用于像Devign或MSR这样的数据集。然而,我们对模型本身了解甚少,例如,模型能够/不能很好地处理哪些类型的程序,我们是应该为每一种漏洞类型构建模型,还是应该构建一个能处理所有漏洞类型的模型,什么是好的训练数据集,以及模型用什么信息来做出决策。了解这些问题的答案可以帮助我们更好地开发、调试和实践中应用这些模型。但考虑到深度学习的黑箱性质,这些问题非常难以回答。本文并不旨在为这些问题提供一个完整的解决方案,而是朝着这些目标探索。

在本文中,我们调查并复现了一系列最新(SOTA)深度学习漏洞检测模型,并构建研究问题和研究以理解这些模型,目的是为了更好地设计和调试未来的模型提炼经验和指导方针。据我们所知,这是第一篇系统地调查和比较多种SOTA深度学习模型的论文。过去,Chakraborty等人探索了如VulDeePecker、SySeVR和Devign等四种现有模型,并指出用合成数据训练的模型在真实世界测试集上报告的准确率较低,以及这些模型使用了如变量名这样的虚假特征来进行预测。

总结来说,我们的研究贡献包括:

我们对深度学习漏洞检测模型进行了全面的调查。我们提供了一个复现包,包括了11个SOTA深度学习框架的训练模型和数据集,以及各种研究设置。我们设计了6个RQs来理解模型能力、训练数据和模型解释。我们构建了研究并实验性地获得了RQs的结果。我们准备了有趣的例子和数据,以便进一步研究模型的可解释性。

2 模型及其复现的调查

为了收集最新(SOTA)深度学习模型,我们研究了2018至2022年间的论文,并且还使用了微软的CodeXGLUE排行榜以及IBM的缺陷检测D2A排行榜。我们与所有我们能找到的开源模型合作,并成功复现了11个模型。关于模型的完整列表以及我们未能复现某些模型的原因都在我们的数据复制包中给出。

如表1所示,复现的模型涵盖了多种深度学习架构。Devign和ReVeal使用了在属性图上的图神经网络(GNN),这些属性图整合了控制流、数据依赖性和抽象语法树(AST)。ReGVD在令牌上使用了GNN。Code2Vec使用了在AST上的多层感知机(MLP)。VulDeeLocator和SySeVR基于RNN和双向长短期记忆网络(Bi-LSTMs)的序列模型。最近的深度学习检测使用了预训练的变换器,包括CodeBERT、VulBERTa-CNN、VulBERTa-MLP、PLBART和LineVul。

表1:复现的11个模型

对于我们的研究问题(RQs),我们使用了Devign和MSR数据集。我们研究了这11个模型在其原始论文中使用的数据集,如表I中的数据集所示。我们发现Devign数据集在11个模型中有8个进行了评估和调整。它是一个平衡的数据集,由大约相同数量的易受攻击和非易受攻击的示例组成,总共27,318个数据点(每个示例也称为一个数据点)。LineVul使用了MSR数据集,这是一个较新提供的数据集。它是一个不平衡的数据集,由10,900个易受攻击的示例和177,736个非易受攻击的示例组成。这些示例按照它们的源项目和它们的通用弱点枚举条目(CWE)进行标记,这表明了漏洞的类型。我们利用这些数据集的特点来研究我们的一些RQs。

为了使模型之间的比较成为可能,我们改进了模型的实现,以支持Devign和MSR数据集。在进行RQs实验时,我们排除了VulDeeLocator和SeSyVR,因为它们不能轻易地为Devign和MSR数据集进行修改。因此,我们使用剩下的9个模型来研究RQs。

3 研究问题与发现

我们将研究问题组织成三个领域,分别是模型能力、训练数据和模型解释。我们介绍了动机、研究设置和我们的发现。

3.1 深度学习模型的能力

RQ1 模型在漏洞检测结果上是否一致?一个模型的不同运行之间以及不同模型之间的变异性是什么?

动机:众所周知,当使用不同的随机种子时,深度学习模型的性能可能会有所变化。在这个研究问题中,我们旨在测量在漏洞检测中,这种变异性实际上有多大。此外,我们想要发现不同深度学习模型之间以及具有相似架构的模型之间存在多少一致性。我们希望我们的发现能够让开发者和研究人员了解这些工具报告的数字背后可能存在的不确定性。

表2:在Devign数据集上3个随机种子的变异性

研究设置:我们使用3个不同的随机种子在Devign数据集的相同训练/验证/测试划分上训练模型。我们使用这个数据集是因为几乎所有模型都在其上调整了它们的超参数。我们测量了稳定输入的百分比——一个对于所有3个随机种子都有相同二进制标签的输入。然后我们比较了模型之间的稳定输入,以测量它们的一致性。

发现:在表2中,我们报告了整个数据集的稳定输入百分比,在stable-all下,以及测试数据集的稳定输入百分比,在stable-test下。我们还报告了3个种子(在测试数据集上)的F1分数变化,在stdev-test-F1下。

我们的结果显示,平均而言,有34.9%的测试数据(总数据的30.6%)报告了依赖于训练中使用的种子的不同预测。在属性图上工作的GNN模型在变异性排名中位列前2;特别是对于ReVeal,对于50%的测试数据,其输出在运行之间发生了变化。与GNN和变换器模型相比,Code2Vec报告了最小的变异性。有趣的是,我们发现不稳定输入与更多不正确的预测相关——稳定输入在所有种子中的不正确预测总数为19%,而不稳定输入为47%。

尽管许多示例在运行之间报告了不同的预测,我们发现F1测试分数的变化并不大,平均标准偏差为2.9。也就是说,对于大多数模型,我们预计95%的性能测量将在多个随机种子测量时,平均性能上下5.8%的范围内。

RQ2 某些类型的漏洞是否更容易被检测到?我们应该为每一种类型的漏洞构建模型,还是应该构建一个能够检测所有漏洞的模型?

动机:在传统的软件保障技术,如程序分析中,我们使用不同的算法来检测不同的漏洞。某些类型的漏洞,例如无限循环,比其他类型,例如内存泄漏,更难以检测,因为前者需要追踪符号值并对循环进行推理,而后者只需检查是否在分配内存后调用了内存释放。在这个研究问题中,我们有兴趣了解对于深度学习漏洞检测器而言,是否也存在某些类型的漏洞比其他类型更容易检测。考虑到不同类型的漏洞具有不同的语义和根本原因,我们还想获得一些洞察,即我们应该为每一种类型的漏洞构建模型,还是应该为漏洞通用地构建模型(不将它们分成不同类型),就像大多数当前的工作所做的那样。

图1:相同漏洞类型和跨漏洞类型性能。条形图表示相同漏洞类型的性能;圆圈表示每个其他漏洞类型的跨漏洞类型性能。

研究设置:在这里,我们基于漏洞类型研究模型,因此我们使用了MSR数据集。MSR数据集中的示例带有CWE 4的注释。使用这些CWE类型,我们将漏洞分为5个类别,分别是缓冲区溢出、值错误、资源错误、输入验证错误和权限提升,如表V下的漏洞类型所示。我们的标准是:(1)每个组包含具有相似根本原因和语义的漏洞;(2)每个组都有足够大的数据集可以有效地训练模型。列“总数”列出了从MSR数据集中收集的示例数量,包括所有带有CWE注释的易受攻击及其修补的示例。

发现:我们在图1中展示了结果。条形图报告了相同漏洞类型设置下的F1分数,圆形图报告了跨漏洞类型的性能。每种漏洞类型与4个跨漏洞类型测试集相关联,因此每个条形图有四个圆形,除了组合模型有五个圆形,每个圆形代表在组合模型上对某种漏洞类型进行测试。

分析相同漏洞类型的性能,我们发现模型并不总是一致认为哪种类型的漏洞最容易检测,不同类型的漏洞在不同模型中获得了最佳的F1分数。有趣的是,输入验证和资源错误(橙色和红色条形图)经常报告比其他类型更低的性能。相反,缓冲区溢出和值错误(蓝色和紫色条形图)通常报告比其他类型更好的性能。在传统程序分析中,这些类型更难检测,因为它们需要追踪变量值,有时还需要对循环进行推理。

Devign和ReVeal使用了GNN架构处理属性图。它们的条形图显示了相似性。对于其他模型,资源错误(红色条形图)在5种漏洞类型中表现最差。一种可能性是,资源的分配和释放在代码中可能相距很远,变换器模型无法捕获这种长距离依赖。另一种可能性是,这类错误覆盖了各种资源,训练数据集可能不包含每种资源的足够数据,以便模型提取模式。

组合模型(棕色条形图)通常比训练有特定类型漏洞的模型表现差,但对于某些漏洞类型,如输入验证和资源错误,组合模型可以表现得更好。例如,对于CodeBERT,组合模型比其他所有5种模型都达到了更高的F1分数。棕色条形图内的圆圈显示权限提升和资源错误报告了相对较低的准确率,但对于所有漏洞类型,组合模型报告的性能比特定类型模型要好。

分析跨漏洞类型的性能,我们发现,大部分时间内,跨漏洞类型检测报告的性能要低得多,除了LineVul,这暗示不同漏洞类型代表不同的数据分布。LineVul似乎非常擅长处理值错误。当应用其他漏洞训练的模型时,值错误报告的性能高于相同漏洞性能。

RQ3 当前的漏洞检测模型是否难以正确预测具有某些代码特征的程序,如果是,那么这些代码特征是什么?

动机:在这里,我们研究是否可以描述那些不能被很好预测且对深度学习模型来说“难以处理”的程序,以及不同模型是否对这种困难有共识。了解我们无法处理的程序为我们未来的工作提供了一个很好的改进目标。在程序分析中,我们知道某些特征难以处理,例如循环和指针。我们想知道这些特征对深度学习是否也是困难的。

研究设置:在第一步中,我们准备了一个要调查的代码特征列表。我们认为与程序分析工具比较,了解哪些类型的程序难以处理是很有趣的。因此,我们的方法是列出对程序分析重要的代码特征,然后检查它们是否对深度学习工具也有影响。

为了理解某些代码特征是否使得深度学习模型更难预测,我们使用了一个多变量逻辑回归(LR)模型来关联代码特征与函数被正确预测的可能性。如果带有某些代码特征的函数更有可能被正确预测,我们认为它对深度学习来说更容易,反之亦然。给定一个具有特定特征组合的函数,方程1中的Y报告了深度学习模型正确预测它的预测概率。xi是函数中每个代码特征的计数。βis是从数据中学到的系数。每个βi与一个代码特征xi相关联。当βi为负时,值βi * xi降低了正确性的预测概率,因此我们将这些特征视为模型难以处理的。同样,具有正系数的代码特征被视为容易处理的。同时,一个大的βi意味着代码特征xi的计数增加大大增加了正确预测的预测概率,反之亦然。我们在验证集上的预测结果上训练LR模型,然后使用训练好的LR模型来从测试集中找到难易示例。为了量化测试集中一个示例的难易程度,我们使用了LR模型中sigmoid函数的logit输入: 

我们在验证集上的预测结果上训练了LR模型,然后使用训练好的LR模型从测试集中找到难易示例。为了评估这个LR模型的有效性,我们选择了测试集中难度得分排名前10%和后10%的示例。然后我们通过比较我们选定的易处理和难处理数据集上的模型性能来评估我们的LR模型的预测。应该注意的是,最初我们尝试了统计显著性测试和基于相关性的方法来链接代码特征与模型准确性。然后,我们意识到这些方法一次只考虑一个特征。例如,具有3个循环、400个指针和500个宏的函数表现良好,而那些有300个循环、300个指针和100个宏的函数表现不佳。我们不能得出性能差异是由于循环、指针还是宏造成的结论。另一方面,LR模型一次性纳入多个代码特征,并考虑了特征组合的效果。

图2:根据LR模型难度得分选择的评估集上的比较性能,在Devign数据集上平均3个随机种子。“原始”是在原始测试集上的性能。

图3:在Devign数据集的稳定示例上训练的LR模型的系数。

发现:图2显示所有9个模型在易处理数据集上的表现优于难处理数据集。对于所有模型,易处理/难处理性能之间的平均差异为10.3%。对于大多数模型(9个中的7个),原始测试集的性能位于难处理和易处理集合的性能之间。这些结果表明,LR模型和难度得分对于为深度学习模型选择难易示例是有效的。

图3绘制了为每个深度学习模型训练的LR模型中每个代码特征的系数。我们发现,特征call、length和pointers的点为所有模型聚集在一起,表明所有模型都同意这些特征的重要性。有趣的是,所有这些点都位于0附近,这表明这些特征没有很大的影响。另一方面,特征for、goto、if、jump、switch和while在模型间有所变化。这些都是与控制流相关的结构。我们还观察到,对于所有模型,arrays和switch与正系数相关联,而对于大多数模型,if、goto和while落在负范围内。特别是,对于所有模型,if都有负系数。程序中if、goto和while的高数量指示了其高的环形复杂度,这种类型的程序对程序分析也是挑战。特别是,基于属性图的模型如Devign使用控制流信息,所以特征for、goto、jump和while都是负的。

3.2 训练集数据

RQ4 增加数据集大小是否有助于提高模型在漏洞检测中的性能?

动机:高质量的漏洞检测数据难以获得。过去,我们使用自动标注方法,如静态分析和挖掘更改提交。这些方法可能引入错误的标签。我们还使用了手动标签,这些标签的产生速度较慢。这个研究问题帮助我们理解目前可用的数据集是否足够大以训练模型,以及增加数据集大小是否可以显著提高模型性能。

图4:当模型用增加部分的训练数据集进行训练时,在保留的测试集上的F1分数。

研究设置:为了调查这个RQ,我们合并了Devign和MSR数据集,即不平衡数据集。由于Devign中使用的项目与MSR中使用的项目有重叠,我们排除了82个提交ID匹配的重复示例。这样生成了一个总共194,285个示例的数据集。一些已发布的模型最初是在平衡模型上调整的,如Devign,所以我们还通过采用MSR中所有易受攻击的示例,然后随机欠采样等量的非易受攻击示例,构建了一个包含45,363个示例的平衡数据集。对于每个数据集,我们为所有模型保留了10%的数据作为测试集。

发现:我们在图4中总结了我们的结果。图4a和4b显示了类似的趋势。一般而言,当我们增加更多数据时,所有模型的性能都有所提升。然而,改进不是很显著。将100%的数据与10%的数据进行比较,当我们对平衡数据集的所有模型取平均值时,测试集上的F1分数没有差异。对于不平衡数据集,F1分数的值平均提高了0.16。

在图4a中,模型中只有LineVul在我们每次增加10%的数据时显示出了一致的改进。当我们增加训练数据集时,所有其他模型的表现都有波动,这表明增加数据并不总是有益的。例如,对于VulBERTa-MLP,F1值在增加数据集大小的过程中平均下降了0.1。使用100%数据集训练的模型的性能比使用10%数据集训练的模型的性能低0.08。似乎除了数据集大小之外的其他因素在性能方面起了更为重要的作用。在图4b中,ReVeal是随着数据集增加而改善最多的模型。在100%的数据集上,ReVeal正在追赶最佳模型LineVul。然而,Devign,它使用了类似的使用GNN在属性图上的架构,并没有显示出额外数据的好处。

RQ5 训练数据集中的项目组成如何影响漏洞检测模型的性能?

动机:在这个研究问题中,我们旨在进一步了解如何构建一个好的漏洞检测训练数据集。特别地,我们有兴趣知道训练数据集中项目的多样性是否有帮助。我们也有兴趣了解不同的项目是否确实代表不同的分布,以至于当测试数据和训练数据来自相同的项目时,模型能显著表现得更好,而当训练和测试数据来自不同项目时,模型是否能对未见过的项目进行泛化。

研究设置:我们为这项研究设计了两个实验。在第一个实验中,我们准备了一个非多样化的训练数据集和一个多样化的训练数据集,并比较了用这两个数据集训练的模型在同一个测试集上的表现。在MSR数据集中,我们发现Chrome包含76k个示例,是所有310个项目中最大的。我们将其用作非多样化数据集。我们在5折交叉验证设置中进行了这个实验,以消除选择项目时可能存在的潜在偏见。对于每一折,我们从MSR数据集中随机抽取了10k个示例作为测试集。然后,我们排除了Chrome和测试集中使用的项目,并从剩余的项目中随机抽取了总共76k个示例(与Chrome的数量相同)。多样化数据集中项目的平均数量在5折中为50.6。

在第二个实验中,我们准备了一个混合项目设置,测试集和训练集是分开的,没有考虑源项目,训练和测试集中的一些示例可能来源于相同的项目。这是我们大多数深度学习论文评估的设置。我们还构建了一个跨项目设置,测试集的示例必须来源于与训练集代表的项目不同的项目。这种设置帮助我们了解,当使用没有见过测试项目的现成训练好的深度学习漏洞检测模型时,我们是否会有显著的性能下降。

图5:关于训练数据中项目组成的研究。条形图显示平均F1分数,区间显示标准偏差。

发现:第一个实验的结果展示在图5中。我们使用箱线图来总结5折交叉验证的结果。令我们惊讶的是,我们发现对于所有模型来说,多样化的训练集相比于仅由Chrome组成的训练集并没有提供任何好处。事实上,6个模型中有5个在非多样化数据上训练时报告了更高的中位性能,而不是在多样化数据上。

对于第二个实验,图5b显示,对于所有模型来说,混合项目的表现显著优于跨项目(F1分数的平均差异和最大差异分别为0.11和0.32)。这意味着,从一个项目中看到的数据确实可以帮助预测来自同一项目的其他数据。与直接使用现成的已经训练好的模型相比,漏洞检测可以从定制训练的模型中获得极大的好处。对于LineVul,5折在跨项目设置中报告了非常不同的性能,表明给定一个目标测试集,一些项目对训练比其他项目更有用。结果还暗示,模型可能学会从数据集的项目特定属性中检测漏洞,如风格、语言特征或命名约定。我们认为,这激发了进一步研究通用化的漏洞因果检测的动机。

3.2 深度学习内部机制

RQ6 模型使用哪些代码信息进行预测?模型对重要特征的认同度如何?

动机:最近的深度学习漏洞检测器已经达到了高性能,例如,LineVul报告了91%的F1分数。我们想知道这些工具为什么能表现得很好,以及模型是否使用了漏洞的任何语义方面来做决定。例如,为了检测缓冲区溢出,基于语义的程序分析工具会识别依赖语句并推理字符串长度和缓冲区大小。我们还想调查不同模型对什么是重要特征是否有共识。

研究设置:我们调查了一组最新(SOTA)深度学习解释工具,特别是对GNN和变换器架构。我们使用GNNExplainer来分析Devign和ReGVD,以及LIT来分析LineVul、VulBERTa-CNN、VulBERTa-MLP、CodeBert和PLBART,因为在我们调查的所有工具中,这两个工具能够适用于我们的大多数模型。在我们的数据复现包中,我们记录了为什么其余模型无法与GNNExplainer和LIT一起工作,以及我们尝试过的其他可解释性工具的原因。

为了报告相似性,我们首先对测试集中的每个程序计算IAB和J(IA, IB),然后分别取IAB和J(IA, IB)的平均值。我们从以下几组中抽取了示例进行手动检查:1) 示例是易受攻击的,且模型正确地检测到了它(正确);2) 示例是非易受攻击的,但模型预测为易受攻击(假阳性);以及3) 示例是易受攻击的,但模型预测为非易受攻击(假阴性)。

表3:通过IAB(用蓝色报告)和J(IA, IB)(用黑色报告)测量的每两个模型之间重要特征集的相似性。最大和最小值以粗体显示。

发现:在表3中,我们报告了每对模型之间重要特征集的相似性。我们的结果显示,Linevul和ReGVD在所有模型对中具有最大的重叠。在被排名为重要特征的10行中,这两个模型平均共享了6.88行。我们发现有趣的是,尽管模型在个别预测上可能有很大的分歧(见表3),但它们使用的代码信息却有重叠。所有的模型对至少在重要特征中有3行是共同的。Devign,作为唯一基于属性图的GNN模型,与其他模型的重叠较低,与PLBART的重叠最低,平均为3.38行。PLBART使用了与其他变换器模型不同的变换器架构,也报告了与其他变换器模型的重叠较低。我们还使用与表3相同的方法分析了正确预测的示例。我们发现,对于正确预测的示例,重要特征集有更多的重叠,例如,Linevul和ReGVD仍然有最多的共同点,在重要特征集中共享了7.29行。

我们的第二个观察是,变换器模型有时在没有看到根本原因的情况下做出预测。这是因为变换器模型接受固定大小的输入,有些代码,有时包括根本原因,被截断了。有趣的是,这些模型仍然能够准确预测一个函数是否易受攻击,并且F1分数很高。第三,我们检查了所有模型都错过的漏洞,并研究了用于检测这些漏洞的重要特征集。我们发现这些漏洞非常特定于应用。这些漏洞可能被错过是因为对这类漏洞的训练数据不足。

代码清单1:LineVul成功检测到的内存泄漏。由LIT报告的前10行以黄色突出显示。

在代码清单1中,我们展示了一个预测正确但使用的特征不是因果性的示例。示例包含一个内存泄漏漏洞,LineVul将该示例预测为易受攻击。在第14行分配给name的内存从未被释放。第23行的修补程序展示了一个修复。由LIT报告的重要特征集(前10行)以黄色突出显示。我们可以看到它包括我们讨论的“模式”,包括第1行的函数签名,包含ERROR的行,例如第13行和第16行,以及第21行的if语句。我们还看到,对于这个项目,变量name_len很重要,并被多次包含。然而,这10行中没有一行覆盖第14行的内存分配,这对于理解这个漏洞很重要。

转述:葛一飞

0 阅读:0

互联不一般哥

简介:感谢大家的关注