VulEval:迈向软件漏洞检测的代码库级评估
VulEval: Towards Repository-Level Evaluation of Software Vulnerability Detection
原文链接:https://arxiv.org/pdf/2404.15596

摘要
基于深度学习(DL)的方法已被证明在软件漏洞检测中行之有效,并且具备大幅提升漏洞检测效率的潜力。当前的方法主要集中于单个函数的漏洞检测(即,函数内漏洞),而忽视了实践中更为复杂的跨函数漏洞检测场景。例如,开发人员通常通过程序分析来检测跨越代码库中多个函数的漏洞。此外,广泛使用的基准数据集通常仅包含函数内漏洞,导致对跨函数漏洞检测能力的评估尚未被充分探索。
为缓解这些问题,我们提出了一种代码库级评估系统,名为 VulEval,旨在同时评估函数内和跨函数漏洞的检测性能。具体而言,VulEval 包含三个相互关联的评估任务:(1) 函数级漏洞检测,目标是根据给定的代码片段检测函数内漏洞;(2) 漏洞相关依赖预测,目标是从调用图中检索最相关的依赖关系,为开发人员提供关于漏洞的解释;(3) 代码库级漏洞检测,目标是结合第二项任务中识别出的依赖关系,检测跨函数漏洞。VulEval 还包含一个大规模数据集,总计包含 4,196 个 CVE 条目、232,239 个函数以及相应的 4,699 个 C/C++ 编程语言的代码库级源代码。通过对数据进行随机划分和按时间划分后分别评估 19 种漏洞检测方法,我们观察到代码库级漏洞检测框架的表现优于对应的函数级方法,F1 分数平均提升 1.51%,MCC(马修斯相关系数)平均提升 2.63%。这表明,结合漏洞相关依赖关系有助于提升漏洞检测效果。我们的实验结果还表明,基于程序分析和提示(prompt)的方法在按时间划分数据时性能未受影响。此外,在研究的七种依赖检索方法中,我们发现基于词法的方法在识别漏洞相关依赖关系方面表现优于基于语义的方法。我们的分析突出了当前软件漏洞检测领域的进展及未来发展方向。
1 引言
软件漏洞主要由不安全的代码引发,可被利用来攻击软件系统,从而导致系统崩溃、数据泄露甚至关键基础设施损坏等安全问题。在过去的十年中,软件漏洞的数量增加了五倍以上,从 2013 年的 5,697 个增长至 2023 年的 29,065 个 [48]。漏洞数量和类型的持续增长已导致经济损失不断增加 [50]。例如,Clop 勒索软件成功从多个组织勒索了超过 5 亿美元 [37]。因此,开发有效的软件漏洞检测技术显得尤为重要。
现有的漏洞检测方法可以分为四类:基于程序分析的方法、基于监督学习的方法、基于微调的方法以及基于提示(prompt)的方法。传统的基于程序分析的漏洞检测技术,如 INFER [15] 和 CheckMarx [25],依赖预定义规则来识别漏洞。这些方法因漏洞类型和库的多样性而显得劳动密集且效率低下。基于深度学习(DL)的方法已成为一种有效的解决方案,通过减少对领域专业知识的依赖并增强检测各种软件漏洞的能力,展现了显著的成功 [9]。早期基于 DL 的方法采用监督学习方法,利用卷积神经网络(CNNs)[60, 61]、循环神经网络(RNNs)[34, 47] 和图神经网络(GNNs)[6, 65] 来学习漏洞表示。
然而,这些基于监督学习的方法的有效性受到漏洞数据稀缺性的限制 [41]。预训练模型的出现,如 CodeBERT [17] 和 UniXcoder [21],它们在大规模开源代码库上进行了训练,显著推动了这一领域的发展。这些方法具备广泛的通用编程知识,可以通过微调漏洞数据集极大地提升漏洞检测性能,称为基于微调的方法。如今,基于提示的技术利用大型语言模型(LLMs),如 LLaMA [51] 和 CodeLlama [46],进行漏洞检测,标志着该领域逐渐倾向于无监督方法的趋势。
尽管通过微调和提示技术在漏洞检测方面取得了显著进展,但评估这些方法的有效性仍然具有挑战性。具体而言,当前的评估场景与现实世界的漏洞检测场景之间存在差距,体现在以下两个方面:
(1) **缺乏检测跨函数漏洞的方法。**尽管各种漏洞检测方法已被证明有效,但当前的评估框架主要集中于单个函数或文件的粒度,未能充分考虑跨越多个文件或整个代码库的复杂漏洞。这种狭隘的关注点无法充分反映现实世界漏洞检测环境中的复杂性,其中开发人员通常使用程序分析技术来检测跨越代码库中多个文件的漏洞。例如,图 1 展示了一个 CWE-20(输入验证不当)的跨函数漏洞 [2]。图 1(a)、(b) 和 © 分别展示了函数级代码片段及其相关的被调用函数和调用函数。具体来说,函数 dd_close 假设 dd 指针非空且未进行验证,并在图 1(a) 的第 5 行调用 dd_unlock 并访问成员变量,这可能导致漏洞(图 1© 的第 6-7 行)。同样,dd_delete 在未确保 dd 指针有效的情况下执行与锁定状态相关的操作(图 1(b) 的第 7-8 行)。这种跨越多个函数的跨函数漏洞难以被现有方法识别。

