机器学习漏洞检测的困境:函数级分类为何是伪命题?

发布时间:2026/5/24 11:05:26

机器学习漏洞检测的困境:函数级分类为何是伪命题? 1. 项目概述与核心问题在软件安全研究领域利用机器学习进行漏洞检测Machine Learning for Vulnerability Detection, ML4VD已经成为一个炙手可热的方向。简单来说这个领域的梦想是让AI像一位经验丰富的安全专家一样扫描成千上万行代码自动、精准地揪出那些可能导致缓冲区溢出、整数溢出、释放后使用等安全漏洞的“坏代码”。过去五年顶级软件工程和安全会议上近九成的相关论文都在朝着这个目标努力它们不约而同地将问题简化成了一个看似清晰的二元分类任务给你一段独立的函数代码模型需要判断这个函数是否包含安全漏洞。这个设定听起来很直接也便于构建数据集和评估模型。业界常用的几个基准数据集比如 BigVul、Devign都是按照这个思路构建的从开源项目的漏洞修复提交patch commit中提取被修改的函数标记为“漏洞函数”并从同一项目的其他部分或不同项目中提取一些函数标记为“安全函数”。然后研究者们就拿着这些数据集训练各种复杂的图神经网络、Transformer模型比拼谁的准确率、F1分数更高。但这里存在一个根本性的、被广泛忽视的假设仅凭一个孤立的函数体我们真的能断定它是否“漏洞”吗作为一名长期混迹于代码审计和漏洞挖掘一线的从业者我每次看到这种“函数级分类”的论文心里都会打个问号。在实际工作中当我面对一个可能有问题的函数时我的第一反应绝不是只看它本身。我会立刻去翻看它的调用链谁调用了它传入了什么参数这些参数在更上游的代码路径中是否经过了充分的校验这个函数所处的模块状态是怎样的一个函数本身可能写得“人畜无害”但如果在某个特定的、未经校验的调用上下文里它就会变成灾难的源头。反之一个看起来“危如累卵”的函数比如内部有一个未经验证的空指针解引用如果它的所有调用者都确保了传入参数不可能为空那它在当前程序中就是安全的。这种漏洞是否显现依赖于外部调用上下文的现象我们称之为“上下文依赖”。令人震惊的是我们的实证研究发现在主流数据集中超过90%的所谓“漏洞函数”或“安全函数”其标签的有效性都严重依赖于上下文。这意味着当前绝大多数ML4VD研究赖以生存的“函数级分类”问题本身可能就是一个“伪命题”。更糟糕的是模型在这些有缺陷的基准测试上取得的高分很可能不是学会了识别漏洞而是学会了识别数据集中与漏洞标签虚假相关的表面特征比如某些特定API名称、变量名的出现频率甚至是代码注释的风格。这就好比让学生参加一场考试考题本身就有问题但评分标准却只看最终答案是否与一个有缺陷的“标准答案”匹配。学生可能通过死记硬背“标准答案”的特征比如选择题总是选C得了高分但这完全不能证明他理解了知识本身。我们的研究就是要揭示这场“错误考试中的高分”背后的真相并探讨如何设计一场更靠谱的“考试”。2. 主流方法剖析函数级分类为何成为“标准答案”要理解问题的根源我们得先看看当前ML4VD领域的研究范式是如何形成的。通过对近五年顶级会议和期刊如ICSE、FSE、SP、USENIX Security等上81篇相关论文的系统性调研我们发现了一个高度统一的模式。2.1 问题定义的趋同与数据集垄断高达88%的论文将ML4VD明确定义为函数级二元分类问题。给定一个函数f的代码通常是C/C代码模型输出一个二值标签1表示有漏洞0表示安全。这个定义的吸引力是显而易见的数据单元明确函数是编程中天然的逻辑单元边界清晰易于从代码仓库中提取。便于建模无论是基于序列代码文本、抽象语法树AST还是代码属性图CPG的模型都能以函数为粒度进行特征提取和表示学习。评估简单可以直接套用机器学习中成熟的分类评估指标如准确率、精确率、召回率、F1分数、AUC-ROC等便于论文间横向比较。这种问题定义的趋同直接导致了数据集的集中化。如图3所示BigVul、Devign和ReVeal这三个数据集占据了绝对主导地位超过65%的论文至少使用了其中之一。这种“基准-方法”的紧密耦合使得整个领域的研究在很大程度上被这几个数据集所定义和局限。2.2 一个典型的“漏洞函数”示例及其陷阱让我们看一个来自DiverseVul数据集的真实例子对应CVE-2021-29599TfLiteStatus ResizeOutputTensors(TfLiteContext* context, TfLiteNode* node, const TfLiteTensor* axis, const TfLiteTensor* input, int num_splits) { int axis_value GetTensorDataint(axis)[0]; // [...] const int input_size SizeOfDimension(input, axis_value); TF_LITE_ENSURE_MSG(context, input_size % num_splits 0, Not an even split); const int slice_size input_size / num_splits; // 潜在除零错误 for (int i 0; i NumOutputs(node); i) { TfLiteIntArray* output_dims TfLiteIntArrayCopy(input-dims); output_dims-data[axis_value] slice_size; // [...] TF_LITE_ENSURE_STATUS(context-ResizeTensor(context, output, output_dims)); } return kTfLiteOk; }在数据集中这个函数因为其修复提交patch涉及对num_splits参数进行零值检查而被标记为“漏洞函数”。模型的任务是只看这个函数体判断它是否有漏洞。关键问题来了这个函数在第10行有一个除法input_size / num_splits。如果num_splits为0会导致除零错误。但是仅从这个函数内部我们无法知道num_splits是否可能为0。这个信息存在于所有调用此函数的地方。如果所有调用者都确保传入的num_splits 0那么这个函数在当前的程序上下文中就是安全的。反之如果存在一个调用路径传入了num_splits 0那它就是漏洞。这个简单的例子揭示了函数级分类的根本缺陷它强行将一个本质上需要上下文信息调用者行为才能做出的判断压缩成了一个仅基于局部信息的猜测。这就像医生只看一张局部皮肤的照片就诊断是否患癌而不询问病史、不做全身检查。2.3 与静态分析和软件测试的对比其实“上下文依赖”问题在传统的程序分析领域早已被充分认识和讨论。静态分析会严格区分过程内intra-procedural和过程间inter-procedural分析。过程内分析只关注单个函数但会因此产生大量误报False Positive因为它必须对未知的调用上下文做最坏的假设。高级的静态分析工具会尝试构建函数摘要function summary或要求用户提供前置条件precondition来缓解这个问题。软件测试区分单元测试和系统测试。单元测试针对单个函数需要精心构造测试用例包括输入参数来覆盖各种情况。如果测试用例没有覆盖到num_splits0的情况那么这个除零错误就无法被发现。这正说明了函数行为对输入的依赖。然而当前的ML4VD研究在很大程度上“忘记”了这些来自姊妹领域的深刻见解将漏洞检测过度简化并沉浸在基于有缺陷基准的高性能报告中。3. 实证研究拆解基准测试的“黑箱”为了量化函数级分类问题的缺陷到底有多严重我们设计并实施了一项严格的实证研究。我们选取了三个最流行的数据集BigVul、Devign和DiverseVul。研究核心围绕两个研究问题展开。3.1 RQ1函数级分类是一个定义良好的问题吗我们首先需要确认数据集中标记为“漏洞”的函数是否真的包含漏洞解决标签噪声问题。然后重点分析这些漏洞的上下文依赖性。3.1.1 数据采样与人工审计流程我们从每个数据集中随机抽取了100个被标记为“漏洞”的样本总计300个函数。审计工作由两名具有丰富软件安全研究经验的研究者独立进行耗时超过150小时。对于每个函数审计者需要找到对应的原始Git提交commit和CVE报告如果有。理解这个提交修复了什么安全问题。判断这个被抽样的函数是否是漏洞的根源即修复这个漏洞是否必须修改这个函数。如果不是则标记为“安全”Secure。如果是则标记为“真实漏洞”Vulnerable。对于“真实漏洞”函数进一步判断其漏洞是否是上下文依赖的。即仅看这个函数代码本身能否确定它一定会导致漏洞还是说需要额外的调用上下文信息才能做出判断注意这个审计过程极其耗时且需要专业知识但它对于评估数据质量至关重要。许多后续研究直接使用这些数据集的原始标签而忽略了其中可能存在的巨大噪声和歧义。3.1.2 令人震惊的发现无处不在的上下文依赖审计结果清晰地揭示了当前基准测试的根本性缺陷标签噪声即使在经过筛选的“漏洞”样本中仍有相当一部分函数并非漏洞的真正根源。例如有些提交修复的是文档、配置或构建脚本其关联的函数代码本身并无安全缺陷。这与其他研究如Croft等人的发现一致即数据集中存在标签不准确的问题。漏洞的上下文依赖性Context-dependent Vulnerability对于被确认为“真实漏洞”的函数超过90%的漏洞是上下文依赖的。这意味着仅看这个函数代码你无法断定它“有漏洞”。它的“漏洞”属性完全取决于是否存在一个“错误”的调用上下文比如传入一个非法参数。图1中的除零错误就是一个典型例子。函数本身只是一个“工具”工具是否危险取决于使用者如何用它。安全的上下文依赖性Context-dependent Security更有趣的是反向思考。对于那些被标记为“安全”的函数通常是从非漏洞提交或不同项目中抽取的我们能否构造一个调用上下文使得它变得“有漏洞”答案是在绝大多数情况下可以。许多“安全”函数包含潜在的危险操作如指针解引用、数组访问、除法运算它们之所以安全仅仅是因为在当前程序的所有调用路径中前置条件都得到了满足。只要稍微改变一下调用上下文例如一个调用者忘记进行边界检查这些函数立刻就会变成漏洞源。结论对于数据集中绝大多数的函数样本无论是标为漏洞还是安全其标签的有效性都严重依赖于未在样本中提供的上下文信息。因此要求模型仅根据函数体代码进行分类相当于让模型去猜一个缺失了关键条件的谜题。这个问题的定义本身是不完整的基于此得出的任何模型性能评估其内部效度都值得严重怀疑。3.2 RQ2高分数从何而来虚假相关性的力量既然问题定义有严重缺陷为什么那么多论文报告他们的模型在BigVul、Devign等数据集上取得了“优异”的性能例如超过90%的准确率我们的假设是模型并没有学会检测漏洞的本质特征而是学会了利用数据集中与漏洞标签虚假相关的表面统计特征。这些特征与漏洞的因果机制无关但恰好在这个特定的数据分布中与标签有较强的相关性。3.2.1 设计一个“荒谬”的实验为了验证这个假设我们设计了一个极简的、完全抛弃代码语义和结构的分类器特征我们不再使用复杂的AST、CFG或代码嵌入向量。我们只使用最简单的词袋模型统计每个函数中各个单词包括变量名、函数名、关键字等出现的频率。模型使用一个经典的梯度提升树模型如XGBoost。任务在同样的数据集BigVul, Devign上用同样的训练/测试划分进行函数级漏洞分类。这个设置是“荒谬”的因为单词频率完全无法表达程序的逻辑、数据流或控制流而这些才是决定是否存在漏洞的关键。3.2.2 实验结果与启示令人震惊的是这个仅基于单词频率的“荒谬”分类器其性能以F1分数、AUC等指标衡量竟然与许多声称利用了高级代码表征的最先进的ML4VD模型不相上下这个结果具有颠覆性意义它提供了强有力的证据证明当前主流数据集存在严重的虚假相关性。模型可以通过记忆诸如“在漏洞函数中memcpy、strcpy等‘危险’函数名出现频率更高”、“安全函数中assert、CHECK等检查宏出现更多”之类的表面模式来获得高分而无需理解这些API为何危险、在什么条件下危险。它挑战了现有评估方法的有效性。如果一个仅靠数单词就能达到SOTA性能的模型被认为是“成功”的那么整个评估体系就在奖励错误的技能。这就像考试奖励背答案而不是理解原理。它解释了为何模型泛化能力差。一旦部署到新的、代码风格或词汇分布不同的项目上这些基于虚假特征的模型就会迅速失效因为其依赖的表面相关性在新环境中不复存在。实操心得在评估一个ML4VD模型时一个非常有效的“嗅觉测试”是尝试用最简单的特征如n-gram、关键字频率和简单的模型如逻辑回归、随机森林跑一个基线。如果这个基线模型的性能和你精心设计的复杂神经网络模型差距不大那就需要高度警惕你的数据集和任务定义是否存在虚假相关性问题。真正的进步应该体现在模型能够超越这些表面特征捕捉到更深层的、语义上的漏洞模式。4. 构建更有效的漏洞检测评估体系既然当前的函数级分类基准存在根本缺陷我们应该如何前进完全否定机器学习在漏洞检测中的应用前景是武断的。关键在于我们需要重新思考问题定义和评估方法使其更贴近安全分析的现实需求。4.1 迈向上下文感知的评估核心思路是将调用上下文纳入评估范围。这并非要完全抛弃现有工作而是对其进行升级和细化。以下是几个有潜力的方向函数对Function Pair或上下文片段分类不再孤立地给一个函数打标签而是提供一个“函数其某个调用者”的配对或者一个小的代码片段包含该函数及其直接上下文。任务变为在此特定上下文中该函数是否存在漏洞这更接近代码审计时“点击函数调用查看调用方”的实际操作。漏洞触发条件生成对于给定的一个函数任务不是二分类而是生成使其触发漏洞的前置条件或测试输入。例如对于前面的除零例子模型应输出“当num_splits 0时存在除零漏洞”。这直接评估了模型对漏洞根因的理解。过程间Inter-procedural切片级分类以“漏洞触发点”为核心构建包含相关数据流和控制流依赖的代码切片这个切片可能跨多个函数。以此切片作为分类单元比单个函数更合理因为一个漏洞的成因和触发可能涉及多个函数协作。4.2 创建更严谨的数据集构建新数据集是推动领域发展的基础。新的数据集应包含上下文信息至少提供函数的主要调用者信息或整个模块的代码。精细化的标签不仅标注“是否有漏洞”还应标注漏洞类型CWE ID、漏洞触发的具体位置、以及关键的漏洞触发条件。平衡与多样性确保数据来自更多样化的项目避免编码风格和库使用的单一化减少数据集特定的偏见。区分“易混淆”样本主动包含那些在表面特征上相似但漏洞属性不同的样本以及上下文依赖性的正反例迫使模型学习更深层的特征。4.3 改进模型训练与评估策略即使暂时仍使用现有数据集进行研究也可以采用一些策略来缓解问题对抗性评估使用代码重构、变量重命名、语义保持的代码变换等技术生成测试集的变体。一个健壮的模型应该在经过合理变换的代码上保持性能而一个依赖虚假特征的模型则会性能骤降。因果特征学习探索如何让模型更多地关注与漏洞有因果关系的代码模式如缺少边界检查的数组访问而不是与漏洞共现的无关特征如特定的变量名。这可以通过解耦学习、因果干预等前沿机器学习技术来尝试。评估指标的扩展除了传统的分类指标引入更能反映实用性的指标如可行动性模型是否能指出漏洞具体位置和类型、可解释性模型的决策依据是否是人类可理解的代码模式。5. 对研究与实践的启示这项研究的结果对ML4VD领域的研究者和意图将相关技术应用于实践的工程师都有重要启示。5.1 给研究者的建议重新审视问题定义在开始设计下一个复杂的GNN或Transformer模型之前请先花时间思考你试图解决的“漏洞检测”问题在现实中究竟是什么样的。函数级分类可能是一个方便的起点但绝非终点。重视基准测试的缺陷对BigVul、Devign等数据集的高分结果保持批判性态度。在论文中应主动讨论上下文依赖性和虚假相关性对结果的潜在影响并使用4.3节提到的策略进行鲁棒性测试。探索新范式勇于尝试4.1节中提出的更贴近实际的问题定义。虽然构建带上下文的数据集更费力评估也更复杂但这可能是推动领域产生实质性突破的关键。跨领域借鉴多向程序分析、软件测试领域的同行学习。他们数十年来在上下文敏感分析、条件生成、切片技术等方面的积累能为ML4VD提供宝贵的先验知识和解决方案框架。5.2 给安全工程师的提醒对现有工保持合理期待目前市场上或开源社区中直接宣称能“扫描代码即得漏洞”的AI工具其实际效果很可能被夸大。在关键系统中绝不能将其作为唯一或主要的安全审计手段。关注工具的“可解释性”选择一个工具时不要只看它报出了多少个漏洞。更要看它是否清晰地解释了为什么这里可能是漏洞例如指出了哪条数据流未经验证、哪个条件可能被触发。一个能提供合理解释的工具即使召回率稍低也远比一个乱报高分但无法解释的“黑箱”模型有用。将其作为增强手段而非替代品最有效的使用方式可能是将ML4VD工具作为传统静态分析工具如Coverity, Fortify和动态分析/模糊测试的补充。用它来对海量代码进行初步筛选标记出需要人工重点审查的“可疑区域”可以提升资深安全专家的工作效率。机器学习为自动化漏洞检测带来了新的希望但我们必须清醒地认识到将复杂的软件安全问题强行塞进一个过于简化的机器学习框架中可能会带来误导性的“进步”。这项研究敲响的警钟是在追逐更高的准确率数字之前我们首先需要确保自己正在解决一个正确的问题并且用正确的方式去评估它。未来的道路在于建立上下文感知的、因果驱动的、贴近真实安全分析场景的评估体系只有这样机器学习才能真正成为软件安全工程师手中一件可靠而强大的工具。

相关新闻