漏洞

Libreswan CVE-2026-12413——IKEv2 分片重组中的满表断言

CVE-2026-12413 让未完成身份认证的 IKEv2 对端在受保护的分片消息中恰好占满 30 个载荷描述符,Libreswan 4.6 至 5.3 随后把合法计数误判为内部不变量破坏并主动终止 pluto ,远程攻击者可据此反复中断 VPN 控制面,5.3.1 已修正这项边界判断。

暖纸手绘的 IKEv2 重组台:密封分片包裹填满每个 digest 槽位,旧终点挡住最后一个有效包裹,修复后的料架则接纳完整的受保护集合。
文章导航

研究依据固定复核 Libreswan 5.3.1、4.5/4.6 历史边界、修复提交 5c4369af / 6088d294 与上游 KVM 回归

来源Libreswan 公告、固定源码与历史提交 / RFC 7296、RFC 7383 / SOSEC 源码复核

1 第 30 格填满后,VPN 进程退出

六月末的一行补丁看起来小得容易被略过:< 后面多了一个等号。它没有调整数组长度,没有更换内存分配器,也没有重写 IKEv2 状态机。可这一个字符决定了一台 VPN 网关面对合法满容量状态时,是继续处理协议错误并保持服务,还是在网络输入到达后调用 abort()。CVE-2026-12413 的全部故事,都藏在“30 是合法数量,还是非法下标”这个问题里。

受影响的是 Libreswan 的 IKE daemon pluto。攻击者先完成 IKE_SA_INIT,获得本次 IKE SA 的加密与完整性密钥,再发送采用 RFC 7383 分片的 IKE_AUTH 消息。第一片可以携带一串外层载荷,末尾才是 Encrypted and Authenticated Fragment,也就是 SKF。只要外层解析结束时 msg_digest.digest_roof 恰好等于 30,旧版重组函数就会命中失败断言。

这一刻并没有第 31 次数组写入。下标 0 至 29 已经保存 30 份载荷描述符,digest_roof 指向末尾后一位置。重组函数从现有 SKF chain 取回已经分配的描述符,在原槽位上把它改写成 SK。旧断言却要求 roof 严格小于容量,相当于拒绝了“数组刚好装满”这个状态。Libreswan 的 passert 是致命断言,失败路径最终进入 C 库 abort()

服务管理器可能在数秒内重新拉起 pluto,但恢复并不等于风险消失。进程中的 IKE SA 管理状态、正在认证的对端、重协商和 DPD 调度都会中断;内核里尚存的 XFRM state 可能让部分旧流量短暂继续,使监控产生一种“隧道还通”的错觉。攻击者在 UDP 500 或 UDP 4500 恢复监听后再次触发,就能把控制面维持在退出、启动、重新协商的循环里。

Libreswan 公告将 4.6 至 5.3 列为受影响版本,5.3.1 是上游首个固定版本。CNA 给出的 CVSS 3.1 向量为 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H,基础分 7.5。这个评分准确捕捉了它的边界:网络可达、条件稳定、不要求有效账户,公开源码证据指向可用性破坏;重组路径没有越界写入,也没有形成远程代码执行原语。

代码与处置可以先收束在这里:固定树中,programs/pluto/demux.hPAYLIMITmsg_digest.digest[] 定义容量,错误比较位于 programs/pluto/ikev2_message.creassemble_v2_incoming_fragments()。4.6 至 5.3 需要升级到 5.3.1 或等效回移,并重启到新二进制。只有确认所有 IKEv2 connection 都不需要 RFC 7383 时,才可临时设置 fragmentation=no;ACL 与限速只能缩小触发机会,不能替代永久修复。

1.1 攻击发生在身份认证完成之前

“受保护的 IKE_AUTH”很容易被读成“经过身份认证的消息”,两者在 IKEv2 中是两个时间点。RFC 7296 的 IKE_SA_INIT 交换协商算法,交换 nonce 与 Diffie–Hellman 公钥,并由双方推导 SK_dSK_aiSK_arSK_eiSK_er。只要攻击者按协议完成这一步,就掌握自己作为 initiator 应持有的加密与完整性密钥。

身份、证书、EAP 或预共享密钥证明进入后续 IKE_AUTH。于是,一个尚未被网关认可的人已经能够生成通过 ICV 校验的 SK 或 SKF。这个顺序是协议建立保密认证通道的基础,也让 IKE_AUTH 解密、分片收集和载荷解析天然位于身份信任边界之前。网关不能以“包有正确认证标签”为理由把发送者视为已授权用户。

触发者仍需维护一条结构正确的 IKE SA,使用协商出的密码套件,为每片设置匹配的 SPI、message ID、exchange type、fragment number、fragment total、IV、padding 与完整性标签。这些工作可以由修改后的 IKE 实现自动完成,复杂度不会构成高门槛。它不需要窃取现有用户密钥,也不需要让证书校验通过。

因此,暴露面应从“哪些连接接受 IKEv2 和 fragmentation”开始盘点,不能从“哪些用户拥有 VPN 凭据”开始。公网远程接入、站点到站点集中器、云边缘节点和合作伙伴入口只要允许匿名发起 IKE_SA_INIT,便会到达这条预认证解析链。源地址 ACL、上游 DDoS 过滤与速率限制可以降低触发频率,版本修复仍是关闭代码缺陷的唯一长期措施。

1.2 一次退出怎样扩散成业务中断

pluto 是协调 IKE 协商、Child SA 建立、重密钥、失活检测和内核策略安装的用户态核心。断言终止的是整个 daemon,不是单个恶意 exchange。已有 ESP 数据面在不同部署中可能继续一段时间,新连接、到期重密钥与策略更新则立即失去控制者。业务影响会沿各条 SA 的剩余寿命逐步展开,看起来像一场从边缘向内扩散的故障。

自动重启会创建第二层压力。大量 peer 同时重新认证,证书验证、DNS、LDAP、RADIUS、PKCS#11 和硬件安全模块负载随之上升;HA 节点接管时还要承受状态不同步与流量回灌。连续触发可能撞上 systemd 的启动频率限制,原本几秒的重启最终变成需要人工介入的 failed unit。

诊断时,端口探测与单条长期 ESP 流量都不够。应同时观察 service restart counter、pluto PID、IKE_SA_INIT/IKE_AUTH 成功率、Child SA 数量、重密钥延迟、认证后端请求量和 HA 角色切换。只有把进程、协议与业务三个时间轴叠在一起,才能看清一次 abort() 对网关群的实际影响。

这也是本报告把它按高危可用性漏洞处理的原因。它没有公开的持久化、提权或数据读取链路,却位于组织外部最重要的加密入口之一;无需有效身份的对端可以重复到达;服务管理器提供的自动恢复会被同一输入再次打断。修复窗口应由网关可达性和承载业务决定,不能只依据单次停顿时长。

IKEv2 信任边界图:IKE_SA_INIT 产生保护密钥,尚未认证的 initiator 随后发送通过完整性校验的分片 IKE_AUTH,消息进入载荷解析、收片与重组并触发满表断言。
移动端可横向滑动查看密钥建立、身份认证与重组的先后关系
图 1:完整性验证证明消息来自本次 IKE SA 的发起者,身份授权要到 IKE_AUTH 内部才完成。漏洞路径恰好位于这两个信任阶段之间。

2 第一片为什么能把载荷表推到边缘