(2) **缺乏全面的漏洞检测评估系统。**现有工作通常在随机划分的函数/文件级数据集上进行评估,而未单独考虑不同场景和时效性。先前的数据集 [16, 35] 仅使用漏洞补丁构建数据集,忽略了代码库中相应的依赖关系(如被调用函数和调用函数)。此外,由于调用图中依赖关系的数量庞大,有必要为开发人员检索与漏洞相关的依赖关系。此外,鉴于每年发现的大量漏洞,利用历史漏洞数据来检测未来漏洞已成为一项关键需求。然而,现有的随机划分设置可能导致数据泄露风险和性能虚高的问题,最终损害漏洞检测方法的可靠性,并反映了现实世界软件开发环境中的挑战。
为缓解上述问题,本文提出了一种整体评估系统,名为 VulEval,旨在同时评估跨函数和函数内漏洞。具体而言,我们通过三个相互关联的任务构建评估系统:
(1) 函数级漏洞检测,任务是预测给定代码片段是否存在漏洞,目标是检测函数内漏洞;
(2) 漏洞相关依赖预测,任务是从调用图中检索与漏洞相关的依赖关系,从而为开发人员提供关于漏洞的解释;
(3) 代码库级漏洞检测,目标是检测跨函数漏洞。
为了探索现有漏洞检测方法在第三项任务中的表现,我们结合第二项任务中识别出的依赖关系,提出了一个代码库级漏洞检测框架。
我们收集了每个漏洞补丁的大规模代码库级源代码,以提供代码库信息。该数据集包含 4,196 个 CVE 条目、232,239 个函数以及相应的 4,699 个 C/C++ 编程语言的代码库级源代码。我们还从代码库中提取了 347,533 个函数依赖关系(即被调用函数和调用函数)和 9,538 个与漏洞相关的依赖关系,用于检测跨函数漏洞。
基于所提出的评估系统,我们对四种类型的漏洞检测方法(即 19 个基线)在 VulEval 上进行了函数级和代码库级漏洞检测的实证研究。我们还评估了三种类型的检索方法(即 7 个基线)在漏洞相关依赖预测中的表现。在评估过程中,我们分析了两种设置下的有效性(即随机划分和按时间划分)。此外,我们强调了当前的进展并指出了未来方向。
主要发现
基于广泛的实验,我们的研究揭示了以下几个关键发现:
(1) 在代码库级漏洞检测中结合与漏洞相关的上下文,相较函数级漏洞检测提升了性能。
(2) 在按时间划分的设置下,基于监督学习和微调的方法表现出性能下降;而基于程序分析和提示的方法性能未受影响。
(3) 在识别与漏洞相关的依赖关系方面,基于词法的方法优于基于语义的方法。开发更有效的检索技术以检索与漏洞相关的依赖关系至关重要。
贡献
总之,本文的主要贡献总结如下:
(1) 据我们所知,我们是第一个提出一种整体评估系统以同时评估跨函数和函数内漏洞的研究者。
(2) 我们收集了大规模代码库级源代码,并提取了相应的依赖关系以提供代码库级信息。我们提取了 347,533 个依赖关系和 9,538 个与漏洞相关的依赖关系,用于检测跨函数漏洞。
(3) 我们在两种设置下对 19 种漏洞检测方法和 7 种依赖检索方法进行了广泛的评估。我们的分析突出了当前软件漏洞检测领域的进展及未来方向。
2 背景
在本节中,我们将介绍现有的漏洞检测方法,包括基于程序分析、基于监督学习、基于微调和基于提示的方法,如图 2 所示。

