Industry & PracticeResearch & Benchmarks
CornerCase: 协议 实现 的 自动 化 极值 测试
许多网络协议实现中的软件缺陷出现在规范边界附近,例如刚好落在允许范围之内或之外的输入,或者单独看来合法、但在给定状态下非法的报文。从 SSL Heartbleed 漏洞到 TCP 圣诞树数据包,边界输入一再暴露关键弱点,
In this piece
CornerCase:协议实现的自动化极值测试(中文全译)
翻译说明:本文是 arXiv 论文 CornerCase: Automated Extremal Testing of Protocol Implementations(arXiv:2606.29124)的中文全译,由智测团队翻译。原作者:Rathin Singha、Kuan Qian、Srinath Saikrishnan、Tracy Zhao、Soheil Abbasloo、Ryan Beckett、Siva Kesava Reddy Kakarla、Todd Millstein、George Varghese。原文以 CC BY 4.0 许可发布。译文保留原文全部章节、数据与结论;参考文献列表从略。
译者注:所提供源文对应 arXiv:2606.29124v2 [cs.NI](2026 年 8 月 4 日),正文止于第 7 节结论,未收录附录 A、B、C 的正文。文中对附录的交叉引用予以保留,不编造附录内容。源文中由排版产生的重复公式标记(如将 \(22\times\) 显示为 “22×22\times”)按原数值还原,不改变任何实验数字。
作者: Rathin Singha¹,Kuan Qian¹,Srinath Saikrishnan¹,Tracy Zhao¹,Soheil Abbasloo²,Ryan Beckett²,Siva Kesava Reddy Kakarla²,Todd Millstein¹,George Varghese¹
¹加州大学洛杉矶分校(UCLA) ²微软研究院(Microsoft Research)
许可:CC BY 4.0
摘要
许多网络协议实现中的软件缺陷出现在规范边界附近,例如刚好落在允许范围之内或之外的输入,或者单独看来合法、但在给定状态下非法的报文。从 SSL Heartbleed 漏洞到 TCP 圣诞树数据包,边界输入一再暴露关键弱点,却仍被模糊测试、基于模型的测试等现有技术测得不够充分。本文提出 CornerCase,一种自动化极值测试方法,系统性地针对此类边界行为。我们的核心思想是把测试生成分解为两个阶段:首先,大语言模型(LLM)以结构化、逐节的方式从协议规范(例如 RFC)中抽取显式的有效性约束;其次,在每条约束的边界上或边界附近生成极值测试用例。这些测试在多个实现上执行,再通过差分测试识别不一致。我们在广泛使用的 HTTP、DNS、BGP、SMTP 与 QUIC 实现上评估 CornerCase,发现了许多此前未知的缺陷。例如,HTTP 服务器 h2o 在处理包含编码空字节的 URL 时会进入重定向循环。总体而言,我们使用 CornerCase 识别并提交了 42 项异常;截至目前,其中 26 项已被确认为缺陷,18 项已修复,其余仍在积极调查中。
1 引言
物理学家会在无限质量这类极端情形上检验理论,软件开发者则惯常在口语中所谓的边界情形上测试代码,而这些情形通常是手工构造的。本文使用人工智能,先借助大语言模型从协议规范文档(RFC)中抽取约束,从而自动为网络协议实现生成边界情形。我们启动这一项目,是因为意识到许多在语义上有意义的边界情形——例如过长的 DNS 名称,或含有非法字符的 URL——不太可能由其他自动化测试生成器产生,例如模糊测试器 [67],或使用符号执行引擎的测试器 [35, 59]。
问题: 进一步说,许多现实世界中的软件缺陷出现在规范的边缘附近。这一问题在网络协议实现中更为突出:其输入格式复杂,并附带大量选项与约束。边界情形包括报文中非法的字段组合(例如 TCP 圣诞树攻击 [43])、空字段或缺失字段、畸形字段,以及罕见但合法的参数组合。值得注意的是,它们还包括违反协议语义的报文,例如在非预期状态下收到的格式正确的协议报文。举例来说,TLS 中非预期的握手报文(例如在非法状态下出现的 CLIENT_HELLO)可能导致崩溃或状态不一致 [18]。其他例子包括 OpenSSL 中臭名昭著的 Heartbleed 缺陷 [46](CVE-2014-0160):声称的长度大于实际载荷。
上述攻击由黑客手工构造,但大语言模型的兴起,使攻击者可能利用人工智能自动生成此类极值攻击,正如 Anthropic 使用其 Mythos [3] 模型识别内存安全漏洞。本文提出的问题是:我们能否利用大语言模型自动生成极值输入,以加固互联网协议实现,从而(主要)提升可靠性、(次要)提升安全性,并把重点放在报文安全类错误上——即那些会触发实现缺陷的报文。
解决思路: 我们以 CornerCase 对这一问题给出肯定回答。CornerCase 是一种方法及其实现,它以结构化方式使用大语言模型,为协议实现生成极值测试,并分析测试结果(如图 1 所示)。CornerCase 的输入包括:(i)一份协议规范(例如一份 RFC);(ii)用户定义的测试输入/输出格式(见附录 A);(iii)用于针对各实现执行输入的测试驾驭程序。输出是一组极值测试用例,以及在各实现之间观察到的、经过排序的差分异常集合。这一设计使 CornerCase 既与具体协议无关,也与具体实现无关。特别地,我们的方法把实现视为黑盒,不需要源代码访问,因此既适用于开源系统,也适用于闭源系统。
我们的方法利用了许多协议的两个性质。第一,它们拥有详尽的英文规范,例如 RFC,以自然语言描述有效性规则与约束,因此大语言模型成为抽取并推理这些约束的自然机制。第二,协议往往有多个相互独立的实现。这使我们能够通过差分测试发现缺陷:比较各实现的行为并标记不一致,而这些不一致往往表明真实缺陷,或规范中含糊的部分。
在方法上,我们并不要求大语言模型一次性生成测试用例,而是先用它从规范文档中抽取显式的有效性约束,一次处理一节,并由用户提供的测试输入格式加以引导,以便聚焦于与测试设置相关的约束。大语言模型以结构化形式输出约束(见附录 B),每条为元组,包含约束标识、节号,以及包含该约束的原始 RFC 句子。作为约束抽取的一部分,我们还解析文中对其他章节的引用(若有),这些引用被用来定义该约束,并加入所抽取的元组。
这一分解背后的关键方法选择,是先用大语言模型理解规范,再用它构造测试。若让模型直接根据完整 RFC 生成协议测试,得到的输出往往面广而浅:模型会遗漏约束,对细微边界探索不足,也无法系统地覆盖规范。通过先抽取显式约束、再据此生成极值测试,我们把一个含糊的端到端生成任务,转化为规范中约束上的覆盖问题。随后,我们分别要求大语言模型为每条已抽取约束生成极值测试(提示词见 §B.2),并确保所有生成的测试都符合用户提供的测试格式。每条约束被转化为测试用例的方式,是只修改相关输入字段,产生刚好低于、恰好处于、以及刚好高于所规定边界的取值。
把规范理解与测试构造分开,符合大语言模型的长处,也避开其短处。我们通过消融实验表明(表 3),这一分解产生的差分异常最多可达一次性大语言模型生成的 22 倍。
图 1:极值测试流水线
源文图 1 为流水线示意,各阶段如下:
- 约束生成:输入为用户提供的测试输入格式、测试输出格式与 RFC,输出为作用于输入的约束。
- 测试生成(分批进行):输出极值测试用例。
- 测试执行:由用户测试脚本驱动,得到各实现的结果,并筛出存在差异的测试。
- 差分测试。
- 结果分析:得到带置信度分数的结果。
- 分诊:按标签对异常做优先级排序并去重。
每个生成的测试用例都通过用户提供的测试驾驭程序,在多个实现上执行。该驾驭程序针对各实现运行输入,并以统一格式记录输出。随后我们进行差分分析,以检测行为不一致。最后,我们使用大语言模型生成的标签,把相关的差分异常分组;标签刻画的是底层约束与边界类型。标签是一个标记,它捕获约束标识,以及该测试是刚好合法还是刚好非法(见 §3.3)。大语言模型还协助起草供人工检查与编辑的候选缺陷报告。
极值测试与边界值分析(BVA)[5, 58, 56, 69] 相关。边界值分析是一种经典测试技术,针对允许范围边缘上的输入。然而,传统边界值分析主要关注数值或基于范围的约束,并且通常依赖人工识别的边界。相比之下,极值测试把这一思想推广到更广的一类约束,从 RFC 文本中抽取语法、语义以及基于状态的规则。本文概念想法的一个非常初步的版本曾在 [60] 中描述。
示例结果与认识: 使用 CornerCase,我们在 HTTP、DNS、BGP、SMTP 与 QUIC 实现中发现了 42 个缺陷。为刻画这些缺陷,我们区分合法边界情形与非法边界情形:前者是满足规范、但处于可接受行为边缘的输入;后者是几乎格式正确、却违反协议规则的输入。这些边界可能出现在语法、语义或状态机层面。
例如,HTTP 服务器 h2o [51] 对路径中含有百分号编码空字节的请求(例如 GET /%00)以 301 重定向到语义等价的路径作为响应,从而诱发重定向循环。这是低成本拒绝服务攻击的一个潜在向量(一个合法的语义边界情形)。
其他例子包括:畸形的 Host 首部被当作合法而接受(非法的语法边界情形,其中一则是潜在的钓鱼利用);SMTP 中嵌套的 MAIL 事务(一个状态机边界情形);QUIC 握手期间接受不正确的 TLS 版本(一个非法的语义边界情形);以及 AS_PATH 中包含该路由器自身联盟标识符的 BGP 路由(一个合法的语义边界情形)。
这些结果表明,许多重要的协议缺陷并非来自任意的畸形流量,而是来自刚好位于规范边界之内或之外的输入,或者单独看来合法、但在上下文中非法的输入——这正是极值测试旨在暴露的区间。
在运行 CornerCase 的过程中,我们得到以下经验,后文将进一步阐述。
- 聚焦与上下文: 我们最初的提示遗漏了许多约束,直到我们提示大语言模型在每份 RFC 中逐节处理,使模型聚焦于一小段文本。把某一节的约束所引用的章节也加进去(更多上下文)后,效果更好。
- 面向通用协议的提示模板: 允许用户以 JSON 文件指定测试格式后,该框架可以很容易地扩展到新协议以及此前未曾预料的特性。例如,当我们从固定文件系统——模型只生成 URI 查询——转向一种更丰富的格式,使其能够连同查询一起指明哪些文件应当存在、哪些不应当存在时,HTTP 测试得到了改进。
- 瓶颈转移: 我们使用大语言模型加快了测试与异常的生成,速度快到瓶颈现在转移到缺陷确认与理解上。CornerCase 为每个协议产生数百项差异,其中包括真实缺陷、用户配置错误造成的假象,以及规范下多种行为都可谓可接受的情形。为管理这一规模,我们使用人工智能进行优先级排序、打标签与分诊。
本文作出如下贡献:
- 认识: 我们表明,一旦把大语言模型的使用分解为两个阶段,极值测试就会更有力:先从自然语言规范中抽取约束,再由约束生成极值测试,从而把端到端测试生成转化为规范中约束上的覆盖问题。
- 方法: 我们开发了一条与协议无关、与实现无关的流水线,它只使用 RFC 文本、用户提供的测试格式,以及黑盒执行驾驭程序,从而能够在异构实现之间测试,而无需源代码访问或手工形式化。
- 证据: 我们在 5 个协议的 38 个实现上评估该方法,发现 42 个缺陷与不一致(26 个已确认,18 个已修复)。消融研究表明,分解中的每个组成部分都是必要的;完整流水线产生的异常最多可达一次性大语言模型生成的 22 倍。
论文其余部分从三个角度展开我们的主张:该方法所暴露的极值缺陷种类(§2)、使这些缺陷可被发现的结构化流水线(§3),以及分解确实重要、并能跨协议发现新缺陷的经验证据(§4)。
2 动机示例
下面三个例子用来说明规范边界在实践中以三种不同方式产生影响。HTTP 空字节情形处于合法的语义边界:百分号编码规则在语法上允许它,但其解释落在 URI 处理组件被一致定义所能处理的范围之外。BGP 联盟例子是合法的语义边界:报文格式正确,但其有效性取决于在解读一个字段(AS 路径)时同时参照另一个字段(路由器的联盟身份)。SMTP 嵌套例子是依赖于状态的边界:每条命令单独看来都合法,但给定协议状态后,这一序列变为非法。三者都由 CornerCase 发现,并得到相关开发者确认,现已修复。
2.1 HTTP 空字节循环(h2o)
RFC 3986 指出,带有百分号编码空字节(%00)的 URI,“若应用并不期望在该组件中接收原始数据,则应当被拒绝”。历史上,空字节被用于注入攻击,以利用底层字符串处理中的不一致。虽然大多数现代 HTTP 服务器把此类输入视为畸形,并在静态文件服务器的语境下返回 400 Bad Request 或 404 Not Found,我们使用 CornerCase 识别出 HTTP 服务器在空字节处理上的两个问题:h2o [51] 与 Caddy [33]。模型两次生成了这一测试:一次来自 RFC 中的 SHOULD 陈述,一次来自百分号编码的格式规则。
当对包含空字节的路径发出请求时(例如 GET /%00),h2o 以 301 Moved Permanently 重定向作为响应,其 Location 首部指向一条语义等价的路径(例如 /%00/),做法是追加一个尾部斜杠。这会诱发重定向循环,导致重复请求与资源放大。虽然单个客户端通常会限制重定向深度,此类行为在聚合后仍可能增加负载(例如来自爬虫或自动化客户端),从而可能形成一种低成本的拒绝服务向量。
相比之下,Caddy 把畸形路径传播到文件系统层,stat 调用中的空字节触发系统级 EINVAL 错误,并表现为 500 Internal Server Error。Caddy 不是在入口处把请求作为畸形予以拒绝,而是泄漏了一种内部失败模式——这可能干扰把 5xx 响应视为服务器故障的上游错误处理或监控系统。
2.2 BGP 联盟环路(GoBGP)
BGP 要求路由器拒绝 AS_PATH 中包含其自身自治系统(AS)号的路由,以防止路由环路。这一规则同样适用于 BGP 联盟。RFC 5065 规定,若路由器收到一条 AS_PATH 中包含其自身联盟标识符的路由,则必须将其视为 AS 环路并拒绝该路由。
极值测试生成了一个测试用例:路由器收到一条 AS_PATH 中包含其自身联盟标识符的路由。与前一例子不同,收到的报文在语法上是合法的,但因违反语义约束而应当被拒绝。
在各实现上执行时,FRR [12] 与 Batfish [27] 正确地拒绝了该路由,将其视为 AS 环路。相反,GoBGP [13] 接受了该路由,并将其安装进路由表。接受此类路由可能导致不正确的行为以及潜在环路。
2.3 SMTP 嵌套失败(Mailpit)
作为基于状态的约束的例子,SMTP 限制新的邮件事务何时可以开始。一旦 MAIL FROM 命令已被接受,并且已经发出一条或多条 RCPT TO 命令,服务器就期望客户端要么发送 DATA,要么中止该事务。此时再用另一个 MAIL FROM 开始新的邮件事务,被 RFC 5321 明确禁止。
极值测试生成了一个测试用例:在服务器已经接受收件人、但尚未发送 DATA 时,向服务器发出第二条 MAIL FROM 命令。这个例子是极值的,但与上面两个例子不同:该命令在语法上合法,但基于协议的当前状态,它在语义上非法。
我们测试的大多数 SMTP 服务器都以 503 错误拒绝嵌套的 MAIL FROM 命令,或关闭连接。相反,Mailpit [21] 以 250 2.1.0 Ok 作为响应,实际上允许在前一个事务仍然打开时开始新事务。允许嵌套事务可能导致内部状态不一致以及未定义行为。
3 方法论
我们的方法论不仅寻求自动化测试生成,还要把自然语言规范转化为一组结构化的测试目标。从这个意义上说,这条流水线最好被理解为一种把 RFC 散文转化为覆盖对象的方式,使面向边界的测试变得系统、可追溯,并且与具体协议无关。
图 1 给出完整流水线。在其中我们:(1)从规范中抽取有效性约束;(2)生成许多靠近这些约束边缘的极值测试;(3)在多个实现上运行这些测试,并用差分测试检测异常。下面分阶段描述。
3.1 输入
极值测试针对这样的系统:(a)存在书面规范(通常是一份 RFC);(b)存在多个可以测试的独立实现。许多网络协议满足这些条件。
我们的流水线假定用户提供以下输入:
- 规范文档: 描述该协议及其规则的 RFC 或类似文档。
- 输入/输出测试格式: 以 JSON 格式给出的测试用例结构化表示,包括对每个测试输入字段的英文描述,以及一份类似的 JSON 文件,其中包含测试输出字段及其描述。(详见附录 A)
- 测试器目录: 一个目录,其中有主脚本,能够针对每个实现执行 JSON 测试输入格式的测试用例,并以 JSON 测试输出格式输出结果。
3.2 阶段 1:约束生成
在流水线的第一阶段,我们从规范中抽取有效性约束。输出是一组显式约束,稍后可用于系统性的测试生成。
切分规范:
规范往往过长,无法一次送入模型。因此我们把文档切分为节。我们从作为单个文本文档的整份 RFC 开始,然后逐行扫描 RFC,把任何匹配节标题模式
^\s*(\d+(?:\.\d+)*)\.\s+(.+)$的行视为新小节的开始。其中第一个捕获组是节号(例如 3、3.1 或 3.1.2),第二个是标题。
对于节号为 \(k\) 的每个此类标题,我们收集从该标题起、直到(但不包括)下一个标题的全部行,并把得到的文本保存到名为 section_k.txt 的文件中。这里 \(k\) 是把点号替换为下划线后的节号(例如 3.1 变为 section_3_1.txt)。然后我们把这些 section_k.txt 一次一个地送给大语言模型,同时附上用户给出的测试格式,以及从该节文本中抽取全部约束的提示。大语言模型为每一节返回一份约束列表,这些约束被追加到全局约束列表中。
约束定义:
我们把约束定义为规范中任何这样的句子:它限制协议输入可以如何构成、排序或组合——也就是实现被期望强制执行的、可接受输入与不可接受输入之间的边界。具体而言,大语言模型提示(见附录 B)指示模型寻找描述下列内容的句子:语法规则、允许或不允许的取值、长度或大小限制、字符集限制、多个输入之间的关系、可以表示为测试输入的顺序或状态规则,等等。
提示规定,约束一般是使用规范性语言(MUST、MUST NOT、SHOULD、SHOULD NOT)的 RFC 陈述,但也包括清晰描述可测试规则的非规范性句子。关键的是,大语言模型被指示原样返回每条约束句子,与 RFC 中所写完全一致——我们在抽取时不改写、不规范化、也不形式化约束。每条约束被记录为元组 ["<section_number>", "<constraint sentence>"]。这避免引入解释错误,并允许后续阶段把每个测试用例直接追溯到源头。
测试输入格式在这里起关键作用:它被包含在提示中,使大语言模型能够推断测试框架中哪些输入是可控的,从而只选择那些描述这些输入上之约束的 RFC 句子。这自然滤除了那些虽然是有效约束、但无法被测试设置触发的规范规则(另见下文关于测试格式过滤的讨论)。
根据我们的抽取过程,约束通常落入以下类别。这里用从已抽取约束集中取出的具体例子加以说明:
- 范围约束: 对取值的数值界限。例如,SMTP 应答码必须以三位数字代码开头(RFC 5321,§2.4);DNS 把每个标签限制在 1 到 63 个八位组之间(RFC 2181,§11)。
- 大小约束: 字符串、列表、分组或字段的最小或最大尺寸。例如,SMTP 把命令行长度限制为 512 个八位组,包括
<CRLF>(RFC 5321,§4.5.3.1.4);DNS 把完整域名限制为 255 个八位组,包括分隔符(RFC 2181,§11)。
- 格式约束: 输入必须遵循的语法规则。例如,URI 方案名必须由一串字符组成,以字母开头,其后可以是字母、数字、加号、句点或连字符的任意组合(RFC 3986,§3.1);HTTP 首部字段必须遵循名称—值语法(RFC 9110)。
- 依赖约束: 多个输入之间的关系。例如,在 SMTP 中,邮箱的 local-part 必须被当作区分大小写,而动词与关键字则不区分(RFC 5321,§2.4);在 URI 中,百分号编码的八位组必须编码为由 “%” 后接恰好两个十六进制数字组成的字符三元组(RFC 3986,§2.1)。
- 存在性约束: 某个字段或命令在何种条件下是必需的或被禁止的。例如,SMTP 要求客户端在开始邮件事务之前必须发出 HELO 或 EHLO(RFC 5321,§4.1.1.1);TLS 1.3 要求希望进行基于证书的服务器鉴别的客户端必须发送 signature_algorithms 扩展(RFC 8446,§4.2.3)。
- 枚举约束: 必须从固定的允许取值集合中选取的输入。例如,在 TLS 1.3 中,不得提供或协商 MD5、SHA-224 与 DSA(RFC 8446,§4.2.3);DNS 记录类型必须是合法的 RR 类型(RFC 1035)。
- 顺序与状态约束: 关于输入或命令可以按何种顺序出现的规则。例如,SMTP 规定邮件事务命令必须按规定顺序使用(RFC 5321,§3.3)。TLS 1.3 要求协议报文必须按第 4.4.1 节所定义的顺序发送(RFC 8446,§4)。
- 跨字段语义约束: 其有效性取决于跨多个字段或协议状态来解释取值含义的规则,超出简单的语法检查。例如,收到 AS_PATH 中包含与其自身 AS 联盟标识符相匹配的自治系统的 BGP 联盟成员,应当把该路径当作包含其自身 AS 号来处理,即把该路由作为环路予以拒绝(RFC 5065,§4)。这一约束不能仅靠语法检查来强制执行——它要求把 AS_PATH 的内容与路由器自身的配置状态相比较。
交叉引用检测。
我们使用类似的正则表达式,检测每条约束句子内部的交叉引用。具体而言,我们寻找形如 “Section 4.1”、“Sections 4.1 and 4.2” 等的文本引用,从匹配组中提取全部被引用的节号,并在生成测试时用相应的 RFC 章节扩充模型的输入。
把约束映射到测试格式:
RFC 包含许多或许可以被归类为约束、但对我们的测试设置并不相关的陈述。当我们从 RFC 各节抽取约束时,会提供设置摘要,以便只挑选那些可以用我们的测试设置来测试的约束。例如,BGP RFC 4271 中的一条约束说:“KEEPALIVE 报文的发送频率不得高于每秒一条。”若我们的测试设置是在测试决策过程并选择最佳路由,则该约束并不相关。
去重与规范化:
只有当(节号,约束)这一对此前未见过时,新约束才会被加入全局列表。
3.3 阶段 2:测试生成
本阶段的目标,是系统性地构造紧贴每条约束所描述的合法行为与非法行为之边界的测试。
由约束驱动的生成:
每个测试用例都恰好由一条 RFC 约束句子生成。约束连同其 RFC 节号、以及任何被交叉引用章节的文本,被原样传给测试生成器。每个生成的测试都显式记录它意图施加的那条约束。这使得每个测试都可以直接追溯到规范中的某一具体句子。
极值:
对每条约束,我们要求大语言模型生成多个极值测试用例。其中包括:勉强满足该约束的取值(例如允许的最小长度),以及勉强违反该约束的取值(例如超长一个字符)。
对于 \(0 \leq x \leq 255\) 这类数值约束,大语言模型可以生成 \(-1\)、\(0\)、\(1\)、\(254\)、\(255\) 与 \(256\) 等取值。对于 \(\texttt{len}(s) \leq 1024\) 这类大小约束,大语言模型生成长度刚好低于、恰好处于、以及刚好高于该限制的字符串。对于格式与顺序约束,模型被指示生成语法合法但被放在非法位置上的输入,或者与合法输入仅有最小差异的输入。
测试用例示例:
为说明极值测试所生成的测试种类,考虑 RFC 5321 第 2.3.5 节中的 SMTP 约束,其陈述为:“保留邮箱名 postmaster 必须在 RCPT 命令中被接受,且无需域名限定。”
从这一条约束,我们的系统同时生成正向与负向边界测试。正向测试检查:在合法的 EHLO 与 MAIL FROM 序列之后,RCPT TO:<postmaster> 被接受。相反,负向边界测试对输入做轻微修改以违反该约束,例如使用几乎匹配的名称(postmaste),或非法形式(postmaster@)。这些输入只相差一个字符或一处结构细节,但按照 RFC 必须被拒绝。此类近边界情形很难用模糊测试或基于模型的测试生成。
标签生成:
在测试用例之外,大语言模型还为每一条生成一个标签。标签把约束标识与边界指示符(正向或负向)组合起来,例如 C11_Positive 或 C18_Negative。正向表示刚好合法,负向表示刚好非法的测试用例。
约束分批:
约束以小批量处理(默认每批 5 条)。每一批在单独的一次调用中送给大语言模型。分批在实践中是必要的:一次提供过多约束会导致输出重复、覆盖不佳。处理小批量会促使模型聚焦于每条约束,并产生多个彼此不同的极值测试。
提供给大语言模型的上下文:
对每一批,大语言模型获得(提示词见 §B.2):JSON 形式的输入测试用例格式、当前批次的精确约束句子,以及在适用时、由这些约束所引用的任何 RFC 章节的全文。
提供被引用的 RFC 章节,有助于模型正确解释其含义依赖于规范其他部分的约束,例如状态转移或例外情形。
测试格式与标识符:
所有生成的测试都必须精确符合用户提供的输入测试格式。大语言模型被指示按照预定的极值测试用例填写每个字段。这种落地减少了由无关解析错误导致的失败,并提高测试走到预期代码路径的可能性。大语言模型只输出测试对象的 JSON 数组,不附加其他文本。每个测试对象包含一个 constraint 字段,其中是它所针对的精确 RFC 句子。测试标识符在生成之后赋值,以确保跨批次的全局唯一性。
覆盖目标:
测试生成的目标是最大化对 RFC 约束的覆盖,而不是代码覆盖。对每条已抽取的约束,我们旨在生成探索刚好低于、恰好处于、以及刚好高于所规定边界之行为的测试。
总体而言,本阶段以系统且可复现的方式,把自然语言规范规则转化为具体的、面向边界的测试输入,同时保持生成过程简单、可扩展。
3.4 阶段 3:测试执行
生成之后,我们运行用户提供的测试脚本,在每个实现上执行测试,并按指定的输出格式产生结果文件。在我们的实验中,这些脚本为每个协议实现使用 Docker 容器。给定测试用例 \(t\) 与实现 \(I\),测试器启动或复用相应的容器或进程,由 \(t\) 构造具体的输入报文,把它们发送给 \(I\),等待响应,并按预期格式记录输出。
3.5 阶段 4:差分测试
执行完全部测试用例后,我们进行差分分析,以识别各实现行为不同的情形。目标不是判定哪个实现正确,而是把值得更仔细检查的测试用例呈现出来。
差异逻辑:
对每个测试用例,我们用 Python 脚本处理结果文件,比较所有实现的输出,每个输出都是一个 JSON 字典。若至少有两个实现对同一测试用例返回不同的字典,我们就把该测试用例及全部响应加入一个单独的文件,作为检测到的异常。
差分分析的输出:
所有响应不一致的测试用例都被记录到一个单独的文件中。每条记录包括原始测试元数据,以及所有实现的实际响应。这使后续阶段能够检查、分组并推理异常,而不丢失上下文信息。
3.6 阶段 5:结果分析
差分分析可能呈现出大量在各实现之间触发分歧行为的测试用例。其中一些差异并不重要,来自用户配置错误,或规范下多种行为都可谓可接受的情形;另一些则对应真实缺陷或规范违反。我们的流水线使用大语言模型分析这些差异并确定其优先级,从而减少确认与解释所需的人力。
基于大语言模型的差分结果分析:
我们分批处理差分测试的输出,并将其送给大语言模型分析。每一批包含少量测试用例(例如五个),以及实现设置信息和完整的测试元数据。对每个测试用例,大语言模型产生一段简短的文字分析,解释所观察到的分歧,并赋予 0 到 10 之间的置信度分数,表示该情形有多大可能代表真实缺陷或有意义的不一致。
本阶段的输出是已分析测试用例的列表,每一条都附加了大语言模型生成的评论与置信度分数。这些结果被写入一个分析文件,该文件保留全部原始测试信息。
3.7 阶段 6:分诊
在流水线的下一阶段,我们进行分诊,以消除语义上相似的结果,并对结果排序,使人工调查变得容易。
优先级排序与分组:
我们按置信度分数对已分析的测试用例排序,以优先处理那些最可能对应真实问题的用例。由于多个测试用例可能针对同一条规范约束,我们按标签对结果分组(见 §3.3)。在每一组内,测试用例按置信度分数排序。流水线输出全部分组并排序后的测试用例,使用户可以按需要检查任意多少条。实践中,审阅每组中置信度最高的那一条是有效的起点,但完整集合仍可供更深入的调查使用。
人工确认:
在提交给维护者之前,结果要经过人工审阅,以判断某一情形是否代表真实缺陷。大语言模型只用于协助分析、优先级排序与去重。但这条流水线减少了人力,使我们能够迅速从数百项差分异常,推进到一小套可管理的、高质量的、可行动的缺陷报告。
3.8 小结
极值测试的核心,是把基于规范的测试生成做两阶段分解:
- 约束抽取: 把自然语言规范规则转化为显式的、与测试格式对齐的约束。
- 极值测试生成: 对每条约束,系统性地在所规定边界之上及其周围生成测试。
这一分解赋予流水线两个测试生成通常缺乏的性质。第一,一种完备性:通过逐节抽取约束,并跟踪哪些约束已经生成了测试,我们可以度量并改进对规范的覆盖。第二,可追溯性:每个生成的测试都记录它所针对的精确约束句子与 RFC 节号,因此任何差分异常都可以直接追溯到规范文本。
4 评估
本节在多个真实协议与库上评估极值测试。评估旨在回答三个问题。
Q1. 极值测试能否发现真实的、此前未知的协议缺陷?
Q2. 这些缺陷在性质上是否不同于现有模糊测试器已经暴露的一般解析器失败?
Q3. 结构化分解——逐节处理、约束抽取、引用展开——是否必要,还是一次性的大语言模型调用就已足够?
实验设置在 §4.1 中描述。随后 §4.2 的结果回答前两个问题;§4.3 的消融研究回答第三个问题。
4.1 实验设置
模型: 除非另有说明,流水线中所有基于提示的阶段——约束抽取、测试生成与异常分析——都通过 OpenAI API 使用 GPT-5。完整提示词见附录 B。我们在下列实现上评估了极值测试(细节见附录 C)。
- HTTP 服务器: Nginx [62]、Apache [28]、Lighttpd [36]、Caddy [33]、H2O [51]。
- DNS 服务器: BIND [15]、NSD [37]、Knot [16]、PowerDNS [14]、CoreDNS [11]、GDNSD [6]、Technitium [68]、HickoryDNS [30]、TwistedNames [39]、Yadifa [25]。
- BGP 实现: FRR [12]、GoBGP [13]、Batfish [27]。
- SMTP 服务器: AioSMTPD [1]、MailPit [21]、Stalwart [38]、SMTPD [20]、OpenSMTPD [22]。
- QUIC 服务器: quiche [10]、msquic [45]、quic-go [54]、ngtcp2 [65]、mvfst [44]、kwik [24]、picoquic [34]、aioquic [40]、neqo [48]、nginx [49]、chrome [53]、lsquic [42]、haproxy [63]、quinn [55]、go-x-net [64]。
所有实现都在 Docker 容器内运行,以确保可复现性。
对于 HTTP,我们测试 RFC 3986 中的 URI 解析规则。每个测试指定一个带有 scheme、authority、path、query 与 fragment 组件的 URI,以及包含目录与符号链接的文件系统布局;我们比较各服务器返回的 HTTP 状态码与解析得到的文件路径。对于 DNS,我们测试 RFC 2181 中的资源记录集语义,包括重复抑制、TTL 一致性与缓存优先级。每个测试提供一个区域文件以及一条或多条查询,我们比较所有解析器的结构化响应,包括应答记录与返回码。对于 SMTP,我们测试 RFC 5321 中的命令语法与顺序规则。每个测试指定一个 SMTP 命令序列(例如 EHLO、MAIL FROM、RCPT TO)以及服务器状态,我们比较各服务器对最后一条命令返回的三位响应码。对于 BGP,我们测试联盟处理(RFC 5065)与路由反射(RFC 4456)。每个测试指定 AS 拓扑、联盟成员关系以及通告的路由,我们比较每个实现是否把该路由安装进其 RIB,以及每个路由器上得到的 AS 路径。对于 QUIC,我们测试 RFC 8446 中的 TLS 握手。每个测试指定对一条原本合法的 ClientHello 报文的修改,我们使用 QuicInteropRunner [57] 以及一个修改过的 aioquic [40] 客户端,比较握手在每个实现上是成功还是失败。
我们把差分异常定义为这样的测试用例:至少有一个实现以有意义的方式与其他实现行为不同——例如崩溃、不同的错误码(例如 4xx 对 5xx),或对同一输入的接受与拒绝。
对每个协议,我们运行了 §3 所述的完整 CornerCase 流水线。作为其输出的一部分,该工具从规范中抽取输入约束,生成面向边界的测试用例,在多个实现上执行它们,识别差分异常,并应用大语言模型辅助分析,根据规范违反的可能性为每项异常打分。该工具还按异常所施加的约束对其分组,并在每组中选取置信度最高的实例,以减少冗余(§3.7)。从这一排序后的输出中,我们为每个协议使用一个置信度阈值(一般为 8;当异常总数较少时降低该阈值),以选择供人工检查的候选,并对通过这一人工确认步骤的异常提交缺陷报告。在人工检查期间,一些候选被丢弃,因为它们对应实现的设计选择、可配置行为,或 RFC 并未严格规定单一行为的情形。此外,相关异常常常被归并为一份报告。这些情形不计入所报告缺陷的总数。
4.2 差分异常
表 1 汇总了每个协议所抽取的唯一约束数、生成的极值测试数、发现的差分异常数,以及经过基于置信度的优先级排序与分诊之后的候选数。每条约束产生 3 到 11 个测试用例,包括处于边界上、刚好低于边界、以及刚好高于边界的取值。分诊阶段之后的候选在交给开发者之前要经过人工检查。
表 1: 每个协议的唯一约束、极值测试、异常(实现之间的分歧)、优先候选(高于置信度阈值)以及经大语言模型按标签分诊后的候选。
| 协议 | RFC | 约束数 | 测试数 | 异常数 | 优先候选 | 分诊后 |
|---|---|---|---|---|---|---|
| SMTP | 5321 | 177 | 630 | 526 | 84 | 54 |
| DNS | 2181 | 65 | 213 | 156 | 28 | 23 |
| BGP(联盟) | 5065 | 28 | 98 | 64 | 25 | 20 |
| HTTP | 3986 | 239 | 757 | 297 | 75 | 42 |
| QUIC | 8446 | 136 | 328 | 16 | 6 | 5 |
表 2 汇总了跨五个协议发现的 42 个缺陷(经过人工检查)。其中 26 个已被确认,18 个已由维护者修复,其余已经提交并正在等待回复。人工检查确认,这些异常并非仅仅是实现差异:它们在广泛使用的协议栈中暴露了数十个具体缺陷与规范违反,包括 HTTP 主机校验与 BGP 环路检测中与安全相关的问题(表 2)。
有几项发现因其影响和在协议中的关键性而尤为突出。在 BGP 联盟中,我们发现 GoBGP 接受带有 AS 环路的路由,并且还与非法的对等关系建立会话,这可能影响多 AS 部署中的路由安全与策略隔离;其中若干已被确认或修复。在 HTTP 中,多个服务器接受畸形或缺失的 Host 取值,却仍然提供内容,违反了 RFC 的主机校验规则,并造成请求路由与虚拟主机方面的安全风险。在 QUIC 中,围绕 TLS 1.2/1.3 组合的版本协商行为暴露了互操作性与降级面问题;而在 SMTP 中,我们观察到状态机与语法校验错误(例如在非法的命令序列中接受 MAIL FROM),这可能导致各实现之间的邮件处理不一致。这些例子共同表明,极值差分测试不仅能暴露解析器边界情形,也能暴露安全与正确性关键协议逻辑中的高价值语义缺陷。
缺陷模式:
这些缺陷落入三大类。最常见的是缺失校验:实现接受了规范要求必须拒绝的语法非法输入——例如 aiosmtpd 接受没有尖括号的 MAIL FROM,或 Caddy 为带有畸形或缺失 Host 首部的请求提供文件。第二类是不正确的状态机转移:命令在错误的顺序上被接受。Mailpit 在任何 EHLO 问候之前就接受 MAIL FROM,并允许在活动事务期间出现嵌套的 MAIL FROM。第三类是语义误读:实现错误地应用了一条规范规则。例如,FRR 把出现在常规(非联盟)AS_PATH 中的成员 AS 当作联盟环路,从而错误地拒绝了合法路由。
协议趋势:
BGP 联盟缺陷涉及路由逻辑中的语义错误,尤其是环路检测与会话处理。HTTP 缺陷常常与安全相关,畸形的 Host 首部被接受。SMTP 问题源于对语法与命令顺序规则的强制执行薄弱。DNS 表现出在处理边界语义与畸形输入时的健壮性问题。QUIC 则揭示了 TLS 版本协商中的不一致。
表 2: 由极值测试发现的缺陷(共发现 42 个缺陷,26 个已确认,18 个已修复)。每一行是由源自某条 RFC 约束的极值测试所触发的一个不同的实现级问题。已修复:已由维护者打补丁。已确认:已承认但尚未修复。已发现:已报告,正在由开发者调查。缺陷涵盖我们所评估的全部五个协议中的缺失校验、状态机违反与语义误读。
| 协议 | 实现 | 缺陷描述(简述) | 状态 |
|---|---|---|---|
| SMTP | Mailpit | 在任何 HELO/EHLO 之前接受 MAIL FROM,返回 250,而不是将该命令作为错误序列予以拒绝。 | 已修复 |
| SMTP | aiosmtpd | 接受缺少尖括号、语法非法的 MAIL FROM 命令,并返回 250,而不是以 501 错误拒绝它。 | 已发现 |
| SMTP | aiosmtpd | 接受非法收件人语法 RCPT TO:Postmaster(缺少尖括号),并返回 250,而不是拒绝它。 | 已发现 |
| SMTP | Mailpit | 接受包含畸形源路由(缺少必需冒号)的非法 RCPT TO 地址,返回 250,而不是拒绝该语法错误。 | 已修复 |
| SMTP | OpenSMTPD | 对 MAIL FROM 之后发出的第二次 EHLO 以 503 错误拒绝,尽管 RFC 5321 要求它重置事务状态。 | 已修复 |
| SMTP | Mailpit | 在活动事务期间(RCPT TO 之后、DATA 之前)接受嵌套的 MAIL FROM 命令,返回 250,而不是拒绝它。 | 已修复 |
| SMTP | OpenSMTPD | 在 EHLO 之后接受 MAIL FROM 中未限定的本地别名(例如 <sales>),返回 250,而不是拒绝它。 | 已确认 |
| SMTP | Stalwart | 在 5 次 RCPT TO 被拒绝后断开连接。 | 已确认 |
| DNS | Technitium | 对顶点 NS 查询返回非法的 NS 目标。 | 已确认 |
| DNS | Bind | DNS 服务器返回不完整的 TXT 资源记录集,且未设置 TC 位。 | 已发现 |
| DNS | NSD | DNS 服务器返回不完整的 TXT 资源记录集,且未设置 TC 位。 | 已发现 |
| DNS | Twisted | DNS 服务器返回不完整的 TXT 资源记录集,且未设置 TC 位。 | 已发现 |
| DNS | GDNSD | 对重复且相同的 TXT 资源记录设置 TC,而不是在资源记录集中抑制重复项。 | 已修复 |
| DNS | Yadifa | KEY 记录类型(RR 类型 25)导致整个区域加载失败。 | 已发现 |
| DNS | Technitium | KEY 记录类型(RR 类型 25)导致整个区域加载失败。 | 已发现 |
| DNS | CoreDNS | CoreDNS 丢弃所有者名称中带有 \DDD 十进制转义的区域记录。 | 已修复 |
| DNS | Yadifa | 区域文件解析器把 \DDD 十进制转义的点号当作标签分隔符,而不是标签内部的字节。 | 已发现 |
| DNS | HickoryDNS | 对委托之下的名称返回权威应答(忽略了区域切割)。 | 已确认 |
| DNS | Twisted Names | 对委托之下的名称返回权威应答(忽略了区域切割)。 | 已修复 |
| BGP(联盟) | GoBGP | 接受 AS_PATH 中包含其自身联盟标识符/本地 AS 的路由(未拒绝 AS 环路)。 | 已修复 |
| BGP(联盟) | FRR | 当自身成员 AS 出现在常规(非联盟)AS_PATH 中时,错误地拒绝路由,将其视为联盟环路。 | 已发现 |
| BGP(联盟) | GoBGP | 在 eBGP 会话上错误地使用成员 AS 而非联盟标识符进行 AS 环路检测。 | 已确认 |
| BGP(联盟) | GoBGP | 错误地在同一成员 AS 内的对等体之间建立外部会话并传播路由。 | 已确认 |
| BGP(联盟) | Batfish | 在 BGP RIB 中不区分 AS_CONFED_SEQUENCE 与 AS_SEQUENCE。 | 已修复 |
| BGP(联盟) | GoBGP | 接受来自非成员对等体、且具有相同联盟标识符的 EBGP 会话,并安装路由。 | 已发现 |
| BGP(联盟) | GoBGP | 错误地接受与非成员对等体的联盟内部会话,并安装路由。 | 已发现 |
| BGP(联盟) | FRR | 远端 AS 配置为外部的联盟外部对等体把路由重新通告回发送方。 | 已修复 |
| BGP(联盟) | Batfish | 同一联盟成员 AS 内的 iBGP 路由未传播给对等体。 | 已发现 |
| BGP(联盟) | FRR | 拒绝来自其 AS 等于联盟标识符的 eBGP 对等体的路由。 | 已修复 |
| BGP(联盟) | FRR | 未跨联盟间 eBGP 会话把路由从 R2 传播到 R3。 | 已确认 |
| BGP(联盟) | FRR | 向外部对等体通告时,在 AS_PATH 中泄漏联盟成员 AS 号。 | 已修复 |
| BGP(联盟) | Batfish | remove-private-as all replace-as 未被解析——私有 AS 泄漏给外部对等体。 | 已发现 |
| BGP(联盟) | Batfish | 当外部对等体的 AS 与某个联盟成员 AS 相匹配时,错误地建立 BGP 会话。 | 已修复 |
| HTTP | Caddy | Host 首部存在但为空时仍提供文件,而不是返回 400 Bad Request。 | 已修复 |
| HTTP | Caddy | Host 首部中的非法 IP 字面量仍提供文件,而不是返回 400。(多种示例) | 已修复 |
| HTTP | Caddy | 请求路径中的 %00 返回 500 内部服务器错误。 | 已修复 |
| HTTP | H2O | 请求路径中的 %00 导致 301 重定向到同一路径。 | 已修复 |
| HTTP | Nginx | Host 首部中的非法 IP 字面量仍提供文件,而不是返回 400。(多种示例) | 已确认 |
| HTTP | H2O | Host 首部完全缺失、或存在但为空时仍提供文件,而不是返回 400 Bad Request。 | 已发现 |
| HTTP | H2O | Host 首部中的非法取值仍提供文件,而不是返回 400。(多种示例) | 已发现 |
| QUIC | kwik | 客户端在所支持的版本中只提供已过时的 TLS 1.2 时,握手仍然通过,而不是失败。 | 已修复 |
| QUIC | quic-go | 服务器拒绝这样的握手:所支持的版本中在提供合法 TLS 1.3 的同时还提供已过时的 TLS 1.2。 | 已发现 |
4.3 消融研究
我们进行消融研究,以识别流水线的哪些部分负责把大语言模型的原始能力转化为系统性的测试能力。主要结果是:每一层结构都有作用,去掉其中任何一层都会实质性地降低异常产出(表 3)。
我们的系统包含三个关键设计选择:(1)逐节处理 RFC(S);(2)在生成测试之前从规范中抽取显式约束(C);(3)当正文引用其他 RFC 章节时纳入这些被引用章节(R)。我们评估这些组成部分各自如何影响所生成测试的数量与有用性。
我们比较流水线的若干变体:
- Base: 整份 RFC 在一条提示中交给大语言模型,模型直接生成测试用例,没有逐节处理、约束抽取或引用展开。
- S: RFC 被逐节送给大语言模型,但模型直接生成测试用例,而不先抽取约束。
- SC: RFC 被逐节处理,从每一节抽取约束,然后根据收集到的约束生成测试用例。
- SR: RFC 被逐节处理,并在被提及时纳入所引用的章节,但测试用例直接生成,没有显式的约束抽取步骤。
- CR: 整份 RFC 一次提供,但系统在生成测试之前抽取约束,并在必要时纳入所引用的章节。
- SCR(完整系统): 我们的完整流水线,逐节处理 RFC,抽取约束,并纳入所引用的章节。
对每个变体,我们生成测试,并在 §4.1 所列的全部实现上运行它们。我们记录生成的测试数量,以及跨实现发现的差分异常数量。对于执行约束抽取的变体,我们还报告所抽取的约束数量。五个协议——DNS、BGP、SMTP、HTTP 与 QUIC——上的消融结果汇总于表 3。
结果表明,完整流水线(SCR)在所有协议上都稳定地产生最多的异常。每个组成部分都有实质贡献:仅逐节处理(S)在 HTTP 上产生的异常就约为 Base 的 10 倍(13→131),在 BGP 上约为 22 倍(2→45)。加入约束抽取会进一步成倍增加异常——对 SMTP,SCR 发现 526 项,而单独的 S 为 124 项。引用展开(R)改善了 DNS 的结果:SCR 发现 156 项异常,而 SC 为 100 项,这大概是因为某些 DNS 约束只有通过交叉引用的章节才被完整规定。
消融表明,核心挑战不是让大语言模型产生测试,而是让它系统地覆盖规范。整份 RFC 提示(Base)对于边界发现来说过于弥散。逐节处理(S)把推理局部化到一个可处理的单元,从而恢复聚焦。约束抽取(C)把 RFC 从非结构化散文转化为一组显式、可枚举的测试目标,使测试生成成为覆盖问题。当一条约束的含义确实依赖于另一节时,引用展开(R)使这一集合更为精确。这条流水线之所以有效,不是因为它使用了更多提示,而是因为每一层都给模型的推理施加了额外结构。
QUIC 这一例外: 在 QUIC 上,SC(20 项异常)略高于 SCR(16 项)。我们不把这读作矛盾,而是读作整张表中可见的一种一般性权衡的证据:额外上下文只有在它使某条约束更为精确时才有帮助;当它拓宽提示、却不增加可直接测试的结构时,则可能有害。RFC 8446 中的交叉引用大量是散文性评注,并不能转化为我们测试格式中新的可控输入,因此引用展开稀释了提示,却没有增加覆盖。这与本文更大的主题一致:额外的上下文信息若要不削弱聚焦,就必须密集地包含与约束相关、并能丰富约束的信息。
表 3: 对流水线三个结构组成部分的消融:逐节 RFC 处理(S)、显式约束抽取(C),以及有针对性的引用展开(R)。各列报告所抽取的约束(约束数;变体不执行抽取时记为 “X”)、生成的测试(测试数)以及差分异常(异常数)。完整的 SCR 流水线在五个协议中的四个上给出最高异常数,产生的异常最多可达一次性 Base 的 22 倍。
| 协议 | 变体 | 约束数 | 测试数 | 异常数 |
|---|---|---|---|---|
| SMTP | Base | X | 25 | 20 |
| SMTP | S | X | 234 | 124 |
| SMTP | SC | 154 | 613 | 504 |
| SMTP | SR | X | 284 | 168 |
| SMTP | CR | 41 | 169 | 95 |
| SMTP | SCR | 177 | 630 | 526 |
| DNS | Base | X | 20 | 11 |
| DNS | S | X | 168 | 71 |
| DNS | SC | 63 | 209 | 100 |
| DNS | SR | X | 162 | 86 |
| DNS | CR | 27 | 96 | 66 |
| DNS | SCR | 65 | 213 | 156 |
| BGP(联盟) | Base | X | 12 | 2 |
| BGP(联盟) | S | X | 68 | 45 |
| BGP(联盟) | SC | 23 | 85 | 49 |
| BGP(联盟) | SR | X | 64 | 33 |
| BGP(联盟) | CR | 20 | 78 | 25 |
| BGP(联盟) | SCR | 28 | 98 | 64 |
| HTTP | Base | X | 27 | 13 |
| HTTP | S | X | 437 | 131 |
| HTTP | SC | 165 | 660 | 207 |
| HTTP | SR | X | 430 | 139 |
| HTTP | CR | 28 | 124 | 47 |
| HTTP | SCR | 239 | 757 | 297 |
| QUIC | Base | X | 20 | 0 |
| QUIC | S | X | 230 | 8 |
| QUIC | SC | 168 | 344 | 20 |
| QUIC | SR | X | 336 | 10 |
| QUIC | CR | 21 | 52 | 1 |
| QUIC | SCR | 136 | 328 | 16 |
4.4 经验教训
我们的经验给出三条实证结论:
- 分解优于直接生成: 当被要求从规范的一个局部片段中抽取显式约束时,大语言模型比被要求根据整份 RFC 生成端到端测试要可靠得多。逐节处理与“先约束、后生成”不是实现细节;它们是把大语言模型能力转化为系统性测试覆盖的机制(表 3:异常产出相对一次性生成最多可达 22 倍)。
- 瓶颈从生成转移到理解: 有了自动化测试生成,主要成本变为解释差分异常并对其去重。因此,异常排序、打标签与分诊成为方法论的一等组成部分,而不是可选的工程便利。对 SMTP,我们把 526 项原始异常减少到 84 个优先候选和 54 个分诊类别,使人工检查减少了一个数量级(表 1)。
- 一个很小的接口层就足以跨协议推广: 唯一与协议相关的输入,是暴露相关可控输入与输出的测试格式和执行驾驭程序。实践中,这一接口足以把同一条流水线带到 HTTP、DNS、BGP、SMTP 与 QUIC 上,而无需重新训练或重新设计核心方法。现代代码生成工具(例如 GitHub Copilot)可以根据规范本身引导出这一接口的很大一部分,从而进一步降低每个协议的成本。
5 相关工作
相关工作可以按每种方法依赖什么来生成测试或漏洞来分类:实现(模糊测试、符号执行、基于大语言模型的单元测试、基于人工智能的漏洞分析)、过去的输出(基于人工智能的漏洞分析)、形式模型(基于模型的测试),或自然语言规范本身。极值测试属于第四类。
边界值分析(BVA):
边界值分析 [5, 58, 56, 69] 通常只关注范围约束。极值测试要一般得多;例如,它包括针对报文的基于状态的约束。通常,边界值分析并不使用大语言模型来生成边界条件。一个近期的例外 [32] 使用大语言模型直接从代码生成边界值测试,而我们是从规范生成。他们的评估只针对几百行的代码,远小于我们所处理的大型代码库。
自动化测试:
模糊测试被广泛用于一般的软件测试 [2, 71, 52, 7, 41],也被专门用于 BGP 与 DNS 实现(例如 [17, 23, 29, 50, 61])。虽然模糊测试器在发现解析器缺陷方面有效,随机输入只有很小的概率能发现极值缺陷。符号执行使用 SMT 求解器,为一个程序的许多执行路径生成测试用例(例如 [9, 31])。协议实现包含成千上万行代码,使符号执行不可行。与符号执行不同,极值测试可以作用于源代码不可得的软件。基于模型的测试使用系统的抽象模型来生成测试 [8]。它已被用于 DNS [35] 与 BGP [59],但生成的是合法输入,而不是极值输入。Eywa [47] 使用大语言模型生成模型,而不是测试。
由大语言模型驱动的测试生成:
基于大语言模型的软件测试是一个广阔领域 [66],它是基于大语言模型的软件工程 [26] 的一个子集。然而,现有工作 [66] 使用大语言模型来提高覆盖率,或改进基于变异/模糊的测试,而不是用于极值测试。我们的框架在多个实现上生成端到端测试,而不是为单个实现生成单元测试。
基于人工智能的漏洞分析:
近期工作把大语言模型用于安全,包括自动化渗透测试 [19] 与代码级漏洞检测 [70]。LAPRAD [4] 先在先前的 DNS 攻击上训练大语言模型,再构造新的攻击;与之相反,我们只基于 RFC 生成测试。
6 局限与未来工作
极值测试目前有以下局限:
隐式约束: 定义 Heartbleed [46] 利用的约束(载荷长度与长度字段不匹配),以及圣诞树数据包 [18] 的约束(SYN、FIN 与 RST 标志不应全部置位),可以称为隐式约束:它们并未在 RFC 中显式陈述,但被强烈暗示。未来工作或许可以通过一种新的提示策略,要求大语言模型生成隐式约束。
短交互: 我们目前聚焦于生成涉及单条报文或短命令序列的极值测试。扩展到涉及长的多步工作流、时序依赖或复杂状态机的协议行为,是有意思的未来工作。
单一 RFC: 约束抽取目前基于单份 RFC。协议行为可能依赖于相互引用的多份 RFC。例如,RFC 9113(HTTP/2)使用 RFC 9110(HTTP 语义),而后者又引用 RFC 3986(URI 语法)。
多种模型: 不同的大语言模型在子任务上的表现可能不同(例如约束识别、分诊),我们聚焦于在很大程度上与模型无关的流水线级设计选择。绝对性能可以通过选择不同模型而提高,但我们预期总体趋势保持一致。
7 结论
极值测试生成出现在规范边界附近的测试。我们的目标是加固广泛使用的互联网协议实现,使其能够抵御带有非预期字段的报文,或在非预期状态下收到的合法报文;这些报文可能降低可靠性,并在极端情况下暴露危险的漏洞。
在更高层面上,本文的主要教训是:大语言模型在这里最有用的方式,不是作为端到端的测试生成器,而是把自然语言规范转化为显式的测试目标。我们两次使用大语言模型:首先从协议规范中抽取约束,然后才系统性地在这些约束的边界上或边界附近生成测试用例。我们的方法与具体协议无关,允许用户以 JSON 指定自己的测试输入与输出格式。
我们把极值测试应用于 HTTP、DNS、BGP、SMTP 以及 QUIC 握手,发现了 42 个缺陷与不一致。其中 26 个已被确认,18 个已由维护者修复。缺陷涵盖三大类:缺失输入校验(例如 HTTP 服务器接受畸形的 Host 首部)、状态机违反(例如 SMTP 服务器按错误顺序接受命令),以及语义误读(例如 GoBGP 未能拒绝联盟路径中的 AS 环路)。我们的消融研究确认,两阶段分解——先抽取约束、再生成极值测试,并逐节应用、辅以交叉引用展开——是必要的,其产生的异常最多可达朴素的一次性大语言模型生成的 22 倍。
极值测试专注于识别由违反 RFC 中约束所导致的错误,这使它与现有的协议实现可靠性方法形成互补,包括模糊测试、符号执行与基于模型的测试。它们共同构成一套有力的技术组合,可以使互联网协议实现更加健壮、更加安全。本工作部分得到美国国家科学基金会资助 CNS-2402958 的支持。
署名与许可
本文译自 Rathin Singha、Kuan Qian、Srinath Saikrishnan、Tracy Zhao、Soheil Abbasloo、Ryan Beckett、Siva Kesava Reddy Kakarla、Todd Millstein、George Varghese 的论文 CornerCase: Automated Extremal Testing of Protocol Implementations。原文链接:https://arxiv.org/abs/2606.29124 。原文以 CC BY 4.0 许可发布。译者:智测团队。
Found it useful? Pass it on
Scan with WeChat to open it on your phone and forward it.