理解触发条件,先要把两种“分片”分开。普通 IP fragmentation 由网络层拆包,任何一片丢失都会使整份 IP 数据报失效,中间设备也经常丢弃碎片。RFC 7383 把拆分移到 IKE 层:发送者先拥有 IKE SA 的密钥,再把一份加密消息分成多个独立受到认证的 SKF payload。每片都是完整 IKE 消息,可以单独校验并按序号收集。

这种设计主要服务于证书链庞大的 IKE_AUTH。IKE_SA_INIT 本身不能使用 RFC 7383,因为此时加密与完整性密钥尚未建立。双方在初始交换中用 IKEV2_FRAGMENTATION_SUPPORTED 通知能力,随后才可将较长的受保护请求或响应拆片。Libreswan 配置项 fragmentation=yes 默认启用这项能力。

SKF 是外层载荷链的最后一项。第一片的 SKF header 中,Next Payload 指向重组后明文的首个内部 payload;后续片的该字段必须为 NONE。RFC 7383 还保留一个少见但重要的布局:第一片可以在 SKF 前携带未加密的外层 payload,后续片不得重复这些外层元素。接收者因而需要保留第一片的完整 msg_digest,再把其他片的明文拼回它。

载荷链由每个通用头中的 Next Payload 字段串起来。解析器从 IKE header 给出的首个类型出发,读取当前头、长度与 critical bit,再移动到下一项,直到 NONE。攻击者无需让每个外层载荷承担复杂业务语义;只要它们符合通用头和长度规则,就能推动解析状态。主开发线的回归利用 unknown noncritical 类型,维护线则必须考虑其不同的计数实现。

2.1 RFC 7383 给第一片留下的特殊结构

一组合法片段至少包含片号与总片数。片号从 1 开始,总数对该组保持一致;第一片的 inner Next Payload 不得为 NONE,第二片及以后必须为 NONE。发送者用同一 exchange type、message ID 与 IKE SA,接收者按 initiator/responder role 将它们放入对应 fragment set。所有片都通过完整性验证后,才拼接加密容器中的明文。

Libreswan 将最大片数设为 32,并为索引 1 至 32 分配 MAX_IKE_FRAGMENTS + 1 个元素,下标 0 明确留空。ignore_v2_incoming_fragment() 会拒绝片号 0、片号大于总数、总数超过 32、首片缺少 inner next payload、后续片携带非零 inner next payload以及与既有集合不一致的 total。重复片也不会被当作新成员计数。

这些约束说明触发不依赖畸形片号或数组越界。攻击者可以使用两片或更多结构完整的 SKF,每片都位于允许范围,ICV 正确,总长度也在 UDP 输入上限内。缺陷只在全部片到齐、进入重组且第一片已经让全局 payload digest 表处于满容量时显现。

“外层未加密”不代表可以在网络上随意篡改。它位于整个 IKE 消息的认证范围,修改 header、payload bytes 或链条都会让完整性校验失败。攻击者是本次 IKE SA 的合法协议发起者,拥有自己方向的 SK_ai,因此能够选择并认证这条外层链。这再次把入口定位在预身份、已建密钥的阶段。

2.2 unknown payload 在两条源码线上并非同一种记账

上游主开发线曾在提交 6824359f416a1eba0fabc0f19d5fe9267338bf3b 中调整未知 IKEv2 payload 的保留方式。当前主线解析循环在容量检查后直接取得 &md->digest[md->digest_roof++],即使类型没有专用 descriptor,也会用通用头完成解析并占用一个 digest 槽位。于是 29 个 unknown noncritical 外层 payload 加最后一个 SKF,能够稳定得到 roof 30。

5.3.1 维护线的同名函数形状不同。它先令 pd = md->digest + md->digest_roof,未知类型分支读取通用头后直接 continue,循环底部的 digest_roof++ 没有执行。多个 unknown payload 会反复使用同一临时槽位,最终只有后面的已识别载荷推进计数。因此,主线 KVM 测试中的“29 unknown + SKF”不能原样解释维护版本的每一步。

官方公告用“payload 数量能够填满数组”描述漏洞,结论对两个分支都成立。维护线要抵达完整计数,需要用会实际记账的已识别外层 payload 组合,再以 SKF 收尾;具体组合还受到各类型出现次数、critical 规则和消息语义验证影响。研究报告必须保留这种版本差异,避免把测试 harness 的便利构造写成跨版本唯一网络载荷。

这项差异不改变受影响范围,也不削弱修复必要性。漏洞根因位于重组前置条件,它只关心到达时 roof 是否为 30。分支特有的 parser 行为决定怎样构造这个状态;一旦状态成立,旧版都执行同一错误比较。下游产品 fork 若改过 unknown handling,更应以自身源码与运行期断点确认 payload 组合。

从检测角度看,不能只写一条“29 个未知类型”的固定签名。主线回归流量会命中,维护分支或厂商 fork 可以用不同已识别载荷到达同一 state。更稳健的检测模型应统计第一片 SKF 前的实际 digest 消耗、验证链条闭合,并把满表状态与 daemon 退出关联;网络侧若无法还原实现计数,就把高密度外层载荷作为调查信号,交给主机证据完成确认。

Libreswan 两条解析分支的 digest 记账模型:主线中 unknown payload 各占一格,维护线 unknown 分支在递增前 continue;两条路径最终都可能在 SKF 重组前形成 roof 等于 30。
移动端可横向滑动比较主线与 5.3.1 维护线的记账位置
图 2:漏洞状态是“重组入口处 roof = 30”,构造方式随解析器分支变化。公开回归验证主线 unknown 路径,维护版本需要用其实际记账的载荷组合复现。

3 30 格表如何表示一条 IKE 消息

网络报文进入 pluto 后,不会被直接映射成一组长期存在的业务对象。Libreswan 先建立 struct msg_digest,在其中保存 IKE header、原始 packet buffer、解析后的 payload 描述符、按类型链接的 chain、接收端点和状态机定位信息。它是一份贯穿 demux、认证、解密与状态转换的短期工作台。

programs/pluto/demux.h 定义 PAYLIMIT 30,随后在 msg_digest 内嵌 struct payload_digest digest[PAYLIMIT]unsigned digest_roof。固定数组避免为每个小载荷反复分配内存,也给不可信报文设置明确的解析资源上限。每个 payload_digest 保存解码头的 union、当前载荷 body 的 pbs_in、类型、next 指针及所属 chain。

digest_roof 的名字来自半开区间:已使用元素构成 [0, roof)。空表时 roof 为 0;写入第一项使用下标 0,随后 roof 成为 1;写入第 30 项使用下标 29,随后 roof 成为 30。所有有效元素都满足 index 小于 roof,而 roof 自身可以等于数组元素数。它既是计数,也是末尾迭代器。

固定容量代码在“恰好填满”时最容易发生语义错位,明显超限通常已有直接防线。写入端需要在索引前检查 roof >= capacity;遍历端需要以 index < roof 结束;只检查状态是否仍处于数组表示范围时,应允许 roof <= capacity。这三个表达式看起来只差等号,分别回答“还能写吗”“还有元素吗”“当前计数合法吗”。

Libreswan 的解析器正确执行了写入端检查。每轮开头先判断 md->digest_roof >= elemsof(md->digest),容量已经耗尽便把 summary 设为 v2N_INVALID_SYNTAX 并停止。只有尚有空间时才取得描述符地址。CVE 的错误位于另一个阶段:重组函数没有准备写下一格,却套用了“还能写”的严格条件。

3.1 parser 同时维护数组顺序与类型 chain

ikev2_decode_payloads() 接收起始 Next Payload、输入流、需要记录的 payload set 和 logger。循环先核对全局 digest 容量,再根据类型选择 struct_desc,用通用或专用结构描述读取固定头,验证声明长度,建立 body 子流。解析成功后,已识别类型会挂入 md->chain[np],同类型的重复元素通过 next 串联。