2.1 基于程序分析的方法
许多基于程序分析的方法已被提出并在业界广泛使用,例如 CheckMarx [25]、FlawFinder [58]、PCA [29] 和 RATs [3]。这些方法利用专家设计的预定义规则或模式来识别特定类型的漏洞,例如基于栈的缓冲区溢出、基于堆的缓冲区溢出等。
图 2(a) 展示了一个来自 Splint [14] 的示例。它表示一个形式化规范,用于表达 strcpy 函数的预期行为,并同时提供了一条规则以检测潜在的缓冲区溢出漏洞。具体而言,它检查调用是否符合条件 maxSet(s1) >= maxRead(s2)。如果 Splint 发现任何违反这些条件的调用,它将向开发人员发出可能存在的漏洞警告。这些方法的优势在于它们不依赖大规模的漏洞数据集。此外,它们通过报告触发漏洞的路径来解释检测到的漏洞 [9]。该路径由一系列代码片段组成,从而有助于开发人员的验证过程。然而,设计明确的漏洞规则或模式既耗时又费力 [30, 31],使其难以覆盖所有漏洞。
2.2 基于监督学习的方法
近年来,许多基于监督学习的方法被提出,利用表示学习技术捕捉漏洞模式。这主要包括基于序列的方法 [34, 35] 和基于图的方法 [6, 33, 65]。图 2(b) 展示了这些方法的流程。基于序列的方法通常以源代码作为输入,并学习相应的表示,以确定给定的代码片段是否存在漏洞。例如,SySeVR [34] 提取代码片段(code gadget),然后使用双向长短期记忆网络(LSTM)进行漏洞检测。VulCNN [61] 将源代码转换为图像并使用 CNNs 检测漏洞。
最近的研究表明,基于图的方法因其卓越的可解释性和有效性而逐渐受到关注。与基于序列的方法相比,这些方法从源代码中提取结构化表示,包括抽象语法树(AST)、控制流图(CFG)、数据流图(DFG)和代码属性图(CPG)[55]。随后,利用图神经网络(GNNs)学习图表示以进行漏洞检测。与基于程序分析的方法相比,这些方法可以自动捕获漏洞模式,从而减少人力和时间成本。然而,这些方法的有效性高度依赖于大规模高质量的数据集进行训练。
2.3 基于微调的方法
尽管基于监督学习的方法在漏洞检测中表现出有效性,但 Croft 等人 [12] 指出,现有的漏洞数据集往往缺乏质量和准确性,难以应用于现实场景 [57]。
图 2© 展示了基于微调方法的流程 [18, 22, 64]。这些方法首先在大量代码和文本数据上进行预训练,然后对预训练模型进行微调以适应特定任务。例如,CodeBERT [17] 使用 Transformer 架构,利用编码器进行训练。类似地,CodeT5 [56] 和 UniXcoder [21] 专为代码相关任务设计,提供编码器和解码器。通过利用预训练模型中封装的知识,这些方法在漏洞检测方面表现出色。EPVD [63] 提出了一种执行路径选择算法,并利用预训练模型学习路径表示。PILOT [57] 提出了一种正样本和未标记样本框架,并使用预训练模型构建分类器。然而,这些方法受限于输入代码的长度,并且在可解释性方面存在不足。
2.4 基于提示的方法
近年来,大型语言模型(LLMs)因其广泛的泛化能力和推理能力,在软件工程(SE)领域表现出卓越性能 [23]。其中最突出的是 OpenAI 开发的一系列生成式预训练 Transformer 模型,包括 ChatGPT [7] 和 GPT-4 [40],以及 Meta 推出的 LLaMA 系列模型,包括 LLaMA [51] 和 LLaMA2 [52]。图 2(d) 展示了基于提示方法的流程 [7, 19]。该方法以源代码作为输入,随后构建专门用于漏洞检测的提示,并将其输入到 LLMs 中。接着,LLMs 生成响应,以检测源代码是否存在漏洞。然而,这些 LLMs 在软件漏洞检测中面临显著挑战 [19],主要源于两个方面。首先,代码片段通常缺乏足够的上下文信息,无法有效检测漏洞。其次,LLMs 缺乏漏洞检测所需的特定领域知识,这显著影响了其性能。
3 VulEval 系统
在本节中,我们将介绍 VulEval 的评估系统。它主要包括两部分:数据收集和评估任务。
3.1 数据收集
3.1.1 数据来源
遵循先前的研究 [54],用于构建 VulEval 的原始数据包含来自 Mend [59] 的大量 CVE 条目。该数据集总计包括 4,196 个 CVE 条目、4,699 个漏洞补丁以及 C/C++ 编程语言中的 164 种漏洞类型。
3.1.2 代码库代码收集
为了评估跨函数漏洞,我们通过三个步骤进一步收集了代码库源代码:
(1) 我们从 GitHub、Chrome 和 Android 中选择可以从其检索完整源代码和提交日志的代码库。
(2) 对于每个漏洞补丁,我们收集与漏洞补丁提交时间相对应的代码库级源代码。
(3) 对于漏洞补丁中的每个文件,我们使用 Tree-sitter [1] 将其切分为函数级代码片段,其中每个函数级代码片段都分别包含相应的代码库级源代码。

