这期解读的安全论文是来自安全顶级会议之一的NDSS 2026论文题目是Accurate Identification of the Vulnerability-Introducing Commit based on Differential Analysis of Patching Patterns官网链接为 https://www.ndss-symposium.org/ndss-paper/accurate-identification-of-the-vulnerability-introducing-commit-based-on-differential-analysis-of-patching-patterns/一、论文背景这篇论文关注的是漏洞引入提交识别问题也就是当某个软件版本被发现存在漏洞之后如何沿着代码提交历史向前追溯准确找出漏洞最早被引入的那个 commit。论文将这个 commit 称为 Vulnerability-Introducing Commit简称 VIC。这个问题在漏洞管理中非常关键因为安全团队在处理 CVE 时不能只关心“当前版本有没有修复”还需要判断“哪些历史版本实际上已经受到影响”。如果无法准确定位 VIC就很难确定漏洞影响范围也会影响补丁回溯、版本修复、供应链安全检测和漏洞数据库维护等工作。从实际背景来看NVD、OSVDB、Bugtraq 等漏洞数据库通常会记录 CVE 描述、受影响版本和补丁信息Snyk、Dependency-Check、OSSIndex 等安全工具也高度依赖这些数据库。但论文指出公开漏洞数据库中的 affected versions 信息并不总是可靠。例如已有研究发现约 25% 的版本被错误标记为受影响版本还有研究显示只有 59.82% 的漏洞报告能够与 NVD 完全匹配。这意味着如果企业只依赖漏洞数据库给出的影响版本范围就可能出现两类问题一类是把并不受影响的版本误判为存在漏洞造成误报和不必要的修复成本另一类是把真实受影响的历史版本漏掉导致漏洞长期残留。因此论文认为更可靠的方式是回到代码演化历史中识别漏洞最早是从哪个 commit 被引入的。现有方法虽然已经尝试通过补丁回溯 VIC但仍存在明显局限。典型方法如 B-SZZ、V-SZZ 等主要关注安全补丁中的删除语句认为被删除的代码往往就是漏洞相关代码。这种思路有一定合理性因为很多补丁确实会删除错误逻辑但论文认为这种假设过于粗糙原因主要有三点第一补丁中的代码不一定都与漏洞修复直接相关。安全补丁中常常混入函数重命名、变量声明、注释、格式调整、代码重构等内容。这些语句虽然出现在 patch 中但并不决定漏洞是否存在。如果将它们也纳入 VIC 匹配一旦历史版本中发生过重构或格式变化就可能导致匹配失败从而误判漏洞引入位置。第二真正触发漏洞的关键语句不一定出现在补丁中。例如修复缓冲区溢出时补丁可能只是新增边界检查语句但真正访问缓冲区的语句可能没有被修改修复 use-after-free 时补丁可能新增释放前的状态检查但真正触发重复释放的控制流语句可能仍然是原代码中的未修改语句。如果只分析 patch 中的新增行和删除行就会漏掉这些漏洞触发路径上的核心语句。第三补丁和早期版本之间可能存在大量代码演化差异。漏洞通常是在较新版本中被发现并修复的但它可能早在很久以前就被引入。早期版本中的代码结构、函数名称、变量名称、控制流组织方式可能都发生过变化因此不能简单地用“补丁文本是否完全匹配”来判断历史版本是否存在漏洞。论文以CVE-2023-6111作为运行示例说明这一问题。该漏洞是 Linux kernel 中的 use-after-free 漏洞补丁中有些代码只是抽取函数、减少重复逻辑并不是漏洞本身但某些没有被修改的控制流语句却是触发漏洞的关键条件。由此可以看出VIC 识别的难点不在于简单匹配补丁文本而在于理解补丁背后的漏洞修复语义补丁到底修复了什么错误逻辑哪些语句真正决定漏洞是否存在以及这些关键语句最早出现在历史代码中的哪个位置。二、工作概述针对上述问题论文提出了一个名为VicDiff的自动化 VIC 识别方法。它的核心思想是不要把补丁看作一组孤立的新增行和删除行而要分析新增语句与删除语句之间的关系从补丁修复模式中还原漏洞相关逻辑再用这组真正关键的语句序列去历史版本中回溯匹配。换句话说VicDiff 并不是简单判断“某个补丁是否存在”也不是简单追踪“哪一行删除语句最早出现”而是先理解补丁的修复行为再提取能够代表漏洞存在性的关键代码序列。具体来说VicDiff 主要完成三件事一是从补丁中去掉明显无关的噪声语句只保留漏洞相关语句二是对补丁中的新增语句和删除语句进行差分分析判断它们属于哪一种修复模式例如新增缺失检查、删除错误语句、修改错误调用、调整参数、移动语句位置等三是基于这些修复模式从漏洞文件中提取一条更精炼的 vulnerability-critical statement sequence简称 VCSeq即漏洞关键语句序列并用它去历史提交中寻找漏洞最早出现的位置。论文第 4 页图 3 展示了 VicDiff 的整体流程。整个过程可以概括为输入安全补丁以某个 CVE 的 security patch 作为分析起点过滤噪声语句删除函数重命名、注释、空行、变量声明、方法抽取等与漏洞逻辑关系不大的语句差分分析补丁模式结合漏洞修复前文件 Fv 和修复后文件 Fp分析控制流、数据流、语句位置、操作符和参数差异提取漏洞关键语句序列 VCSeq不是直接使用完整 patch而是提取真正决定漏洞是否存在的关键语句筛选相关历史提交和文件版本只关注涉及漏洞函数的 commits减少无关代码演化带来的干扰历史版本匹配按照逆时间顺序在历史文件中匹配 VCSeq找到从“漏洞逻辑存在”到“漏洞逻辑不存在”的边界从而确定 VIC。这个流程的创新点在于它把补丁分析从传统的文本级匹配提升到了模式级和语义级分析。传统方法更像是在问“这几行代码有没有出现过”而 VicDiff 更进一步问“这些代码变化代表了哪种漏洞修复行为真正导致漏洞存在的代码逻辑是什么这段逻辑最早从哪个 commit 开始出现”这种方式既能减少无关补丁语句导致的误判也能补充那些没有直接出现在补丁中、但对漏洞触发至关重要的语句。从实验结果看论文使用 Linux kernel、OpenSSL、Wget、MySQL、FFmpeg 等开源项目构建数据集。其中Linux kernel 数据集包含6,920 个 CVE和 5,859,238 个代码提交总体数据集共涉及 6,943 个 CVE。实验结果显示在 Linux kernel 上VicDiff 的 **precision 达到 94.94%recall 达到 86.92%**明显优于 B-SZZ、V-SZZ 和 Redebug 等基线方法。论文还通过消融实验说明噪声过滤和漏洞关键语句序列提取是性能提升的关键如果不进行噪声过滤容易因为重构、变量声明等无关语句造成漏报如果只依赖删除语句又会漏掉大量没有直接出现在 patch 中的关键漏洞触发语句。因此VicDiff 的优势主要来自它对补丁修复语义的更细粒度理解。三、工作具体说明VicDiff 的第一步是过滤补丁中的噪声语句。论文认为安全补丁虽然包含漏洞修复信息但补丁并不等于漏洞逻辑本身。一个 patch 中除了真正修复漏洞的代码外还可能包含大量非语义修改。例如函数重命名可能只是为了提升可读性方法抽取可能只是为了减少重复代码变量声明通常只是为了支持后续新增逻辑注释和空行更不会影响程序实际执行。如果这些语句被纳入 VIC 匹配那么历史版本中只要出现一次函数重构、命名调整或格式变化就可能导致匹配失败从而把仍然存在漏洞的版本误判为不受影响。为此VicDiff 通过语法分析和正则匹配过滤五类常见噪声语句函数重命名、方法抽取、变量声明、注释行和空行。以 CVE-2023-6111 为例补丁中新增的辅助函数和部分变量声明会被视为噪声过滤掉系统只保留真正可能影响漏洞逻辑的新增语句和删除语句形成 vulnerability-related statements简称 VRStmt即漏洞相关语句集合。这个步骤的意义在于先把补丁“净化”为更接近漏洞本质的代码变化集合减少后续分析被重构类修改干扰的可能性。第二步是论文的核心即基于差分分析识别补丁修复模式。论文指出补丁中的新增语句和删除语句并不是互相独立的。一个新增语句可能是对某个删除语句的替换也可能是把原来的逻辑移动到了其他位置还可能是在原有控制流附近补充缺失的安全检查。因此VicDiff 会比较漏洞修复前文件 Fv 和修复后文件 Fp 的控制流图分析补丁语句在位置、操作符和参数上的差异并把语句划分为不同的 patching patterns。论文第 5 页图 4 总结了这些模式最基础的是 A、R、M 三类其中 A 表示新增语句常见于补充边界检查、权限检查或状态校验R 表示删除错误语句常见于移除会导致漏洞的冗余逻辑、危险调用或错误释放M 表示修改语句通常意味着原有语句存在缺陷需要删除错误逻辑并插入正确逻辑。对于更复杂的“既有新增也有删除”的补丁论文进一步把 M 类型细分为 M.1 到 M.5。可以这样理解M.1原位置直接修改。控制流位置基本不变只是某条语句本身被修正通常对应明显的编码错误修复。M.2前后定位节点一致。修改发生在同一个局部代码块中前后逻辑结构基本一致说明修复集中在某个局部语句上。M.3部分定位节点一致且控制语句类型相同。说明修复前后逻辑意图相近但实现条件发生了调整。M.4操作符相同但参数不同。说明函数调用或操作本身没有变但传入的数据发生变化通常暗示数据流存在问题。M.5语句本身相同但位置变化。说明语句内容可能没错但原本放置的位置或控制依赖不合适可能导致执行时机错误。通过这种分类VicDiff 不只是判断“代码发生了变化”而是进一步理解“这次变化是在修复哪一种漏洞逻辑”。这也是论文区别于传统 SZZ 类方法的重要地方传统方法主要看删除了什么而 VicDiff 会同时分析新增、删除、修改、移动、参数变化和数据流关系。第三步是根据修复模式提取漏洞关键语句序列 VCSeq。这一步非常关键因为 VIC 回溯最终依赖的不是完整补丁而是一组真正决定漏洞是否存在的关键语句。论文认为不同修复模式对应不同的关键语句提取策略。例如对于 Pattern R删除语句本身通常就是错误逻辑因此会被直接加入 VCSeq对于 Pattern M.1、M.2、M.3原始被修改的语句往往就是漏洞关键点也会直接加入对于 Pattern A由于新增语句在历史版本中本来不存在不能直接拿新增语句去匹配所以 VicDiff 会沿着数据流寻找与新增检查相关的上游赋值语句和下游使用语句把这些仍然存在于漏洞版本中的语句加入 VCSeq对于 Pattern M.4系统不仅会加入原删除语句还会结合新增语句的数据流寻找相关依赖对于 Pattern M.5由于问题往往不在语句内容本身而在执行位置或控制依赖因此系统会把其控制流相邻语句加入关键序列。这个设计解决了一个非常实际的问题很多漏洞的关键触发逻辑并不完整地出现在 patch 中。如果直接匹配 patch可能过于依赖表面文本如果只匹配删除语句又可能漏掉真正的触发条件。VCSeq 则试图在两者之间取得平衡它不是完整补丁也不是单一删除行而是一组更能代表漏洞存在性的关键语句。以 CVE-2023-6111 为例最终提取出的关键序列包括第 21、26、31 行这些语句共同反映了漏洞触发路径而不是简单把补丁中所有新增删除行都拿来匹配。这样一来VicDiff 对漏洞逻辑的刻画更加准确也更适合在历史版本中回溯。最后一步是在历史提交中匹配 VCSeq 并确定 VIC。VicDiff 会先筛选与漏洞函数相关的提交和文件版本因为大型项目中绝大多数 commits 与某个特定漏洞无关如果不筛选会造成巨大的计算开销和噪声干扰。对于函数名随版本演进而变化的问题论文设计了函数声明栈用来记录函数旧名称和新名称避免因为函数重命名导致回溯中断。随后系统按照逆时间顺序扫描相关文件版本在每个版本的漏洞函数中按顺序匹配 VCSeq。如果某个历史版本仍然能够完整匹配这组关键语句就认为该版本仍然存在漏洞当继续向更早版本回溯时第一次出现无法匹配的位置就说明漏洞逻辑在此之前不存在因此最后一个仍然匹配的提交就是漏洞引入提交。COM1 是安全补丁COM2 是真正的 VICCOM3 是漏洞引入前的最后一个无漏洞提交。B-SZZ 会因为追踪所有删除语句而误判V-SZZ 会因为过度依赖删除语句的首次出现位置而定位不准Redebug 则会因为匹配补丁中的定位行和上下文行而受到历史代码变化影响相比之下VicDiff 通过提取 VCSeq更准确地抓住了漏洞存在所依赖的关键逻辑因此能够正确识别 COM2。总体来看这篇工作的价值在于它把补丁中的“修复行为”转化为可回溯的“漏洞关键逻辑”从而在大规模开源项目中更准确、更高效地识别漏洞最早被引入的位置。