数组保留报文中的总体解析顺序,chain 提供按类型快速访问。重组函数并不搜索“第几个元素是 SKF”,它直接读取 md->chain[ISAKMP_NEXT_v2SKF]。后续状态机也可通过 SK、CERT、AUTH、NOTIFY 等 chain 找到需要的 payload。一个描述符同时属于顺序表和某条类型链,改写时必须同步两个视图。

parser 的容量是整份 msg_digest 的预算。第一片外层载荷消耗一部分;SKF 重组成 SK 后,内部明文还要继续解码,复用同一张表中剩余槽位。如果外层已经用满 30 格,重组本身可以完成,但内部首个 payload 没有描述符空间。正确行为是由 ikev2_decode_payloads() 返回 INVALID_SYNTAX,让状态机拒绝这次 exchange,同时保持 daemon 运行。

上游满表回归的成功标准不是“隧道最终建立”,也不是“30 个外层元素都被业务接受”。它证明 roof 30 可以安全经过 SKF 到 SK 的原位重组;随后的内部 payload decode 正常发现预算耗尽,响应或记录协议语法错误。安全边界从致命进程断言退回可恢复的对端错误。

这种行为也解释了为什么补丁没有扩大 PAYLIMIT。把数组改成 31 只会把崩溃状态向后推一格,且改变全局解析预算。上游保留 30 个描述符的资源约束,只让重组接受合法满表;任何真正尝试解析第 31 份记录的路径仍由既有 >= 检查阻止。

审计同类代码时,应画出每个计数变量的写入点、索引点、迭代点和纯状态检查。单看变量名或单行 diff,很难判断等号是否安全。将数组与 chain 两个视图叠在一起,才能看到 reassemble_v2_incoming_fragments() 取得的是已存在的对象,后续 *skf = sk 也是原位覆盖。

3.2 SKF 被改写为 SK,没有第 31 次分配

全部片段验证完成后,重组函数首先对第一片保存的 msg_digest 增加引用。它断言 SK chain 为空、SKF chain 非空,并期望该 SKF 来自片号 1。这些条件描述当前对象的形状:还没有普通加密容器,第一片的 SKF descriptor 将成为重建容器的锚点。

第一遍循环计算所有 fragment plaintext 的总长度,第二遍分配一块连续 raw_packet 并依次复制明文。随后代码构造局部变量 struct payload_digest sk,让其 pbs 指向这块拼接缓冲区,设置 SK payload header 和 chain 信息。局部结构只是准备写入的值,不会进入 digest[] 的新位置。

真正的转换只有三步:从 SKF chain 保存 skf 指针,将 SKF chain 清空,把 SK chain 指向同一个 skf,最后执行 *skf = sk。源码注释使用 “scribble” 描述覆盖动作。地址没有改变,数组长度没有改变,digest_roof 也没有递增。

若 SKF 原来位于 digest[29],改写后的 SK 仍在 digest[29]。roof 为 30 继续准确表达已用区间 [0, 30)。因此修复表达式 md->digest_roof <= elemsof(md->digest) 对当前操作足够严格:它允许 0 至 30,拒绝任何已超出结构表示能力的值。

固定树中全部 digest_roof 使用点显示:IKEv1 与 IKEv2 parser 都在索引前进行容量检查;dump 与诊断逻辑用小于 roof 的循环;clone 和释放沿实际已用元素迭代;本次重组通过 chain 取得目标。修复后的等号不会放行下标 30 的访问,也没有另一处把 roof 当作最后有效下标的确认漏洞。

这类证据比“补丁由上游提供”更重要。一个 assertion 放宽可能掩盖真实越界,只有证明后续代码不追加、不索引 roof、不把满表状态传给错误长度的循环,才能确认修复没有用可用性换内存安全。对设备厂商的回移评审也应重复这项检查,因为 fork 中的重组实现可能已发生变化。

30 格 payload_digest 表的原位转换:索引 0 到 29 全部占用,SKF chain 指向索引 29;重组清除 SKF chain,将 SK chain 指向同一地址并用局部 sk 结构覆盖,digest_roof 保持 30。
移动端可横向滑动查看数组、chain 与对象地址的对应关系
图 3:等号安全性的决定性证据来自对象地址。SKF 与重建后的 SK 共用一格,roof 始终是计数,重组没有申请新槽位。

4 从 UDP 报文到 abort() 的完整函数链

漏洞分析常在断言那一行停住,运营团队却需要知道输入怎样穿过前置验证、哪一层拥有可观测日志、core stack 为什么会出现这些函数。固定版本中的处理链可以从收到已关联 IKE SA 的报文开始追踪:外层 demux 建立 msg_digest,IKEv2 decoder 填充 payload table,secured-message 分派识别 SKF,fragment collector 保存片段,验证与解密成功后调用 reassembly。

不同历史版本的上层函数名称略有变化。当前主线在 process_packet_with_secured_ike_sa() 附近处理受保护消息;早期 4.x 代码在 ikev2_process_state_packet() 中显式分支。核心顺序保持一致:解析外层链,找到 IKE SA,确认交换状态,收集 SKF,验证每片完整性,重组,再解码 SK 内部 payload。

collect_v2_incoming_fragment() 不是无条件缓存。它先调用 fragment 规则验证,按消息角色定位 st_v2_incoming,为首片保存 msg_digest 引用,并把加密文本、IV offset 与片号放入集合。总数未齐时状态机暂时返回;最后一片补齐后才进入 decrypt/reassemble 分支。

每片都经 verify_and_decrypt_v2_payload() 使用本方向 IKE 密钥验证。任何 ICV 错误都会被丢弃,无法触发重组断言。攻击者必须发送密码学上自洽的片组,这正是完成 IKE_SA_INIT 后具备的能力。网络中间人若不知道密钥,篡改现有用户片段不会得到相同效果。

旧版进入 reassemble_v2_incoming_fragments() 后依次检查 chain 形状,然后计算 30 < 30passert 宏构造包含源码位置与表达式的 logjam,调用标记为 NEVER_RETURNSpassert_logjam_to_logger()lib/libswan/passert.c 先把日志写到 logger,紧接着调用标准库 abort()

4.1 致命断言为什么影响整个 daemon

passert 用于表达开发者认为运行期绝不应出现的内部不变量。与 pexpect 或返回 INVALID_SYNTAX 的协议校验不同,它没有为不可信输入设计恢复分支。失败后记录断言、文件和行号,再以 SIGABRT 结束进程。CVE 的严重性来自一个外部可构造状态被错误归入了内部不变量。

在 systemd 部署中,journal 往往先出现 assertion 文本,随后是 unit 的 code=dumped 或 status=6/ABRT,再跟随自动 restart。core backtrace 应包含 passert_logjam_to_logger、重组函数及其上层 secured-message 处理。发行版是否生成 core 取决于 LimitCORE、systemd-coredump、容器权限和磁盘策略,未发现 core 不能排除触发。

进程重启后,原先内存中的 fragment set 已消失,攻击者需要重新完成 IKE_SA_INIT 并发送片组。这只增加少量往返。若入口由负载均衡分发,攻击序列可能命中多台节点;若设备使用单进程管理全部租户,单个来源触发会跨越连接配置影响共享控制面。