如表 1 所示,我们收集了 4,699 个代码库级源代码用于漏洞检测。在代码库级漏洞检测中,我们还利用目标函数的函数级标签作为代码库级标签(即,“1”表示存在漏洞,“0”表示无漏洞)。目标函数及其对应的依赖关系被作为一个整体样本,用作代码库级样本的输入。
3.1.3 上下文依赖提取
VulEval 的主要贡献之一是考虑了目标代码片段的上下文依赖,这指的是对漏洞检测至关重要的外部代码函数。
我们通过两步程序分析提取代码片段的上下文依赖:
(1) 在提取过程之前,我们首先为每个漏洞补丁构建代码库数据库,其中包括带有不同头文件(即 .h 文件)和源代码文件(即 .c 和 .cpp 文件)的相应代码库源代码。
(2) 然后,我们选择漏洞补丁中发生更改的代码文件,并使用静态程序分析工具 [42] 提取依赖元素。我们将这些依赖分类为“Callee”(被调用函数)和“Caller”(调用函数)。具体而言,“Callee”表示由漏洞补丁调用或执行的用户定义函数。“Caller”表示代码库源代码中负责调用漏洞补丁中函数的用户定义函数。

如表 1 所示,我们在代码库级源代码中提取了 347,533 个依赖关系。我们还标记了 9,538 个与漏洞相关的依赖关系(即标记为“Vul-Dependency”),它们直接参与了漏洞补丁的代码更改。所有其他依赖关系都被认为与漏洞无关。
3.2 评估任务
VulEval 包括三个评估任务:函数级漏洞检测、漏洞相关依赖预测和代码库级漏洞检测,具体如下:
3.2.1 函数级漏洞检测(Detector)

此任务旨在预测函数是否包含漏洞。如图 3 所示,函数漏洞检测仅以目标预测函数的源代码作为输入,而不包含任何超出函数本身的跨程序信息。此任务的目标是学习一个检测器 f,可以表述如下:
f : X ↦→ Y, Y = {0, 1}
其中 X 表示函数级代码片段的输入,Y 表示标签,对于存在漏洞的代码片段设置为 1,否则为 0。
3.2.2 漏洞相关依赖预测(Retriever)
此任务旨在为开发人员提供关于漏洞的解释。表 1 显示,数据集中有 347,533 个依赖关系,但只有 9,538 个依赖关系与漏洞相关。因此,有必要从代码库源代码中的大量依赖关系中检索与漏洞相关的依赖关系。如图 3© 所示,依赖预测的过程通常涉及从输入函数 X 中提取的“Callee”(被调用函数)和“Caller”(调用函数)依赖关系,然后计算输入代码片段与每个候选依赖之间的漏洞相关性。识别漏洞相关依赖的一般检索函数 g 可以表述如下:

其中 Calleei 和 Caller j 分别表示第 i 个和第 j 个候选依赖关系,m 和 n 分别表示“Callee”和“Caller”候选依赖的数量。k 表示在此任务中需要检索的最相关的前 k 个依赖关系。
3.2.3 代码库级漏洞检测
代码库级漏洞检测是我们提出的任务,它整合了在第二项任务中识别出的依赖关系以进行漏洞检测,如图 3(d) 所示。它首先使用“Retriever”检索给定代码片段的相关依赖关系。然后,将识别出的依赖关系(即由“Retriever”检索到的)与目标函数连接作为输入。接着,它使用“Detector”来确定输入是否存在漏洞。代码库级漏洞检测 h 的定义可以表示如下:

其中 CalleeX 和 Caller X 分别表示从代码片段 X 中检索出的“Callee”和“Caller”依赖关系。
4 实验设置
4.1 研究问题
我们的实验旨在回答以下研究问题:
- RQ1:基于程序分析、监督学习、微调和提示的方法在函数级漏洞检测中的表现如何?
- RQ2:检索方法在识别与漏洞相关的依赖关系时的表现如何?
- RQ3:这些方法在代码库级漏洞检测中的表现如何?
- RQ4:这些漏洞检测方法针对每种 CWE 类型的表现如何?
4.2 实验方法
4.2.1 漏洞检测方法的比较
为了评估函数级和代码库级场景中漏洞检测的有效性,我们的基准测试比较了四种类型的漏洞检测方法:
(1) 基于程序分析的方法:遵循先前的研究 [5, 61],我们选择了四种流行的基于程序分析的漏洞检测器,即 Cppcheck [11]、Flawfinder [58]、RATS [3] 和 Semgrep [44]。这些方法利用预定义规则和模式来识别源代码中潜在的不当操作。
(2) 基于监督学习的方法:我们使用 Devign [65] 和 Reveal [6] 作为代表性监督基线,它们被广泛用作近期研究中的基线 [4, 33, 57]。这些方法从源代码构建图,然后利用门控图神经网络 [32] 提取特征进行漏洞检测。
(3) 基于微调的方法:基于微调的方法包括三种通用预训练模型和四种专门用于漏洞检测的最新方法。我们选择了三种广泛应用于代码相关任务的通用预训练模型,即 CodeBERT [17]、CodeT5 [56] 和 UniXcoder [21],并进一步对这些模型进行微调以进行漏洞检测。此外,我们还选择了四种专为漏洞检测设计的最新模型,包括 PILOT [57]、EPVD [63]、LineVul [18] 和 PDBERT [36]。
(4) 基于提示的方法:我们选择了两种开源 LLMs:LLaMA [51] 和 CodeLlama [46],分别因其在文本和代码生成方面的卓越能力。此外,我们还纳入了两种闭源 LLMs:ChatGPT(即 GPT-3.5-turbo)和 GPT-3.5-instruct,它们由 OpenAI 开发,具有 1750 亿参数。对于这些 LLMs,我们利用第 2 节 (d) 中描述的过程评估其在漏洞检测中的有效性。
4.2.2 依赖预测方法的比较
我们首先从调用图中提取所有函数作为依赖候选。然后,采用三种类型和七种基线方法进行与漏洞相关的依赖预测任务:
(1) 随机方法:该方法随机检索代码片段,作为评估其他预测方法的基础基线。为减轻采样偏差,我们重复此随机过程 100 次并报告平均结果。
(2) 基于词法的方法:我们使用两种主要指标作为基线评估漏洞依赖的相关性:Jaccard 相似性和编辑相似性 [49]。此外,我们使用 BM25 [45] 和 BM25+ [53] 作为基于词法的加权函数,根据其与特定代码片段的相关性对依赖关系进行排序。
(3) 基于语义的方法:我们利用预训练模型作为主干,特别是 CodeBERT [17] 和 UniXcoder [21],以获取特征嵌入,然后使用余弦相似性 [62] 测量代码与依赖片段之间的语义相关性。
4.3 评估指标
4.3.1 漏洞检测任务的指标
我们使用以下四个广泛使用的性能指标进行漏洞检测:
- 精确率 (Precision):计算为真阳性 (TP) 与真阳性和假阳性 (FP) 之和的比率,表示为 Precision = TP / (TP + FP)。它表示在所有检索到的漏洞中正确识别出的漏洞比例。
- 召回率 (Recall):计算为真阳性 (TP) 与真阳性和假阴性 (FN) 之和的比率,表示为 Recall = TP / (TP + FN)。它表示基线检测到的漏洞占所有漏洞的比例。
- F1 分数 (F1 Score):F1 分数定义为精确率和召回率的调和平均值,计算公式为 F1 = 2 × (Precision × Recall) / (Precision + Recall)。它是精确率和召回率的综合度量,提供了两者之间平衡的洞察。
- 马修斯相关系数 (MCC):MCC 是一种二分类度量,特别适用于不平衡数据集,计算公式为

,其中 TN 表示真阴性。
4.3.2 依赖预测任务的指标
我们提出了以下两个指标用于识别依赖关系:
- Precision@K (Pre@K):这是正确预测的依赖关系在前 K 个预测依赖关系中的比例,计算公式为 Pre@K = MATCHk / k,其中 MATCHk 表示前 K 个预测依赖关系中正确预测的依赖数量。
- Recall@K (Rec@K):这是正确预测的依赖关系在真实依赖关系中的比例,计算公式为 Rec@K = MATCHk / GT,其中 GT 表示真实漏洞相关依赖关系的总数。
4.4 数据划分
本文在以下两种设置下进行实验:
(1) 随机划分:遵循先前的研究 [6, 65],我们将数据集随机划分为不相交的训练集、验证集和测试集,比例为 8:1:1。
(2) 时间划分:为了降低数据泄露风险并有效评估方法识别新兴漏洞的能力,我们基于漏洞补丁的“提交日期”采用时间划分设置。我们将数据集按 8:1:1 的比例划分为训练集、验证集和测试集。具体而言,2018 年 3 月 21 日之前的补丁用于训练,2022 年 7 月 21 日之前的补丁构成验证集,此后日期的补丁用于测试集。
4.5 实现细节
对于基于程序分析、监督学习和微调的方法,我们直接使用已公开的复现包和超参数。对于基于提示的方法,我们从 HuggingFace Hub [24] 下载了 LLaMA(即 7B 和 13B)和 CodeLlama(即 7B 和 13B),并通过 vLLM [26] 框架本地部署。对于 ChatGPT(“gpt-3.5-turbo-0301”)和 GPT-3.5-instruct(“gpt-3.5-turbo-instruct”),我们使用 OpenAI 提供的公共 API 和初始参数设置。所有评估均在配备四块 NVIDIA A100-SXM4-40GB 显卡的服务器上进行。
5 实验结果
5.1 RQ1:函数级漏洞检测的有效性
5.1.1 随机划分设置中的有效性
为回答 RQ1,我们比较了四种类型的漏洞检测方法,包括基于程序分析、监督学习、微调和提示的方法。结果如表 2 的中间列所示。