数据面是否立刻断开由平台决定。Linux 内核中的 ESP SA 与 policy 不会因为用户态进程退出就同步清空所有条目,旧会话可能继续转发;当 lifetime、rekey、DPD 或路由变更到来时,它们逐渐失效。检测应将“已有 ping 仍通”视为有限证据,主动建立新隧道并等待至少一个 rekey 周期才能评估恢复完整性。

如果 supervisor 配置了 restart burst 限制,重复崩溃可能把 unit 留在 failed 状态。如果没有限制,持续拉起会消耗 CPU、熵、证书与认证后端资源。两种策略都无法替代修复:前者延长停机,后者让攻击者获得稳定的控制面抖动器。

容器化网关还可能由 Kubernetes 重建 Pod。readiness probe 很快恢复绿色,但连接状态已经重置;重建速度、镜像拉取与节点调度把单次 SIGABRT 扩大成更长的不可用窗口。应在集群层关联 Pod restartCount、IKE 错误率和外部 UDP 流量,避免把它误判成普通滚动发布。

4.2 相邻边界审计没有发现第二个确认漏洞

相邻边界审计覆盖 fragment header、收集数组、总长度计算、连续缓冲区分配和 digest 使用点。结果支持上游定性:当前确认影响是可达断言拒绝服务,没有证据表明同一路径还包含独立的越界读写。

片数组采用 MAX_IKE_FRAGMENTS + 1,注释明确说明协议编号从 1 开始并故意留空元素 0。输入验证先拒绝 number 0,再保证 number 不大于 total、total 不大于 32。后续循环均使用 1 到 total 的闭区间,最大索引 32 对应已分配的最后一个元素。这组边界彼此匹配。

单个 UDP 输入上限为 65,536 字节,最多 32 片的明文总量约为 2 MiB。重组变量 unsigned size 在常见 32 位 unsigned 上距离溢出仍很远;每次复制长度来自已验证片段缓冲区,目标空间按相同长度求和分配。我们没有在这组公开约束下建立整数回绕或目标 buffer under-allocation。

重复片不会让 count 超过 total。集合会检查已有槽位,片组的 total、exchange 与 message context 不一致时拒绝合并;完整性失败会减少或清理有效片数。攻击者仍可用大量半完成 IKE SA 或片组消耗资源,那属于协议服务常见的容量防护问题,不能在没有独立证据时包装成这次 CVE 的新漏洞。

全部 digest_roof 用法也经过检索。索引点之前存在容量条件,遍历使用半开区间,修复后的重组只判断当前状态。主线保存 unknown payload 的改动扩大了可触达构造,却仍在递增前检查空间。没有发现 roof 大于 30 可从网络输入自然形成。

这项负面结论同样属于研究成果。安全报告需要区分“审计过并未建立利用条件”与“从未查看”。我们保留了检查范围、固定提交和假设,后续若厂商 fork 放宽最大片数、改变 input size、追加新 descriptor 或改写 unknown 分支,可以据此重新计算,而无需从头猜测。

CVE-2026-12413 函数链:IKEv2 外层解码、片段规则验证、collect_v2_incoming_fragment、verify_and_decrypt、reassemble_v2_incoming_fragments、passert 日志和 abort;修复后转入内部 decode 并返回 INVALID_SYNTAX。
移动端可横向滑动追踪网络输入、状态对象与进程终止点
图 4:补丁改变的是重组入口的分叉。旧路径把合法满表交给致命断言;新路径完成原位改写,再由普通语法错误处理耗尽的内部解析预算。

5 一字符补丁为何能恢复正确语义

维护分支提交 5c4369afcfe95a924ee0a96a0b29a7204dc40504 与主线提交 6088d294b8a6448df1611b98d778d85482646586 做了同一件事:把重组入口的 digest_roof < elemsof(digest) 改成 digest_roof <= elemsof(digest)。补丁说明直接指出 DIGEST_ROOF points at the roof,用变量的抽象语义解释等号。

这项改动没有绕过 parser 的上限。真正准备取得 digest[roof] 的代码仍要求 roof 小于容量;重组只确认当前计数处于可表示区间,然后通过 SKF chain 获取元素。roof 等于 30 时,后续连续 buffer 由 fragment plaintext 总长度单独分配,payload descriptor 则在原位置覆盖。修复精确对应操作所需的前置条件。

若只看断言文本,会担心等号让越界状态继续执行。沿数据依赖检查可以消除这项疑虑:没有表达式使用 digest[md->digest_roof],没有递增 roof,局部 sk 被赋给现有 skf 指针。下一次真正索引数组发生在解码内部明文时,开头的 >= 检查立即发现表已满并返回语法错误。

安全补丁的规模与验证规模没有线性关系。一字符 diff 必须回答至少四个问题:变量究竟是 index 还是 count;等于容量的对象状态是否可能从有效输入形成;断言之后有没有索引或追加;边界状态接下来以何种协议结果收束。上游提交给出了第一和第三个答案,历史与回归补足了第二和第四个答案。

对维护版本回移时,不宜用“删除 passert”代替。完全移除不变量会让真正的 roof 大于 30 静默前进,也降低 future regression 的可见性。应保留 <= 断言、parser 的 >= 容量拒绝、SKF/SK chain 形状断言与 KVM 边界用例,让三道约束各自承担明确职责。

5.1 修复后的正确终点是可恢复语法错误

主线测试 cve-2026-12413-max-payloads 通过 impairment 在 IKE_AUTH 外层加入 29 个 unknown v2 payload,并启用 fragmented exchange。首片 parser 把 29 个 unknown 与一个 SKF 记成 30 个 digest。旧版在所有片到齐后退出;修复版允许重组继续。

重组把 SKF 改为 SK,却没有为 SK 内部的第一个 payload 腾出槽位。ikev2_decode_payloads() 看见 roof 已经等于数组容量,记录 “more than 30 payloads in message; ignored”,把 summary notification 设为 INVALID_SYNTAX。测试的预期 console 最终看到对端拒绝,隧道保持未建立。

这个结果非常有价值。它证明补丁没有因为接受 roof 30 而跳过全局资源限制,也没有把异常消息当作成功认证。对端输入仍被拒绝,只是拒绝发生在为外部数据设计的错误通道中。daemon 继续服务其他连接,systemd restart counter 不增加,后续正常 peer 能照常建立隧道。

回归验收应把“进程存活”与“恶意隧道失败”同时写入断言。只看 exit code 可能遗漏 daemon 在后台重启;只看 tunnel down 可能把旧版崩溃当成测试通过。应记录测试前后 PID、service invocation ID、core 数量、journal assertion、恶意连接状态以及一条正常控制连接的 IKE/Child SA。

相邻边界至少覆盖 28 个填充载荷加 SKF、29 个填充载荷加 SKF、真正尝试超过 30 份记录,以及非分片 SK。具体填充类型按目标分支的 parser 语义选择。每组还应变换片数、到达顺序、重复片和丢片,确认修复没有破坏 fragment collector 的既有拒绝逻辑。

下游设备常常隐藏上游 KVM 框架。可以在隔离网络中使用已修改的 Libreswan peer 或协议测试器生成等价报文,并在目标端打开足够的 debug 日志。测试必须使用授权设备与测试凭据,限制速率和范围;CVE 是可靠的 daemon 终止条件,直接在生产入口试探会造成真实中断。

5.2 补丁验证要证明运行中的二进制已经改变

ipsec version 能打印用户态工具和 Libreswan 版本,是盘点起点。它仍可能来自新软件包,而旧 pluto 进程尚未重启;容器也可能更新 tag 却继续运行旧 digest 的 Pod。闭环需要同时记录软件包 NEVRA/DEB version、二进制 hash、进程启动时间和 /proc/<pid>/exe 指向。