**在随机划分中,基于微调的方法表现优于其他方法。**具体而言,这些方法在随机设置下的平均精确率为 51.80%,F1 分数为 38.97%,MCC 为 39.03%。我们还观察到,基于微调的方法在召回率方面表现不足,平均召回率为 32.13%,而基于提示的方法在召回率上的平均得分为 55.80%。在对四种指标的 Top-3 性能进行更广泛的评估(共 12 个实例)时,这些方法在 12 个案例中有 9 个表现出优势,分别在精确率、F1 分数和 MCC 上达到最高分,分别为 63.64%、42.47% 和 41.80%。
基于程序分析和监督学习的方法在所有指标上始终表现较差。基于程序分析的方法通常仅针对特定类型的漏洞,因此在随机划分中的结果普遍不佳。
5.1.2 时间划分设置中的有效性
我们还在时间划分设置中评估了所有基线方法,以全面验证它们在无数据泄露情况下的实际场景中的有效性。结果如表 2 的右列所示。

监督学习和基于微调的方法在时间划分中的性能下降
分析表 2 中的结果,我们观察到基于微调的方法在四项指标上均出现显著下降,精确率平均下降 36.20%,召回率下降 15.46%,F1 分数下降 32.11%,MCC 下降 33.00%。类似地,基于监督学习的方法在四项指标上的性能也分别下降了 13.85%、13.84%、11.03% 和 13.53%。这可以归因于它们严重依赖从历史数据中提取语义,而不是直接捕获漏洞模式。然而,大多数漏洞是在引入后很久才被发现的。因此,我们可以得出结论,这些方法在现实场景中难以识别新出现的漏洞。
基于程序分析和提示的方法在时间划分设置中未受影响
我们的实验结果表明,基于程序分析的方法由于预定义规则的表现优越,在时间划分设置中其平均 F1 分数和 MCC 分别从随机划分中的 7.85% 和 5.93% 提高到 10.61% 和 8.69%。此外,ChatGPT 在 F1 分数和 MCC 指标上分别达到了 14.22% 和 10.13% 的接近最佳表现。值得注意的是,ChatGPT 仅使用截至 2021 年 9 月的数据进行训练,从而避免了数据泄露问题。ChatGPT 的表现可以归因于其庞大的训练语料库中包含的一般知识,使其能够在不同的数据分布中保持一致的性能。
RQ1 总结
实验结果表明,基于微调的方法在随机划分设置中表现优异。我们还观察到,在时间划分设置中,基于监督学习和微调的基线方法性能下降。此外,基于程序分析和提示的方法在时间划分设置中未受影响,从而在现实场景中保持有效性。
5.2 RQ2:漏洞相关依赖预测的有效性
为回答 RQ2,我们在随机划分和时间划分设置下评估了三种类型方法的性能。表 3 展示了 Top-1、Top-3 和 Top-5 的 Pre@k 和 Rec@k 结果。

基于词法的方法在识别依赖关系中表现优越
实验结果表明,基于词法和语义的技术均提升了性能,在识别依赖关系时分别平均提高了 Pre@1 的 10.21% 和 Rec@1 的 5.74%。值得注意的是,基于词法的检索技术带来了最大的改进,在 Pre@k 和 Rec@k(k = 1, 3, 5)上分别实现了 3.44% ∼ 18.83% 和 3.45% ∼ 18.91% 的一致性提升。当 k = 1 时,性能从与检索技术相关的知识中受益更多。例如,所有检索方法相较于随机方法在 Pre@1 上平均提高了 16.27%,在 Pre@3 上提高了 3.72%,在 Pre@5 上提高了 2.06%。此外,基于语义的检索方法表现中等。这可能归因于预训练模型更关注通用语义而非特定领域的漏洞知识,表明在检索方法中融入漏洞特定特征是有益的。
Jaccard 相似性和编辑相似性分别在随机划分和时间划分设置中表现最佳
如表 3 所示,Jaccard 相似性在随机划分设置中是最有效的方法,在 6 种情况中有 4 种表现出色。它在 Pre@1 和 Rec@1 上分别达到了最佳性能 69.70% 和 37.34%。而编辑相似性在时间划分设置中表现最佳,在 Pre@3 和 Rec@3 上分别比 Jaccard 相似性高出 3.85% 和 3.53%。这一发现表明,在代码片段和漏洞相关依赖之间检索共同标记对于识别依赖关系是有效的。
RQ2 总结
我们的实证分析表明,基于词法的方法在识别依赖关系方面表现更优。具体而言,Jaccard 相似性和编辑相似性分别在随机划分和时间划分设置中取得了最佳性能。
5.3 RQ3:代码库级漏洞检测的有效性
本研究问题旨在探讨整合与漏洞相关的依赖关系是否能够提升现有漏洞检测方法的性能。我们采用两种策略评估基线性能:“Upper”和“Prediction”。“Upper”指将与漏洞相关的依赖作为输入用于漏洞检测;“Prediction”表示在 RQ2 中识别出的最有效的检索方法(即随机划分设置中的 Jaccard 相似性和时间划分设置中的编辑相似性)。对于代码库级漏洞检测,由于输入长度受限,我们仅评估基于微调和提示的方法。实验结果如表 4 所示。

整合与漏洞相关的依赖上下文提升了漏洞检测性能
我们观察到,使用“Upper”策略的代码库级方法总体上优于前述的函数级方法。具体而言,在基于微调的方法中应用“Upper”策略时,VulEval 的六个基线中有五个表现出性能提升。除 PILOT 外,这些代码库级方法相较于对应的基线平均提升了 7.43% 的精确率、3.38% 的召回率、4.91% 的 F1 分数和 5.24% 的 MCC。这表明,整合与漏洞相关的依赖提供了额外的上下文信息,使模型能够更全面地理解代码库。PILOT 的性能下降可能归因于其弱监督学习,这可能是数据集中未标记样本过多的结果。
更大的模型从代码库级漏洞相关知识中获益更多
我们的实验发现表明,LLaMA 和 CodeLlama 从代码库级信息中获得的收益有限,这可能是由于这些模型在捕获漏洞模式方面的能力存在局限性。相比之下,ChatGPT 在结合代码库级依赖时,在四项评估指标上均表现出性能提升,分别提高了 11.60%、24.43%、14.48% 和 26.35%。这些结果表明,具有更大基础架构的模型在处理大量文本输入时具备更强的理解能力。
亟需探索更有效的依赖识别检索方法
尽管利用了 RQ2 中识别出的最有效的检索方法来识别依赖关系,但将其与现有的代码库级漏洞检测技术结合并未显著提升性能。例如,在随机设置下,ChatGPT 使用编辑相似性在四项指标上分别提升了 4.07%、10.63%、5.24% 和 8.43%。然而,在时间划分设置下使用编辑相似性并不有效。这些观察结果强调了改进检索策略以更好地捕获和利用漏洞相关依赖的必要性。
RQ3 总结
实验结果表明,整合与漏洞相关的上下文能够提升漏洞检测的性能。值得注意的是,较大的模型尤其从代码库级漏洞知识的整合中获得了改进。此外,开发更有效的检索技术以识别漏洞相关依赖变得至关重要。
5.4 RQ4:每种 CWE 类型漏洞检测的有效性
为回答 RQ4,我们从不同类型的检测方法中选择了四种表现最佳的方法(即 RATS、Devign、PDBERT 和 ChatGPT),这些方法在其类别中总体性能最优。随后,我们在 CWE-190、CWE-400、CWE-415、CWE-416 和 CWE-787 上评估了这些方法。这些漏洞类型代表了最常见的漏洞,突显了它们对软件破坏的高潜力。对于每种类型,我们特意在时间划分设置下选择了 200 个代表性样本,以避免数据泄露问题。图 4 展示了四种基线方法在每种漏洞类型上正确预测的样本数量。