厂商 backport 不一定把产品版本提升到 5.3.1。判断修复时应查发行版安全公告、package changelog 或源码补丁,确认重组函数使用 <=,并确认编译出来的 build 来自该 source revision。单纯比较 banner 会把固定的旧版本误报,也会把未合入补丁的私有 build 漏报。

升级后必须安排受控 daemon 重启。HA 部署先在备用节点安装并验证,转移新建连接,再处理原主节点;单节点环境应评估现有 SA 的重建时间、远程管理备用通道和业务峰谷。重启完成后用 ipsec statusipsec trafficstatus 或设备等价接口核对关键连接,观察至少一个 rekey。

修复验收不应只运行 exploit-shaped case。证书链较长、NAT-T、MOBIKE、不同加密套件、IPv4/IPv6、主动与响应端两种角色都可能使用 fragmentation。保留一组实际业务 peer 的报文尺寸与分片数量基线,升级后比较成功率和延迟,才能证明一字符边界修正没有在厂商集成中引入互操作回归。

回移 patch 时还要核对函数签名与 logger API。4.6 以来的重组代码经历过返回值、fragment owner 和 message digest 引用方式变更;机械 cherry-pick 可能发生上下文偏移。最终检查点始终是同一个:在取得现有 SKF 后允许 roof 等于容量,且任何后续新 digest 解析仍先做容量判断。

软件物料清单可将 Libreswan 版本与 appliance firmware 映射起来,但运行暴露还需要连接配置。一个固定 build 可以安全处理分片;一个受影响 build 若所有 IKEv2 连接确实禁用 fragmentation,入口暂时受限。版本与配置是两列独立证据,不能互相代替。

6 2021 年重构怎样制造了四年多的脆弱窗口

CVE 公告给出的受影响起点是 4.6。对比 4.5 与后续历史,可以把这个版本边界落到一笔具体提交:1a4715ea4b8501da60cdd72828d8d8d6e10f8aeb,作者在 2021 年 9 月 18 日将分片解密与重组从 ikev2_decrypt_msg() 中拆出,改由上层状态包处理函数显式协调。

重构目标合理。旧函数同时负责判断 SK/SKF、逐片验证、重组、解密普通 SK 和返回布尔状态,职责较重。新代码引入 decrypt_v2_incoming_fragments(),让所有片先完成验证,再调用独立的 reassemble_v2_incoming_fragments()。控制流更清晰,也方便后台加密计算与 fragment lifecycle 管理。

问题发生在搬迁一个既有边界时。4.5 的 ikev2_reassemble_fragments() 在重组前写着:若 digest_roof >= elemsof(digest),记录 “packet contains too many payloads; discarded” 并返回 false。这个条件把 roof 30 当作无法继续解析内部 payload 的对端错误,进程保持运行。

1a4715ea4b 删除了这段可恢复分支,在新重组函数中加入 passert(md->digest_roof < elemsof(md->digest))。比较范围看似相同,失败语义已经从日志加返回变为全进程终止。与此同时,重构后的代码仍沿用原有 *skf = sk 原位覆盖,没有增加下一格写入需求。

这笔提交进入 4.6,之后一直保留到 5.3。版本历史和代码差异相互吻合,形成清楚的因果链:旧版知道满表是网络可达状态,并以协议错误处理;重构把它提升成内部断言;数组模型没有变化;攻击者因此获得远程 SIGABRT。漏洞不是 RFC 7383 新增功能当天产生,也不是 2025 年 unknown payload 保留改动首次创建。

6.1 历史提交改变了错误域,没有改变数据结构

重构审查常把注意力放在 happy path 是否等价:片段能否解密、明文是否按序复制、SK chain 是否恢复、fragment owner 是否释放。这些行为在 1a4715ea4b 中大体保留。真正丢失的是 failure domain:同一个容量条件由可恢复输入错误变成了不可恢复程序错误。

安全敏感 parser 需要把断言分为两类。纯内部结构损坏,如 chain 指针互相矛盾且外部输入无法单独制造,可以使用致命 invariant;由报文长度、元素数量、枚举值或顺序推导的状态,应尽量走可恢复拒绝。即使协议规范说发送者“不应”构造某种组合,网络对手仍会构造它。

这里还有一个典型的 count/index 语义漂移。旧分支的 >= 从“还需要为内部 payload 留槽位”出发,面对满表提前拒绝;新断言似乎把同一表达式解释成“当前结构必须尚有空位”。重组本身并不消费空位,所以这项 invariant 比函数需求更强。开发者看到固定数组时自然选择严格小于,却没有重新追踪对象复用。

后续提交 6a9aac807a89659c874beaaa5af2a0ba801a7c90 调整首片消息的重建方式,3f0b015374f5ce85cc2e99325ef80b69843e0ac430dcb882e8fe2a2fadbe5f448c93f879f6100aab 继续处理未加密外层 payload 的保留。它们让第一片语义更完整,也使边界组合更值得测试;致命的严格小于条件仍可追溯到 2021 年重构。

2025 年提交 6824359f 让主线不再彻底丢弃无法识别的 payload descriptor,公开回归因此可用 29 个 unknown 快速填表。这个变化扩大了主线的简洁触发构造,却不是官方影响范围起点。维护线在 unknown 分支的计数不同,依然被列为受影响,进一步证明根因位于独立的重组断言。

代码考古的价值在于确定应该修什么、测试什么。若把问题归因于 unknown payload,防御者可能尝试过滤未知类型;历史显示已识别外层载荷也能形成容量状态,真正修复必须恢复重组对合法 roof 的认识。若把问题归因于 fragmentation 设计,可能永久关闭业务需要的 RFC 7383;源码显示一字符边界即可保留功能。

6.2 回归测试把历史边界重新变成工程契约

修复合入由提交 52c1c2e039f5f7bb27d34069d2d2426fea93ec36 汇总,并加入 KVM 测试目录 testing/pluto/ikev2-44-cve-2026-12413-max-payloads。发起端使用 ipsec whack --impair add_unknown_v2_payload_to:IKE_AUTHpad_with_unknown_v2_payloads:1 控制测试包形状。

测试生成器的相关支持由 46339ea9fd5dd4e27c322bf327fe564fdc5d3a4d805814a35f22f5370deddf878ea8784f6621d083 完善。impairment 属于测试接口,不是生产配置,也不意味着普通 Libreswan 客户端会自然发送这种消息。它提供一条可重复、可审查的方式,把 parser 推到精确边界。

预期 transcript 中出现 INVALID_SYNTAX 是一项关键设计选择。测试若只确认“没有 core”,可能因消息在更早阶段被静默丢弃而通过,无法证明满表真正走过重组。看到对端协议拒绝,结合 debug 中的 fragment collection 和内部 decode,才能确认状态穿越了旧断言位置。

维护分支应有对应的 recognized-payload 边界用例。若下游只回移主线 unknown 测试,旧 parser 可能因为 unknown 不递增而从未抵达 roof 30,形成虚假的绿色结果。测试设计必须与目标源码版本的计数位置一致,固定 payload 类型、出现次数和 expected digest trace。

建议在 CI 中加入一个小型模型测试,把 capacity 设为更小值并枚举 0、capacity−1、capacity、capacity+1 四种状态。parser 写入、reassembly reuse 与 inner decode 分别声明自己的前置条件。这样的属性测试比单个 CVE 报文更能防止未来重构再次混淆 count 与 index。