ChatGPT 在每种 CWE 漏洞检测中的卓越表现
在单一漏洞检测领域,我们的分析表明,ChatGPT 表现出了卓越的性能,正确识别了 668 个样本,平均 F1 分数达到 47.53%。例如,在 CWE-416 中,ChatGPT 正确检测了 131 个样本,并实现了 45.67% 的 F1 分数,其效果优于一般的漏洞检测。此外,ChatGPT 独家识别了 20 个样本,而 RATS、Devign 和 PDBERT 分别仅能检测到 2、2 和 1 个样本。因此,在现实场景中利用 LLMs 设计针对特定 CWE 类型漏洞的检测器是切实可行的。
值得探索如何结合不同基线的漏洞检测能力
在 CWE-787 的 200 个样本中,四种基线方法平均正确检测了 122 个样本。这四种模型的能力通过它们正确预测 181 个样本的能力得到了证明,展示了它们的互补优势。同时,存在一个子集包含 75 个样本,这些样本可以被四种基线中的任何一种检测到,说明了它们检测能力的重叠部分。未来,值得探索如何结合不同方法的漏洞检测能力。
RQ4 总结
实验结果表明,ChatGPT 在每种 CWE 类型漏洞检测中表现出色。此外,未来值得探索如何结合不同方法的能力以进行软件漏洞检测。
6 讨论
6.1 研究发现的意义
在本节中,我们讨论了我们的工作对软件漏洞检测的意义。实验结果还揭示了软件漏洞检测时代潜在的研究方向。具体如下:
(1) 对于 RQ1,基于微调的方法在随机划分设置中表现出优异性能。然而,它们需要考虑时间因素以在现实场景中更有效。基于程序分析和提示的方法在时间划分设置中未受影响。利用 LLMs 和提示技术可以成为缓解时间划分设置下性能下降的解决方案,从而增强其在现实场景中的适用性。
(2) 对于 RQ2,使用基于词法的方法识别与漏洞相关的依赖关系相较于其他基于语义的方法表现更好。然而,不同代码样本中的依赖数量并不一致。因此,如何自动识别这些依赖关系仍需进一步研究。
(3) 对于 RQ3,在代码库级漏洞检测中整合与漏洞相关的上下文相较于函数级漏洞检测提升了性能。此外,更大的 LLMs 从代码库级漏洞相关知识中获益更多。预测与漏洞相关依赖的检索技术是当前代码库级方法性能提升的主要瓶颈之一,这一问题仍未解决。
(4) 对于 RQ4,ChatGPT 在检测特定 CWE 漏洞类型时比其他漏洞检测方法更有效。此外,未来值得探索如何结合不同方法的能力以进行软件漏洞检测。
6.2 对研究有效性的威胁
基线选择的代表性
我们研究有效性的一个潜在威胁来自于实验中用于漏洞检测的基线的代表性。由于计算资源的限制以及 API 使用成本过高,我们未进行涉及 34B 模型规模的实验,也未涵盖 CodeGeex [64]、StarCoder [28] 和 GPT-4 [40] 等其他当代模型。未来的研究将开展更广泛的基线实验以提高覆盖范围。
在其他编程语言上的通用性
在本文中,我们的实验分析仅聚焦于 C/C++ 编程语言,未包括 Java 和 Python 等其他流行语言。然而,VulEval 的系统设计并不依赖语言特定特性,因此可以推广到其他编程语言。在未来的研究中,我们打算在更广泛的编程语言背景下评估 VulEval 的有效性。
基线的实现
为复现基线,我们仔细遵循开源代码和原始论文中描述的方法。然而,由于 Devign [65] 的实现细节和超参数不可用,我们的复现参考了 Reveal [6] 的实现。
7 相关工作
我们在第 2 节中详细阐述了漏洞检测方法,并在本节中重点说明漏洞数据集。这些数据集大致可以分为三类:函数级、切片级和文件级。
函数级数据集
函数级数据集 [8, 16, 65] 使用人工构造的案例或从真实场景的代码片段中提取函数源代码。例如,SARD [39] 通过手动检查和工业生产构建样本;Reveal [6] 从开源代码库中收集补丁,并从补丁的代码更改中提取函数级数据。这些函数级数据集的主要局限性在于其上下文提供的代码段较为受限。
切片级数据集
切片级数据集 [10, 43] 通常使用预定义规则或静态工具从源代码中提取数据流图(DFG)和控制流图(CFG)。例如,Vuldeepecker [35] 通过生成代码片段(Code Gadget)构建代码片段数据库(CGD),其中包括 CWE-119 和 CWE-399 漏洞。μVuldeepecker [66] 在此基础上扩展,收集了 40 种漏洞类型及其对应的标签。
文件级数据集
文件级数据集 [13, 20, 27] 通常提供来自漏洞补丁的完整文件。这类数据集提供了源代码的全面快照,有助于检测漏洞可能存在的更广泛上下文。例如,CrossVul [38] 构建了一个涵盖 40 种编程语言和 1,675 个项目的数据集,但仅提供文件级源代码。然而,先前的工作更多关注于收集函数级或文件级数据,而忽视了对检测跨函数漏洞至关重要的代码库级依赖关系。
8 结论与未来工作
在本文中,我们提出了一种整体的多级评估系统 VulEval,旨在同时评估跨函数和函数内漏洞的软件漏洞检测性能。具体而言,VulEval 包含三个评估任务:函数级漏洞检测、漏洞相关依赖预测和代码库级漏洞检测。VulEval 还包含一个大规模漏洞数据集。通过对随机划分和时间划分数据分别评估 19 种漏洞检测方法,我们观察到整合与漏洞相关的依赖关系相较于函数级漏洞检测提升了代码库级漏洞检测性能。我们的分析突出了当前软件漏洞检测领域的进展及未来方向。
未来,我们将探索代码库级漏洞检测的更多方面,例如设计用于识别漏洞相关依赖的检索方法,以及在提示中整合依赖信息。

1111

被折叠的 条评论
为什么被折叠?