历史检查还应进入代码评审清单:任何将 return false、notify 或 parser error 改成 passert 的 diff,都要回答该状态是否受网络字段影响;任何固定数组边界迁移,都要保留“刚好装满”用例;任何 descriptor reuse 重构,都要验证 owner、chain 和计数同时守恒。

CVE-2026-12413 源码时间线:4.5 用可恢复返回处理满表,2021 年 1a4715 重构引入严格断言并进入 4.6,2025 年主线保留 unknown 描述符,2026 年 5c4369 和 6088d2 修复,5.3.1 发布回归。
移动端可横向滑动查看版本、提交与错误语义变化
图 5:脆弱窗口由 2021 年错误域迁移开启,2026 年以恢复合法满表状态关闭。unknown 保留改变了主线触发构造,没有改变根因。

7 从资产盘点到生产恢复的处置路径

第一步是建立运行资产表。字段至少包含节点与集群、管理责任人、公网或合作网络入口、UDP 500/4500 暴露、操作系统或 appliance firmware、Libreswan package/source revision、当前 pluto PID 与启动时间、HA 角色、连接配置来源、有效 IKE 版本、有效 fragmentation 值、关键 peer 数量和维护窗口。缺少任一关键字段的节点不能标记完成。

版本判定规则应具体到固定边界:上游 4.6、4.7、4.8、4.9、5.0、5.1、5.2、5.3 进入升级队列;上游 5.3.1 及更新版本满足项目修复条件。发行版 backport 以供应商安全公告列出的 fixed package 为准,同时保存 changelog 或 patch 证据。仅有 “5.x” 或 firmware 大版本无法支撑结论。

配置判定要使用展开后的有效值。ipsec readwriteconf --config /etc/ipsec.conf 可解析语法与 also= 继承,ipsec connectionstatus 可查看运行连接;受管理 appliance 应导出控制器生成的实际配置。搜索单一文件里的 fragmentation=no 不够,include、模板默认值和运行时加载都可能覆盖它。

默认配置 fragmentation=yes 意味着“文件里没写”通常仍是启用。只要一个受影响 daemon 下的 IKEv2 connection 允许 fragmentation,网络处理代码就仍在同一进程可达。多租户或多连接实例不能用某条低风险 profile 的设置替代全进程评估。

7.1 临时缓解必须带着互操作证据

Libreswan 给出的配置缓解是:业务不需要 IKEv2 fragmentation 时,在每一条 IKEv2 connection 上设置 fragmentation=no。这会阻止双方使用 RFC 7383,切断本 CVE 的重组入口。若连接确实需要分片,官方没有等价配置规避,应尽快升级或应用对应分支补丁。

判断“无需分片”不能凭当前隧道数量。证书认证的 IKE_AUTH 常因完整证书链、OCSP、多个 traffic selector、EAP payload 或厂商扩展超过路径可承载尺寸。NAT-T 还增加 UDP 封装开销。关闭后,小型 PSK peer 可能正常,大型证书 peer 在特定链更新或漫游网络上才超时。

变更前应从代表性 peer 采集 IKE_AUTH 报文尺寸、SKF 使用次数、路径 MTU 与重传。覆盖最长证书链、IPv6、移动网络、云安全组、NAT 设备和跨区域链路。若过去七至三十天已经观察到 SKF,直接关闭会创造已知业务回归;若没有观察到,也要做一次完整重新认证与 rekey 测试。

临时变更的 PRD 应写明对象列表、配置 diff、审批人、开始与结束时间、业务探针、失败阈值和回滚动作。例如:修改前导出 resolved configuration;逐个 connection 设置 no;重载或受控重启;在五分钟内由指定 peer 建立新 IKE SA 与 Child SA;连续十五分钟错误率不得高于基线两个标准差;失败即恢复配置并把节点隔离至仅可信源可达。

边界 ACL 可以作为补充。站点到站点 peer 若拥有稳定地址,可只允许已登记来源访问 UDP 500/4500;远程办公入口通常无法严格限定。ACL 保护的是网络可达性,NAT、共享出口、源地址变化和内部被入侵主机都会改变假设。它应有到期日期,不能让受影响版本长期留在库存中。

速率限制要以 IKE SA 建立和失败 exchange 为单位,避免只按 UDP packet 数。正常 fragmented IKE_AUTH 天然包含多包,粗暴 PPS 阈值会误伤。网关或前置设备可以限制单源半开放 IKE SA、异常高密度外层 payload 和短周期重复初始化,同时确保不同 NAT 用户不会被聚合成一个来源封禁。

7.2 升级、重启和回归要形成一张闭环工单

升级优先级按“可达性 × 受影响 build × 业务承载量 × 恢复难度”排序。公网单节点远程接入、共享多租户控制面和无带外管理的站点应最先处理;仅可信专线可达且具备快速 HA 切换的节点可以排在后续批次。所有 4.6 至 5.3 实例仍要给出明确完成日期。

安装阶段保存 package manager 输出、repository origin、签名验证、source package revision 与变更单号。源码构建需固定 commit、compiler flags、依赖与产物 hash。若使用官方补丁,确认 URL 路径中的 CVE 编号为 CVE-2026-12413;公告文本的 patch section 曾出现数字转置为 CVE-2026-21413 的链接文字,应以正确目录和实际补丁内容为准。

滚动升级先处理不承载新连接的节点。安装后停止旧进程,启动新二进制,核对 PID、exe target、启动日志和版本,再让少量测试 peer 接入。确认 IKE_SA_INIT、IKE_AUTH、Child SA、DPD、rekey、删除与重新建立均正常后,逐步恢复权重。节点间不要共享一个未经确认的旧镜像 digest。

单节点维护要准备可达的带外通道和回滚包。提前记录关键 connection 的 owner 与验证窗口,通知可能发生重认证;变更后先建立管理隧道之外的测试连接,再恢复业务。若回滚到受影响 build,立即恢复已验证的 fragmentation=no 或入口限制,并重新打开漏洞工单,不能把回滚视为处置完成。

功能回归包含实际 peer 矩阵,安全回归包含精确容量矩阵。后者至少验证 roof 29、roof 30、尝试第 31 项、错误片号、total 33、首片 NP 为 NONE、后续片 NP 非零、重复片、缺片、乱序与 ICV 错误。每项记录预期日志、协议 notify、PID 是否变化和正常控制隧道是否存活。

满表用例的验收结果写成四条:重组函数被执行;旧 assertion 文本没有出现;恶意 exchange 以 INVALID_SYNTAX 或目标分支等价的可恢复错误结束;pluto PID 与 service invocation 保持不变。若只满足其中三条,仍需检查测试是否真的抵达边界。

生产监控在变更后至少维持一个业务 rekey 周期。观察 IKE authentication failure、fragment receive/decrypt、message too many payloads、service restart、core、Child SA churn、认证后端 latency 和用户报障。长证书链 peer 若出现 IKE_AUTH 重传,优先检查临时 fragmentation 配置是否残留。

完成条件由资产 owner 与安全 owner 双方签署:运行 build 有固定证据;所有有效配置已采集;临时措施已撤销或带到期日;满表和业务回归通过;历史日志完成回看;监控规则上线;回滚包与证据归档。这样“已升级”才会变成可复查的安全状态。

Libreswan CVE-2026-12413 处置闭环:资产与有效配置盘点、5.3.1 或供应商 backport、受控重启、业务分片回归、roof 30 安全回归、日志与 PID 监测、证据签署。
移动端可横向滑动查看每个阶段的输入、验收条件与失败回退
图 6:处置不是版本号单点判断。运行二进制、有效连接配置、业务互操作和满表安全结果共同构成关闭条件。

8 观测、取证与最终判断

检测这项漏洞最可靠的方式,是把网络侧的异常片组与主机侧的进程终止放在同一时间窗。单独看到 fragmented IKE_AUTH 很常见,单独看到 pluto 重启也可能来自升级或其他缺陷;当一组已通过完整性验证、第一片外层载荷密集的 SKF 紧接着触发相同断言 stack,归因强度显著提高。

主机日志应检索 reassemble_v2_incoming_fragmentsdigest_roofpassertSIGABRT、status 6、core dumped 和异常 restart。不同分支的源码行号会变化,函数与表达式比固定行号稳定。保留 journal 的 monotonic timestamp、boot ID、unit invocation ID、PID、build ID 和 coredump metadata。

core 可用时先在隔离分析机提取 backtrace、md->digest_roofelemsof(md->digest)、SKF chain 地址、首片 header、fragment total/count 与 IKE SPIs。不要在生产 core 中公开私钥、证书私有材料或用户身份;根据组织数据处理规则限制访问并生成去敏摘要。

网络证据记录五元组、NAT-T 标记、initiator/responder SPI、exchange type、message ID、fragment number/total、外层 Next Payload 链、critical bit 和包长。受保护内容无需解密即可完成大部分结构关联,但外层 payload 是否计入 digest 仍取决于目标源码分支。PCAP 保存要遵守 VPN 元数据的敏感性要求。

若只有 restart 没有 packet capture,可从 IKE debug 日志、conntrack、flow telemetry 和上游防火墙日志重建来源与节奏。攻击者每次进程恢复后需要新建 IKE SA,因而可能出现重复 IKE_SA_INIT、短命 SPI 和相似 fragment total。证据不足时将结论标记为“与 CVE 行为一致”,不要把任何 SIGABRT 自动归入本漏洞。

8.1 事件响应应同时保护证据与恢复入口

确认正在被连续触发时,先保留一次完整样本和主机证据,再在上游按来源、目的节点或连接入口实施临时限制。若所有业务 peer 地址可枚举,快速切换到 allowlist;远程办公环境可启用半开放 SA 与 per-source 限速。不要反复手工重启而不改变入口,攻击者会在监听恢复后再次命中。

将新建连接引流到已修复节点,隔离受影响实例,核对 HA 备用节点版本。共享虚拟 IP 的集群要确保丢弃规则随流量路径生效;只在崩溃节点本机封禁可能让下一包被调度到另一个旧节点。云负载均衡、Anycast 和 ECMP 场景应逐层确认。

安装固定 build 并受控重启后,先用正常 peer 验证,再在实验环境重放经授权的边界夹具。比对 PID、journal 和协议结果,确认原先导致 crash 的边界流量已经变为可恢复拒绝。随后解除临时 ACL 时分阶段放量,继续监控相同来源与结构。

回看范围从当前进程启动前延伸到组织保留期内的最早受影响版本。检索重复 SIGABRT、短周期 service restart、未知原因的 VPN 波动和同期密集 IKE 分片。没有持久化能力的公开证据,仍需检查攻击窗口中因控制面中断产生的认证绕行、HA 故障、监控盲点与业务补偿操作。

事件报告应明确三层事实:源码证明的触发与进程终止;本环境观察到的版本、配置和日志;仍缺失的 PCAP、core 或 peer attribution。CVE 本身不能读取隧道明文或修改配置,控制面不可用可能诱发人工改路、临时放宽策略或 fail-open,这些后续行为要单独调查。

修复后若仍出现同一 stack,优先确认运行二进制是否真正更新、容器是否复用旧 layer、厂商 backport 是否作用于相同函数、core 是否来自旧 PID。若 stack 已变化,按新的 assertion 表达式重新分析,避免把所有 IKE 崩溃长期归到一个 CVE。

8.2 最终结论:这是错误域与边界语义共同造成的拒绝服务

CVE-2026-12413 的决定性事实已经连成完整链路。msg_digest 以 30 个固定 descriptor 保存一条消息的解析结果,roof 是半开区间末端;RFC 7383 让第一片在 SKF 前保留外层载荷;尚未通过身份认证的 initiator 已可为 IKE_AUTH 片组生成正确完整性标签;旧重组函数面对 roof 30 执行致命严格断言。

重组的真实动作是复用 SKF 槽位构造 SK,没有第 31 次写入。修复允许 roof 等于容量,随后内部 decoder 继续执行既有上限并返回 INVALID_SYNTAX。这条正常错误路径同时保住两个安全目标:不接受资源超限的消息,不因远程输入终止共享 daemon。

历史解释了漏洞为何从 4.6 开始。4.5 对满表记录并返回,2021 年重构把同一网络可达条件转成 passert;数据结构与原位覆盖没有同步改变。2025 年主线 unknown payload 记账使公开回归更易构造,维护线拥有不同 parser 形状,根因仍落在同一个重组不变量。

我们的相邻审计覆盖片号数组、最大片数、重组总长度与全部 roof 用法,没有建立第二个独立越界漏洞。这个结论受固定源码范围约束;设备 fork、修改过的最大输入、额外 descriptor append 或不同 allocator 需要重新验证。研究材料中的未确认空间被保留,不影响当前拒绝服务结论。

生产处置的最短可靠路径是升级至 5.3.1 或包含等效 backport 的厂商包,重启到新二进制,并用业务分片与 roof 30 两类用例共同验收。只有业务明确不依赖 RFC 7383 时,才在所有 IKEv2 connection 上暂时设置 fragmentation=no;需要分片的入口没有配置层替代修复。

这次缺陷留给协议工程的经验也很具体:count 可以等于 capacity,index 不能;资源上限由输入决定时,错误必须回到对端协议域;重构 failure path 时要保留失败的恢复级别;原位对象转换应把地址、owner、chain 与计数画在同一张图上。落实这些规则,比记住某一行补丁更能防止下一次满表退出。

最终验收可用一条简单判断收束:同一台网关在恶意边界输入后仍保持原 PID,返回可恢复协议错误,随后正常 peer 能建立并完成 rekey,同时运行 build 与配置证据可追溯。四项全部成立,CVE-2026-12413 的工程风险才真正关闭。

参考资料

  1. Libreswan:CVE-2026-12413 官方安全公告
  2. Libreswan:CVE-2026-12413 补丁与材料目录
  3. Libreswan:安全公告索引
  4. Libreswan:5.3.1 发布入口
  5. Libreswan 5.3.1 源码发布包
  6. CVE.org:CVE-2026-12413 记录
  7. NVD:CVSS、CWE 与 CISA SSVC 信息
  8. 维护分支修复提交 5c4369af
  9. 主开发线修复提交 6088d294
  10. 修复与 max-payload 回归合入提交 52c1c2e0
  11. 2021 年引入严格断言的重构提交 1a4715ea
  12. 首片重建后续提交 6a9aac80
  13. 主线保留未知 payload 描述符提交 6824359f
  14. 重组保留未加密 payload 提交 3f0b0153
  15. 分片消息纳入外层 payload 提交 30dcb882
  16. IKEv2 unknown payload impairment 提交 46339ea9
  17. 测试 payload padding 修正提交 805814a3
  18. 上游 CVE KVM 回归目录
  19. 回归发起端 impairment 脚本
  20. 回归预期 console 与 INVALID_SYNTAX 结果
  21. 固定源码 demux.h:PAYLIMIT、digest 与 digest_roof
  22. 固定源码 ikev2.c:外层与内部 payload decoder
  23. 固定源码 ikev2_message.c:收片、验证与重组
  24. 固定源码 ikev2_send.h:1-based fragment 数组
  25. 固定源码 passert.c:日志后调用 abort()
  26. Libreswan v4.5:可恢复满表检查的基线
  27. Libreswan v4.6:官方受影响起点
  28. Libreswan v5.3.1:首个上游修复 tag
  29. RFC 7383:IKEv2 Message Fragmentation
  30. RFC 7296:Internet Key Exchange Protocol Version 2
  31. IANA:IKEv2 参数与 Payload Type 注册表
  32. Debian Security Tracker:CVE-2026-12413
  33. CWE-193:Off-by-one Error
  34. CWE-617:Reachable Assertion
  35. Libreswan ipsec.conf:fragmentation 配置语义
  36. Libreswan ipsec readwriteconf:展开与验证配置
  37. Libreswan ipsec connectionstatus:运行连接盘点

研究记录

9证据、对象与来源

下面保留本文实际使用的标识、时间和原始材料,便于继续调查。

9.1研究对象

报告涉及的产品、行为者、技术、受影响对象和控制点。

CVECVE-2026-12413

Libreswan IKEv2 分片重组的预认证可达断言

受影响版本Libreswan 4.6–5.3

2021 年重构后到 5.3 的上游版本范围

修复版本Libreswan 5.3.1

2026 年 6 月 24 日发布的首个上游固定版本

修复提交5c4369af / 6088d294

允许 digest_roof 等于 30 的维护线与主线提交

引入提交1a4715ea4b8501da60cdd72828d8d8d6e10f8aeb

把可恢复满表返回改成致命断言的 2021 年重构

关键函数reassemble_v2_incoming_fragments()

复用 SKF descriptor 构建 SK 的分片重组函数

边界状态digest_roof == 30

合法满容量计数,数组有效下标仍为 0 至 29

临时措施fragmentation=no

只适用于已验证无需 RFC 7383 的全部 IKEv2 connection

9.2事件时间

  1. 重构引入致命边界

    提交 1a4715ea 将旧版安全返回替换为严格小于断言,随后进入 4.6。

  2. 问题报告给项目

    Libreswan 收到分片满载导致 daemon 退出的私下报告。

  3. 修复与 5.3.1 发布

    项目发布维护线与主线修复、固定版本和安全公告。

  4. CVE 记录公开

    CNA 公布受影响范围、网络可达性与拒绝服务影响。

  5. SOSEC 完成扩展源码复核

    固定到 5.3.1 源码,打通协议、函数、历史、回归与生产处置链。

9.3来源与材料

  1. Libreswan:CVE-2026-12413 官方安全公告https://libreswan.org/security/CVE-2026-12413/CVE-2026-12413.txt
  2. Libreswan:CVE-2026-12413 补丁与材料目录https://libreswan.org/security/CVE-2026-12413/
  3. Libreswan:安全公告索引https://libreswan.org/security/
  4. Libreswan:5.3.1 发布入口https://libreswan.org/
  5. Libreswan 5.3.1 源码发布包https://download.libreswan.org/libreswan-5.3.1.tar.gz
  6. CVE.org:CVE-2026-12413 记录https://www.cve.org/CVERecord?id=CVE-2026-12413
  7. NVD:CVSS、CWE 与 CISA SSVC 信息https://nvd.nist.gov/vuln/detail/CVE-2026-12413
  8. 维护分支修复提交 5c4369afhttps://github.com/libreswan/libreswan/commit/5c4369afcfe95a924ee0a96a0b29a7204dc40504
  9. 主开发线修复提交 6088d294https://github.com/libreswan/libreswan/commit/6088d294b8a6448df1611b98d778d85482646586
  10. 修复与 max-payload 回归合入提交 52c1c2e0https://github.com/libreswan/libreswan/commit/52c1c2e039f5f7bb27d34069d2d2426fea93ec36
  11. 2021 年引入严格断言的重构提交 1a4715eahttps://github.com/libreswan/libreswan/commit/1a4715ea4b8501da60cdd72828d8d8d6e10f8aeb
  12. 首片重建后续提交 6a9aac80https://github.com/libreswan/libreswan/commit/6a9aac807a89659c874beaaa5af2a0ba801a7c90
  13. 主线保留未知 payload 描述符提交 6824359fhttps://github.com/libreswan/libreswan/commit/6824359f416a1eba0fabc0f19d5fe9267338bf3b
  14. 重组保留未加密 payload 提交 3f0b0153https://github.com/libreswan/libreswan/commit/3f0b015374f5ce85cc2e99325ef80b69843e0ac4
  15. 分片消息纳入外层 payload 提交 30dcb882https://github.com/libreswan/libreswan/commit/30dcb882e8fe2a2fadbe5f448c93f879f6100aab
  16. IKEv2 unknown payload impairment 提交 46339ea9https://github.com/libreswan/libreswan/commit/46339ea9fd5dd4e27c322bf327fe564fdc5d3a4d
  17. 测试 payload padding 修正提交 805814a3https://github.com/libreswan/libreswan/commit/805814a35f22f5370deddf878ea8784f6621d083
  18. 上游 CVE KVM 回归目录https://github.com/libreswan/libreswan/tree/v5.3.1/testing/pluto/ikev2-44-cve-2026-12413-max-payloads
  19. 回归发起端 impairment 脚本https://github.com/libreswan/libreswan/blob/v5.3.1/testing/pluto/ikev2-44-cve-2026-12413-max-payloads/westrun.sh
  20. 回归预期 console 与 INVALID_SYNTAX 结果https://github.com/libreswan/libreswan/blob/v5.3.1/testing/pluto/ikev2-44-cve-2026-12413-max-payloads/west.console.txt
  21. 固定源码 demux.h:PAYLIMIT、digest 与 digest_roofhttps://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/demux.h
  22. 固定源码 ikev2.c:外层与内部 payload decoderhttps://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/ikev2.c
  23. 固定源码 ikev2_message.c:收片、验证与重组https://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/ikev2_message.c
  24. 固定源码 ikev2_send.h:1-based fragment 数组https://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/ikev2_send.h
  25. 固定源码 passert.c:日志后调用 abort()https://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/lib/libswan/passert.c
  26. Libreswan v4.5:可恢复满表检查的基线https://github.com/libreswan/libreswan/tree/v4.5
  27. Libreswan v4.6:官方受影响起点https://github.com/libreswan/libreswan/tree/v4.6
  28. Libreswan v5.3.1:首个上游修复 taghttps://github.com/libreswan/libreswan/tree/v5.3.1
  29. RFC 7383:IKEv2 Message Fragmentationhttps://www.rfc-editor.org/rfc/rfc7383
  30. RFC 7296:Internet Key Exchange Protocol Version 2https://www.rfc-editor.org/rfc/rfc7296
  31. IANA:IKEv2 参数与 Payload Type 注册表https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml
  32. Debian Security Tracker:CVE-2026-12413https://security-tracker.debian.org/tracker/CVE-2026-12413
  33. CWE-193:Off-by-one Errorhttps://cwe.mitre.org/data/definitions/193.html
  34. CWE-617:Reachable Assertionhttps://cwe.mitre.org/data/definitions/617.html
  35. Libreswan ipsec.conf:fragmentation 配置语义https://libreswan.org/man/ipsec.conf.5.html
  36. Libreswan ipsec readwriteconf:展开与验证配置https://libreswan.org/man/ipsec-readwriteconf.8.html
  37. Libreswan ipsec connectionstatus:运行连接盘点https://libreswan.org/man/ipsec-connectionstatus.8.html