漏洞

strongSwan CVE-2026-47895——空身份克隆中的双重释放

CVE-2026-47895 允许未认证的 IKE 对端用两字节 EAP 身份 @# 制造一块零长度但可释放的内存,strongSwan 克隆身份时让两个对象共同持有该地址,认证失败后的清理会触发 double-free、导致网关进程崩溃,项目同时将其评估为具备潜在远程代码执行风险。

暖纸手绘的所有权前后对照:左侧两个 EAP 身份克隆共用一个红绳空缓冲区,析构时将它撕裂;右侧修复后,每个克隆各自持有一只蓝绳缓冲区。
文章导航

研究依据固定源码 strongSwan 6.0.7 · 历史提交、远程入口、对象所有权与修复回归复核

来源strongSwan 公告、源码与历史提交 / 发行版安全公告 / SOSEC 源码复核

1 一封只有两字节的身份

这条漏洞链从一封极短的 EAP Response 开始。报文头之后只有两个可见字符:@#。它没有超长字段,没有畸形长度,也没有为解析器准备复杂嵌套;strongSwan 会把它理解成“以十六进制书写的 ID_KEY_ID”,井号后面的十六进制正文恰好为空。内容层面什么都没有,内存层面却产生了一块需要释放的对象。

随后发生的事情跨越三个抽象层。EAP method 提取用户名,身份构造器把字节转成 identification_t,认证器再为 auth_cfg_t 克隆该身份。每一层单独阅读都很平静;最后清理 IKE SA 时,两位持有者沿各自生命周期析构,才显露出它们共同指向同一地址。整个故事的转折发生在创建与销毁之间。

1.1 @# 在凭据得到证明前进入 EAP 认证器

EAP-Identity 的协议职责是为后续方法提供身份提示。RFC 3748 定义的 Identity Response 可以携带用户名、网络接入标识或实现约定的字符串;服务端收到它时,用户尚未凭密码、SIM、证书或其他 EAP method 完成认证。对互联网暴露的 IKEv2 网关而言,这段数据属于未认证远程输入。

strongSwan 的 eap_identity.cprocess() 中检查 EAP 帧,跳过 Code、Identifier、Length 与 Type 共五字节的头部,再把剩余数据克隆进 method state。长度为零的 Response 不会设置身份;两字节 @# 满足非空条件,因而会被完整保留。这里的克隆是字节缓冲区克隆,尚未出现所有权冲突。

当 identity method 返回成功,eap_authenticator.cserver_process_eap() 通过 method 的 get_msk() 取回这段数据。Identity method 借用了 EAP 接口里的 MSK 输出槽传递用户名;调用者知道当前 type 是 Identity,随后执行 identification_create_from_data(data)。两个字节从协议对象进入 strongSwan 的通用身份对象。

identification_create_from_data() 位于 src/libstrongswan/utils/identification.c。它为输入建立一个临时、以 NUL 结尾的字符数组,再调用字符串构造器。源码使用 char buf[data.len + 1],以长度受控的 snprintf() 写入,因此这一步没有越界;关键变化是二进制 EAP 数据从此会接受配置字符串的身份语法。

字符串构造器先检查显式类型前缀,再处理 @ 开头的简写。第二个字符为井号时,类型被设成 ID_KEY_ID,剩余字符串交给 chunk_from_hex()。对 @# 而言,传入 helper 的 chunk_t 长度为零。typed form 也有相同能力,例如 <type>:# 可把空十六进制正文送到同一函数。

这项语法原本用于表达无法方便键入的二进制身份,在连接配置、证书匹配和密钥选择里有合理用途。EAP 用户名复用了同一构造器,配置语法因此越过管理边界进入网络路径。漏洞的远程可达性来自这次复用,而非 EAP 协议自身要求解释 @#

报文不需要通过完整认证。identity method 的成功只表示身份交换完成,authenticator 仍会继续选择真正的 EAP method、匹配远端配置并验证凭据。恰恰在这个过渡点,apply_eap_identity() 要把身份写入认证配置,clone 随之执行。攻击者所需能力停留在建立 IKE 会话并回答一次身份请求。

1.2 零长度可以同时意味着“没有字节”和“拥有一块内存”

strongSwan 大量使用 chunk_t 传递字节串。这个结构只有 ptrlen 两个成员。常见的空值 chunk_empty 等于 { NULL, 0 };它没有内容,也没有释放责任。chunk_free() 调用 free(chunk->ptr) 后把描述符重置为空,free(NULL) 按 C 与 POSIX 语义不会执行任何动作。

chunk_from_hex() 对空输入采取另一条路径。函数先扫描十六进制字符,计算目标长度,随后无条件执行 buf = malloc(len)。当输入长度为零时,len 也为零;循环没有写入字节,函数最终仍返回 chunk_create(buf, len)。于是返回值可能是 { unique_ptr, 0 }

C 标准允许 malloc(0) 返回 NULL,也允许返回一个不能解引用、却能交给 free() 的唯一指针。glibc 明确采用后一种行为。运行在常见 Linux 发行版上的 strongSwan 会得到一枚真实地址;它占用的实际 allocator chunk 还可能包含对齐与元数据,即使请求的逻辑载荷为零。

这两种空值在业务比较中通常等价。它们的 len 都是零,哈希没有输入字节,身份格式化也打印不出正文。所有权账本却完全不同:NULL 可以由任意数量的对象“释放”,unique pointer 只能由一位所有者释放一次。仅以长度决定是否需要深拷贝,会把两种状态压成同一个分支。

分配器差异解释了为什么某些环境无法复现。若 allocator 对零尺寸请求返回 NULL,原对象一开始就是 { NULL, 0 },浅拷贝不会形成可重复释放的非空地址。这个条件适合用来理解故障,不适合作为常规修复策略。更换系统 allocator 会改变整个 daemon 的内存行为,维护成本远高于应用上游补丁。

最小 reproducer 因而短得近乎反直觉:ASCII @#,对应十六进制 40 23。它经过合法语法分派,既不依赖奇数位 hex,也不要求错误字符。异常只存在于返回对象的内部表示。普通功能测试若只断言身份 type 与 length,甚至会把结果判为正确。

研究这条链的核心问题是“这枚空身份当前由谁负责释放”。从 chunk_from_hex() 返回开始,原始 identification_t 是唯一所有者;clone 完成后,脆弱版本悄悄增加了第二位所有者,却没有增加第二块分配或共享引用计数。后面的 double-free 已经在这一刻写进对象图。

把最小输入放回 EAP 帧,Code 为 Response,Identifier 回显服务端请求,Length 为七,Type 为 Identity,最后两个 octet 是 40 23。所以它不是“两字节 UDP 包”,而是合法 IKEv2 EAP payload 中两字节、由对端选择的 identity body。eap_identity.process() 完成边界检查并跳过五字节头部后,恰好把这两字节安全交给后续对象 API;问题不在报文长度,而在对象表示。

data constructor 用长度受限的 snprintf("%.*s") 把输入转成字符串。嵌入 NUL 会提前终止语法,@# 则完整进入 parser。随后 chunk_from_hex() 的扫描、初始化和转换循环都处理零字节,sanitizer 在解析阶段没有越界可报;只有 clone 建立第二位 owner、两条析构路径先后结束时,内存错误才出现。

四种状态把漏洞说得最清楚:NULL/0 没有内容也没有 owner;非 NULL/0 没有内容却有一位 owner;非 NULL/N 有内容且有 owner;NULL/N 对正常 chunk API 无效。旧 clone 只看 len,把前两种空表示交给同一分支;但 ID_KEY_ID 的值是否为空并不改变“clone 必须能独立销毁”的接口承诺。

本地配置也能创建空 raw identity,因此受影响版本在配置加载或管理操作中同样可能崩溃;远程严重性来自 EAP 对配置字符串 constructor 的复用。%any、空字符串和 @ 通常得到 chunk_empty,单纯发送空用户名还可能被 EAP plugin 的 if (data.len) 拦住。调查必须保留输入来源与 raw-hex 前缀,不能把本地故障误判成远程利用,也不能用一个普通空值替代真实触发状态。

回归测试应把相邻值放在一起:零长度 EAP body、@@#@#0、合法一字节 raw hex,以及可抵达同一 helper 的 typed empty-hex 语法。它们分别覆盖未创建对象、普通空表示、zero/non-NULL 表示、hex 边界和正常非空 clone,既能锁定内部状态转换,也能保证未来某个 parser 入口变化后,clone 层仍独立守住所有权。

2 漏洞由三次历史改动拼成

修复提交只有一行条件变化,成因却埋在十七年的版本历史里。单看 2026 年父提交,else if (encoded.len) 像是一条普通优化;回到它首次出现的 2007 年,这条条件确实与当时的构造方式相容。2008 年加入的新输入状态和 2009 年换掉的 clone 骨架,逐步撤走了原条件赖以安全的前提。

这段历史很适合作为源码审计样本。维护者没有在某次提交里直接写出“双重释放”;缺陷由内存泄漏修复、功能扩展和代码简化组合而成。评审每次 diff 都可能给出通过结论,只有把对象表示和所有权不变量跨提交追踪,才能看见系统最终落到哪里。

2.1 2007 年的泄漏修复与 2008 年的 @# 语法各自改变一半条件

2007 年 4 月,提交 418dbd624363d9712c0d883084962cf93ae01b2a 的标题是“cloning %any ID without zero-byte memleak”。当时 clone_() 通过 identification_create() 构造一枚全新的空对象,再逐字段复制 type、encoded 和函数接口。构造器已经把 clone 的 encoded 初始化成 chunk_empty

旧代码无条件执行 clone->encoded = chunk_clone(this->encoded)。某些空身份会触发零尺寸克隆,分配器可能为其返回一枚需要释放的地址;提交于是增加 if (this->encoded.len)。长度为零时保持构造器预先放入的 { NULL, 0 },避免制造无意义的零字节分配。

在那份实现里,跳过赋值是安全动作。clone 并未从原对象复制 encoded 指针,它持有构造器自己的空值。条件表达的实际不变量是:“没有字节时,不必替换已经安全初始化的目标成员”。这一上下文后来消失,条件本身却留了下来。

2008 年 5 月,提交 86ab5636c2c9085c70688910fc3cd1f90a38187d 实现 @#hexID_KEY_ID 语法。此前井号分支带着 TODO 并返回 NULL;新代码跳过 @# 前缀,将余下字符串交给 chunk_from_hex()。它让管理员能够精确指定原始二进制身份,解决了真实配置需求。

新语法同时扩大了空值状态空间。过去常见空身份由构造器或 chunk_clone(chunk_from_str()) 规范化成 NULL;现在 @# 可以得到 allocator 产生的非 NULL 零长度指针。2008 年的 clone 仍从干净构造器起步,所以即使原对象持有这种表示,长度条件也会让目标对象保持 NULL,不会别名原地址。

到这一步,输入表示已经具备漏洞需要的第一项条件,clone 模型仍守住第二项条件。功能测试会看到 @# 能创建 ID_KEY_ID,克隆后两者的值仍相等,销毁也各自安全。真正改变安全前提的是 clone 初始化方式的改写。

2.2 2009 年的整结构复制让旧条件开始保留原指针

2009 年 7 月,提交 2147da40a5d79ca7921c028d38fe9503feb42bfb 以“simplified identification_t.clone() using memcpy”为目标。它移除 identification_create() 和数次显式字段赋值,改为 malloc_thing(private_identification_t) 后执行 memcpy(clone, this, sizeof(private_identification_t))。公共方法表、type 和其他标量一次复制完成。

整结构复制也会复制 encoded.ptr。后面的 if (this->encoded.len) 原样保留:非空身份进入分支,以 chunk_clone() 替换刚复制的指针;普通 NULL 空身份跳过分支,复制的值依旧是 NULL;@# 形成的 { ptr, 0 } 同样跳过分支,复制的非 NULL 指针便直接离开函数。

旧条件的语义由“保持新对象的安全默认值”悄然变成“保持从原对象浅拷贝来的值”。代码文本只少了几行,所有权效果完全反转。这里也解释了官方为何把受影响起点定为 4.3.3:漏洞所需的语法和 clone 模型在该版本线汇合,而更早版本缺少完整组合。

多年来它很难被偶然撞中。常规身份都有非零编码,clone 会执行深拷贝;%any、空字符串和 @ 等常见空表示通常持有 NULL;配置中主动写出空 raw-hex 身份也缺少业务价值。EAP 远端输入把这枚边缘状态变成攻击者可选择的报文字节,才让沉睡条件获得稳定入口。

2026 年 5 月,扩展后的 identity fuzz harness 把对象生命周期纳入测试,R. Elliott Childre 发现并报告问题。上游提交 075323d895f574424cfc4a5f491a1d388cdfda37 于 6 月 5 日合入;6 月 8 日,strongSwan 6.0.7 与安全公告同时发布。项目为旧系列准备了三段跨度不同的补丁。

修复把 else if (this->encoded.len) 改成 else。正则身份仍由专用分支复制字符串并重新编译;其余每一种身份都调用 chunk_clone()。该 helper 对零长度返回 chunk_empty,覆盖整结构复制留下的别名。原对象保留自己的 allocation,克隆获得 NULL,二者再次拥有独立析构。

补丁没有禁止 @#,也没有修改 EAP 协议。原始身份语法、value equality、hash 与匹配逻辑继续工作,变化只落在 clone 的资源语义上。这个选择减少兼容风险,也使旧维护分支能够以很小 diff 回移。

三段历史放在一起,旧条件为何变质才完整。2007 年 clone 是“构造后赋值”,跳过 encoded 赋值会保留目标的安全默认值;2008 年 parser 合法地接受空十六进制字节串;2009 年整结构复制则让任何 pointer member 先继承源地址,再等待后续分支覆盖。parser 无需把空值判错,真正的要求是 clone 能处理 constructor 返回的全部合法表示。

普通功能测试长期看不见这个断点:原对象与 clone 的 type、length、hash、equals 和字符串输出完全一致,只销毁一份也不会失败。只有同时保留两份对象、按真实 owner 结束两条生命周期,allocator 才能揭示第二次 free。因此版本考古不能只看 git blame 或值比较;结构定义、constructor、clone 和 destructor 必须在同一对象状态机里阅读。

官方把受影响起点定为 4.3.3,运营者应以项目公告和发行版 tracker 判断版本,不要把 2009 年主线提交日期直接换算成产品范围。三组旧分支补丁都在通用 identity clone 中恢复同一所有权不变量,而不是只修 EAP caller;这既验证了根因,也给后续维护留下明确规则:新增 pointer 或空表示时,重新核对整结构复制后的覆盖,并把“clone 可独立销毁”写进测试。

strongSwan 空身份克隆漏洞的提交时间线:2007 年长度条件修复零字节泄漏,2008 年 @# 语法可创建零长度分配,2009 年 memcpy clone 先复制指针,2026 年修复让所有非正则身份无条件经过 chunk_clone。
移动端可横向滑动查看提交、条件与所有权变化
图 1:漏洞由三次历史改动汇合而成。2007 年条件依赖“目标先由构造器安全初始化”;2009 年 clone 改成 memcpy() 后,这项前提已不再成立。

3 从报文字节到两个所有者

确定 double-free 需要把“同一值被复制”进一步还原成“同一地址被谁释放”。在固定的 6.0.7 树里,这条链跨过 eap_identity plugin、IKEv2 EAP authenticator、通用身份库与 authentication configuration。函数之间没有隐藏的序列化层;identification_t * 以真实对象指针沿调用关系传递。

我们以直接 EAP-Identity 为主线,因为它的远程控制和 clone 时机最明确。其他 EAP method、XAuth 与 RADIUS 会在下一章拆开。主线一旦建立,源地址、对象创建、第一次 clone、两位 owner 和两次 destructor 都能落到具体函数。

3.1 server_process_eap() 把 Identity method 的输出转换成通用身份

src/libcharon/plugins/eap_identity/eap_identity.c 的 server-side process() 接收 EAP payload。它调用 payload 的 get_data(),确认基础长度,随后执行 data = chunk_skip(data, 5)。若剩余长度非零,method state 的 identity 被设置为 chunk_clone(data),并返回 SUCCESS

这份 method state 拥有自己的字节副本,析构会释放它。get_msk() 在 identity 存在时把 chunk 返回给调用者,没有再次转移所有权。对 @# 来说,这里的 chunk 长度是二,内容保存在普通两字节分配里;触发 malloc(0) 的动作尚未发生。

src/libcharon/sa/ikev2/authenticators/eap_authenticator.cserver_process_eap() 识别当前 method 的 type。Identity exchange 成功后,它取得 method 输出并调用 identification_create_from_data(data)。返回对象被存入局部 eap_identity,接着交给 apply_eap_identity()

data constructor 使用字符串 parser 的决定让 @# 进入 chunk_from_hex()。固定树的 src/libstrongswan/utils/chunk.c 中,该 helper 先忽略可选分隔字符并计算目标长度,空后缀得到零;随后 buf = malloc(len),最终以 chunk_create(buf, len) 返回。glibc 上的新身份此时持有一枚非 NULL 地址。

private_identification_t 将 public interface、chunk_t encoded、identity type 与正则辅助状态放在同一结构里。其 get_encoding() 返回 chunk 的值副本,内容消费者据 len 读取;对象 destructor 才拥有释放 encoded.ptr 的职责。EAP authenticator 从构造器接过完整对象所有权。

apply_eap_identity() 先把对象保存到 this->eap_identity。这一步很重要:即使随后配置匹配失败,authenticator destructor 仍会处理原对象。函数再从 IKE SA 取得远端的 auth_cfg_t,用配置中的 EAP identity 规则进行匹配,并决定是否接受当前提示身份。

匹配完成后,函数以 AUTH_RULE_EAP_IDENTITY 调用 auth_cfg->add(),value 是 eap_identity->clone(eap_identity)。原对象继续属于 authenticator,克隆属于 auth config。设计上这样做很合理:认证器可以在 method 生命周期结束时释放自身状态,认证结果仍须随 IKE SA 留存给授权、日志与后续判断。

脆弱 clone 先用 memcpy() 复制完整 private_identification_t。空 raw-hex identity 的 encoded.len 为零,旧条件不进入 chunk_clone(),所以两个独立身份对象里的 chunk 描述符都写着同一地址和零长度。函数返回值看起来是一枚完整 clone,类型和值比较也完全正常。

3.2 auth_cfg_t 与 EAP authenticator 分别走向同一个 free()

auth_cfg_t 把每条规则保存成 type/value entry。清理时,destroy_entry_value() 根据规则类型决定资源处理方式;AUTH_RULE_EAP_IDENTITY 与普通 identity、AAA identity、group、XAuth identity 一样,会把 value 转成 identification_t * 并调用其 destroy()。规则容器随后移除 entry。

完整销毁由 auth_cfg.cdestroy() 调用 purge()。它遍历全部 entry,先销毁 value,再压缩 array,最后释放配置对象。因此 clone 的析构有稳定持有者,不依赖某个临时局部变量碰巧存活。

EAP authenticator 的 destroy() 位于另一文件。它销毁当前 method、EAP payload 等状态,并对 this->eap_identity 执行 DESTROY_IF()。这条路径释放原对象。两位 owner 的析构顺序由认证失败、task 清理和 IKE SA teardown 的具体控制流决定,任一顺序都会让第二位 owner 接触已经释放的地址。

身份 destructor 在 identification.c 中先调用 chunk_free(&this->encoded)chunk_free() 对当前描述符执行 free(chunk->ptr),再把这个描述符设为 chunk_empty。第一次释放后,第一位 owner 的 ptr 变成 NULL。

第二位 owner 持有另一个 chunk_t 结构。两个描述符只是最初地址相同,并不共享后续写回;第一位 owner 被清空时,第二位的 ptr 仍是旧地址。第二次 destructor 读取自己的字段,将旧地址再次传给 free()。这一步构成 double-free。

空身份通常无法匹配有效账户,认证失败会迅速启动清理。官方公告据此判断 double-free 很可能在 IKE SA 销毁时发生。该路径没有要求 attacker 先成功登录,也没有依赖把畸形对象长期保存到某个罕见管理操作;失败本身就是走向第二次释放的事件。

如果 identity 与配置规则不匹配,apply_eap_identity() 会返回 false,server path 终止当前交换;已经保存的原对象与已经加入配置的 clone 随后随各自 owner 清理。若后续 method 已被选择,失败也会在认证器与 SA 生命周期结束时回收这些对象。共同点是两个所有权边界都真实存在。

在调试器或 ASan 报告里,第二次 free() 的栈可能显示 auth config,也可能显示 EAP authenticator,取决于谁先销毁。根因定位应向前寻找两次 allocation history 的共同地址,并回到 clone_()。只凭最后一条栈给某个 owner 定责,会错过真正的别名创建点。

固定 6.0.7 源码把链路钉在四处:identification.c 定义 identity、clone 与 destructor,chunk.c 提供 hex helper,EAP plugin 提取两字节正文,authenticator 完成构造与应用。owner table 只有两行:eap_authenticator.this->eap_identityserver_process_eap() 活到 authenticator destroy;auth_cfg[AUTH_RULE_EAP_IDENTITY]apply_eap_identity() 的 clone 活到 purge()。两行对象地址不同,encoded.ptr 相同。

EAP method 保存的两字节 identity.ptr 与被重复释放的地址不是同一 allocation。后者在 data constructor 解析 @# 时由零尺寸分配产生,因此 core 中搜索 40 23 可能只找到 method buffer。定位时应从两条 free 栈共同回溯到 identity clone,而不是把报文字节地址当成目标 heap chunk。

auth_cfg->add() 接管 clone 的所有权,没有再制造一次浅拷贝;后续 config clone 或 merge 虽可能扩大别名数量,最短远程链只需要原对象与第一次 clone。正则分支则用 strdup()compile_regex() 重建独立状态,说明问题集中在普通分支漏掉零长度非空 pointer,而非所有 memcpy() clone 都必然危险。

auth config 必须持有自己的身份副本,因为授权、日志、凭据选择和 SA 信息都可能在 authenticator 生命周期之后继续使用它;改成 borrowed pointer 只会把 double-free 换成 use-after-free。destroy_entry_value() 也明确把 AUTH_RULE_EAP_IDENTITY 归入需要调用 identity destructor 的规则,容器合同要求传入值可独立销毁。

因此验证可以分成两层:对象测试构造 @#、clone、记录两份 encoding 地址并以不同顺序销毁,证明所有权缺陷;隔离的 IKE/EAP 集成测试再证明不可信 peer 能抵达同一 constructor 与 clone。认证失败沿正常 task teardown 触发问题,日志未必出现 malformed identity 警告;对象、协议和系统恢复分别测试,才不会把一个 allocator 报错反复当作 VPN 黑盒猜测。

EAP 身份所有权链:EAP Response 中的 @# 经 identification_create_from_data 和 chunk_from_hex 变成地址 A710、长度零的编码,apply_eap_identity 克隆后,EAP authenticator 与 auth_cfg_t 各自保存指向 A710 的身份,最终两次调用 free。
移动端可横向滑动查看函数、owner 与析构汇合点
图 2:危险并不来自两个对象本身重复;两份独立描述符错误拥有同一零尺寸 allocation。第一次 chunk_free() 只能清空自己的描述符,无法修正另一份副本。

4 暴露面由认证插件决定

直接 EAP-Identity 给出最短、最清晰的利用路径,生产配置却常把 identity exchange 隐藏在 PEAP、TTLS、SIM、AKA 或 RADIUS 后面。仅搜索 eap_id 无法完整判断。真正决定可达性的,是远程字节何时进入 identification_create_from_data(),以及所得对象在凭据失败前是否执行 clone。

strongSwan 项目建议将所有启用 EAP 的受影响服务端先视为暴露,再凭具体实现缩小范围。这个起点兼顾了 method 内部交换、版本差异和插件组合。运营清单需要记录加载的动态库与实际 connection 配置;“IKEv2 已启用”不足以支持暴露判断。

4.1 隧道内 EAP 与 xauth-eap 会在不同阶段克隆 username

服务端显式发起 EAP-Identity 时,前一章的 apply_eap_identity() 在 method 成功后立即 clone,远端无需知道有效账户。配置中的 eap_id 可以控制请求的 identity 或 method behavior,但未设置该项并不等于没有 identity parsing;不少 method 会在自己的协议里再次请求或携带 username。

EAP-PEAP 把内层 EAP 封装在 TLS tunnel 中。server implementation 处理 inner Identity Response 时调用 identification_create_from_data(),把结果保存为 this->peer,再创建 phase 2 method。后端构造器是否立即 clone peer 取决于所选 method;攻击数据已经穿过通用身份 parser,后续生命周期需要按真实组合复核。

EAP-TTLS 的服务器路径具有相似结构。tunneled identity 在 AVP/内层 EAP 处理中被解析成 identification_t,随后用于创建内部认证 method。外层 TLS 的建立证明对端能完成匿名 tunnel 握手,并不证明其账户身份;恶意 username 仍然处于认证前控制范围。

EAP-SIM 与 EAP-AKA 还会处理永久身份、pseudonym 和 reauthentication identity。固定树中,server path 在收到相应 identity 后调用 data constructor;某些 pseudonym 分支随后执行 peer->clone(peer)。触发条件与 method 状态、身份类别和服务端 provider 能力相关,不能从一个通用 EAP 开关推导出完全相同的报文序列。

这种差异解释了厂商公告中的谨慎表述:内部 identity 经常使用同一构造器,身份却并非在所有失败点都会被 clone。补丁优先级无需等待逐 method PoC,因为所有路径共享脆弱的 identification_t::clone_();暴露面细分主要服务于临时隔离与事件调查。

IKEv1 XAuth plugin 也把 username 交给 identification_create_from_data()。普通 XAuth task 在 backend 返回 SUCCESS 后,才把身份克隆为 AUTH_RULE_XAUTH_IDENTITY。空 raw-hex identity 很难通过账户验证,因此未经认证的直接触发通常被成功条件挡住。

xauth-eap 是明确例外。plugin 解析 XAuth username 后立即创建内部 EAP backend,把该 identity 作为 peer 参数传入。默认或常见的 RADIUS EAP backend 构造期间会克隆 peer 与 server identity,clone 发生在外部凭据得到批准之前。使用 IKEv1/XAuth 的团队必须确认是否加载和选择了这层适配器。

资产发现可从进程已加载 module、构建时 plugin list、swanctl --list-conns --raw 输出和配置仓库四个方向交叉。某个 plugin 被编译进二进制并不保证 connection 使用它;配置声明了 EAP 也可能由另一个节点或 RADIUS 完成。最终应形成“网关节点—connection—认证 method—identity 来源—clone 时机”的映射。

4.2 RADIUS 可以移走客户端入口,也可以从 AAA 信任边界重新引入问题身份

当 strongSwan 不在本地请求 EAP-Identity,而把 EAP 对话完整代理给 RADIUS 时,客户端的 identity 可以作为 RADIUS 属性转发,未必在 IKE daemon 内构造成 identification_t。官方公告据此列出一项缓解:RADIUS delegation、无本地 EAP-Identity request 的服务器可避开直接客户端路径。

eap-radius 还有两项可选属性处理。启用 Class-as-group 时,plugin 读取 RADIUS Class,在长度限制内调用 identification_create_from_data(),再以 AUTH_RULE_GROUP 加入认证配置。启用相应 Filter-Id 处理时,符合 tunnel 条件的非空 Filter-Id 也会走 group identity 路径。

此时攻击者身份发生变化。普通 IKE 客户端无法直接决定本地 group object 的表示,能够控制 RADIUS Response 的 rogue 或已被入侵 AAA server 却可以提供 @#。认证配置在 clone、merge 或 teardown 期间处理 group identity,与 EAP identity 共用同一个脆弱 clone 和 destructor。

因此“由 RADIUS 托管”需要补上两个限定:网关没有本地请求 identity,并且没有启用会把 Class/Filter-Id 解析为 group 的选项,或者 AAA server 被纳入可信、已修复的边界。漏掉后一个限定,会把来自客户端的风险误转成对高权限基础设施的盲目信任。

PPK identity 也使用 data constructor。IKE_AUTH task 解析 PPK_IDENTITY notify 后查找匹配的 post-quantum preshared key,只有确实找到 owner 匹配的 PPK 才继续并 clone 身份。空 ID_KEY_ID 与现有 key owner 匹配的概率很低,远程可达性明显弱于直接 EAP。

attribute certificate group 的构造同样发生在证书验证之后。攻击者需要先提供可信 AC 或控制签发链,才能让 group identity 进入后续配置。它依然是代码层受影响的调用点,却不应与未认证 EAP-Identity 混成同一威胁模型。

最后还有 allocator 前提。glibc 的零尺寸 allocation 提供唯一可释放地址,常见 Linux 网关满足条件;返回 NULL 的 allocator 会让这条精确别名消失。静态链接 appliance、定制 libc 或 hardened allocator 值得在实验环境确认,但版本修复仍是最稳妥的闭环。

风险排序可以据此分层:公网或不可信网络可达、受影响 build、启用本地 EAP identity 的节点优先;xauth-eap 紧随其后;启用 Class/Filter-Id group parsing 的 RADIUS gateway 需要同时评估 AAA compromise;普通 XAuth、PPK 与已验证 AC 路径保留在较低可达层。所有层都应升级,差别只在窗口顺序。

“全部 EAP method 先按受影响处理”是补丁排序规则,不表示每种 method 都共享同一条未认证报文轨迹。PEAP/TTLS 要先建立外层 TLS,SIM/AKA 要进入特定 identity exchange,部分 backend 只在成功状态 clone;配置又可能由 VICI、编排平台或设备 UI 在运行期生成。收窄暴露面时,应以 swanctl --list-conns --raw、已加载模块和 daemon 行为还原真实 connection,再记录 method-specific 的前置条件与 clone 时机。

RADIUS 的信任边界也需要落到实际属性。Class 与 Filter-Id 经过各自的长度、tunnel 和非空检查后才会变成 group identity;短 @# 仍可能满足这些约束。AAA schema 若禁止该表示可以降低可达性,却不能替代修补,因为策略、服务器或 response 来源都可能变化。多租户环境还要区分互联网 peer、能修改 method/endpoint 的 tenant 管理员和被入侵 AAA server,不能只凭“RADIUS 在内网”下结论。

设备 fork 与自定义 backend 可能改变 clone 时机。普通 XAuth 默认在成功后保存身份,xauth-eap 的常见 RADIUS backend 则在构造时 clone;其他实现可能借用、延迟或重新包装对象。判断应读产品实际源码或符号路径,而不是只看界面上的协议名称。

临时关闭入口必须说明业务代价与回滚:停用 EAP 会影响远程接入或 MFA,把认证交给 RADIUS 会改变日志、组映射和故障域。服务端必须发出 Identity Request 才进入直接路径,但攻击者可使用自定义 EAP peer,不受官方客户端输入校验限制;“客户端不允许输入 @#”不是服务端控制。

PEAP/TTLS 会加密内层 identity,网络 IDS 通常看不到触发字节;SIM/AKA 又按永久身份、pseudonym 与 reauth identity 走不同状态。检测与验证要跟随解密终点,结合服务端 method trace、provider 结果、clone breakpoint 和 core。多轮认证也应逐轮记录前置证明:先通过证书不等于后续 EAP identity 已可信,更不能据“连接使用证书”直接排除路径。

CVE-2026-47895 暴露矩阵:直接 EAP-Identity、PEAP/TTLS/SIM/AKA、普通 XAuth、xauth-eap、RADIUS Class/Filter-Id 与 PPK/属性证书 group 分别对应不同输入控制者、clone 时机和运营优先级。
移动端可横向滑动比较认证入口与信任边界
图 3:补丁范围应先覆盖全部 EAP/XAuth 节点,临时缓解再按具体 clone 时机收窄。RADIUS 并非单纯“安全”或“危险”,它同时可能移除客户端入口与引入 AAA 侧 group identity。

5 第二次释放把身份错误变成网关故障

从源码可以确定的安全后果是:未认证远程输入在受影响配置上形成同址双 owner,IKE SA 清理对该地址调用两次 free()。常见 allocator 会把它当作堆一致性破坏并终止进程,外部表现是 charoncharon-systemd 退出、现有与新建隧道受影响。strongSwan 和 Ubuntu 公告进一步保留了任意代码执行的可能性。

评估时应把“已证明的内存错误”“常见运行结果”和“利用上限”分成三个证据层。这样既不会低估位于 VPN 边界的 double-free,也不会把一句潜在 RCE 自动扩写成已经公开、稳定、跨 allocator 的利用链。

5.1 glibc 为零字节请求分配的最小 chunk 仍参与完整堆管理

应用请求 malloc(0) 时,glibc 返回可释放的唯一指针。逻辑长度为零并不让 allocator 跳过元数据、对齐和空闲结构;返回地址仍代表一枚受管理 allocation。第一次 free() 会把它放回相应缓存或 arena,后续对同一地址的操作必须遵守普通 heap object 的生命周期。

现代 glibc 对许多重复释放具有一致性检查。若第二次 free() 到来时该 chunk 仍处于相关 tcache 或 fastbin 状态,allocator 通常记录 double-free/corruption 并调用 abort。systemd 会看到进程因信号退出,core 是否生成取决于 unit、rlimit、容器和系统 core policy。

两次 destructor 之间可能发生其他分配。EAP task、日志、通知 payload 和 SA teardown 都会申请或释放内存;具体构建、线程模型与 plugin stack 决定堆活动。旧地址若被重新分配给另一对象,第二次释放可能影响新 owner 的生命周期,并把错误从“相邻两次 free”扩展成更复杂的 use-after-free 或 free-list 状态破坏。

这正是 double-free 被视为内存安全漏洞而非单纯 assert 的原因。源代码证明攻击者选择了创建别名的输入,也证明第二次释放会发生;它没有直接证明攻击者能控制中间 allocation、绕过 allocator 检测、写入目标地址并劫持控制流。那些能力需要固定版本、构建参数、libc、ASLR 和可重复 heap grooming 的进一步实验。

项目公告用“could potentially be exploitable for remote code execution”描述上限,Ubuntu USN 则写明远程攻击者可以造成崩溃或可能执行任意代码。两份材料都把 crash 作为确定结果,把代码执行保留为可能结果。当前公开修复提交与测试没有附带武器化 exploit。

进程崩溃本身足以影响关键业务。单一 charon 进程通常管理大量 IKE SA 和 Child SA;退出会停止协商、重认证、DPD 与策略安装,并可能使已建立隧道随内核状态或重启策略逐步失效。HA peer 能缩短恢复时间,却会承受同时涌入的重新连接与身份后端负载。

自动重启还可能把一次触发放大为持续抖动。攻击者在新进程监听后重新发送同样 identity,service manager 随即反复拉起 daemon,最终触发启动限速或设备 watchdog。运营指标应同时观察进程 restart count、IKE 建连速率、认证后端延迟、隧道总数和 HA failover,而非只看端口是否重新开放。

不同发行版给出的通用优先级可能不同。Ubuntu 将 CVE 页面标为 Medium,上游强调潜在 RCE;组织自己的优先级还要加入公网可达性、EAP 配置、网关承载量、HA 能力和升级窗口。已确认直接 EAP 入口的边界节点通常值得快速修复,即使通用标签较低。

5.2 现场证据要把 allocation、两个 owner 与认证会话重新连起来

最理想的诊断来自 ASan:报告会列出当前 bad free、先前 free 和最初 allocation。allocation 栈应落在 chunk_from_hex()malloc(len),两次 free 栈分别经过 identification_t.destroy,上层 owner 通常是 auth config purge 与 EAP authenticator destroy。三个地址相同便完成内存链闭环。

生产发行版往往没有 sanitizer。glibc 的 stderr 或 journal 可能出现“double free detected”“free(): double free”或通用 heap corruption 消息,顶部落在 libc。core 中应检查 faulting thread 的 backtrace、private_identification_t.encoded、identity type、auth rule type 与相关 IKE SA。下游优化和符号裁剪会改变函数可见度。

网络证据需要与进程证据对齐。保留崩溃前的 IKE 源地址、SPI、exchange type、EAP Code/Identifier/Type、所选 connection 和 method。完整 EAP identity 可能包含个人标识,应按凭据数据处理;检测本 CVE 只需记录长度、经过授权的哈希或精确两字节模式,不必在普通日志长期保存用户名正文。

@# 是公开、最小的 source-level reproducer,但并非唯一表示。typed empty hex prefix 也可能创建相同 chunk,某些上游 AAA 属性会把字节送进 data constructor。监测规则应优先关联“空 raw-hex identity 表示 + EAP/XAuth failure + daemon allocator abort”,避免只凭网络中出现两个字符产生大量误报。

正常管理配置也可能包含 @#。如果本地工具解析并 clone 该 identity,同一 defect 可触发本地崩溃;它不说明遭到远程攻击。调查必须标记输入来源:IKE peer、内层 EAP、XAuth username、RADIUS attribute、VICI/config load 或证书派生 group。相同字节在不同信任边界有不同事件含义。

修复前不建议在生产网关主动发送触发 identity。隔离实验应固定源代码 commit、allocator 和 plugin 配置,使用最小 IKE/EAP client,限制到授权测试地址,并准备 service isolation。验证目标是观察 owner 独立与清理成功,无需尝试塑造 allocator 以追求代码执行。

若历史 core 只保留最终 libc 栈,可以从包 build-id 找回 debug symbols,再逆向到 chunk_free() 与 caller。把 journal 时间与 IKE audit、RADIUS transaction 和 systemd restart 连接起来,往往能恢复 method 与来源。缺少原始 identity 时,仍可用“认证失败后立即 heap abort”的时序提高相关性。

事件响应还应确认重启后的进程版本。package manager 显示固定包并不保证旧 daemon 已退出;容器节点可能继续运行旧 image,HA peer 也可能漏掉一个成员。build-id、进程映射、容器 digest 和运行时 --version 应共同构成版本证据。

若继续研究代码执行,实验必须固定 build、allocator、worker 数和负载,回答零尺寸 chunk 的 size class、两次 free 间的可控 allocation、owner 释放顺序、检测中止点与地址重用。hardened allocator 可能更早终止,也可能为零尺寸请求返回 NULL;这些差异改变复现结果,不改变 clone contract。缺少这组测量时,RCE 应继续保留为上游给出的风险上限。

业务中断半径取决于部署形态:每租户一进程可能把 crash 限定在少量隧道,共享网关则让一个未认证 peer 触碰大批 SA;容器副本、UDP 会话粘性、HA 与内核 XFRM state 又决定故障如何扩散。数据面可能在控制面退出后短暂可用,直到 rekey、DPD 或策略超时,因此一次 ping 不能代替进程、SA freshness、重认证与新建连监控。

崩溃洪峰中的证据采集也要有上限。按 build-id、stack hash、来源与时间窗口聚合 allocator 错误和重启,同时保存首个完整样本;全部上传 core 可能耗尽磁盘,只保留计数又会丢掉不同 plugin 路径。NAT-T 还可能让许多用户共享公网地址,贸然封禁出口 IP 会扩大中断,优先把流量切到固定节点并冻结旧节点的 core 与配置。

chunk_free() 会在 free 后把当前 descriptor 清成 chunk_empty,但另一对象中的 descriptor 不会同步,这正是“地址别名”与“描述符别名”的区别。cookie 与速率限制可以提高连接成本、减轻容量压力,却不能修正 owner;一次触发成本较高,也可能因 daemon 丢失众多合法 SA 而形成不对称影响。

core 可能包含密钥、身份、证书与拓扑,应进入受限证据库,只公开去敏后的地址关系和调用栈。无法现场取 core 时,可以在评估性能与隐私后短期开启有界 coredump、提高 allocator diagnostic,或在同版本 staging 重放授权的最小会话;日志中的重启与 EAP failure 可辅助归因,但不能代替修复。

对外通报只需保持三层清楚:已经确认的 double-free 与服务影响、上游保留的潜在 RCE 上限,以及现场是否发现控制流劫持、数据访问或持久化。这样既不会淡化 VPN 边界上的内存错误,也不会把 crash 自动写成已证实入侵。

6 一行补丁如何恢复所有权不变量

提交 075323d895f574424cfc4a5f491a1d388cdfda37 同时修改生产代码、定向单元测试与 fuzz harness。三部分回答三个问题:clone 应怎样处理空 encoding,回归应断言什么,随机输入怎样真正走到对象生命周期。补丁规模很小,验证意图却比单纯“崩溃消失”更完整。

修复的核心句可以精确概括为:每个非正则 identification_t clone 都必须覆盖 memcpy() 带来的 encoded 指针。输入有无字节只影响新 allocation 的大小,不能决定是否执行所有权修正。

6.1 无条件 chunk_clone() 把零长 clone 规范化为 NULL

固定后的 clone_() 仍以 malloc_thing()memcpy() 开始。维护者没有重写整个对象构造,也没有删除高效的标量复制。后续分支承担一项明确义务:正则 identity 复制完整 NUL-terminated encoding、重新编译 regex;普通 identity 把 encoded 交给 chunk_clone()

chunk_clone 是宏,先把参数保存为局部 x,再以 x.len ? malloc(x.len) : NULL 创建目标并复制内容。长度为零时目标指针直接选 NULL,chunk_create_clone() 没有字节可复制。返回值稳定为 { NULL, 0 },不受系统 malloc(0) 策略影响。

原对象的 { unique_ptr, 0 } 没有被更改。它仍是构造器创建的合法资源,最终 destructor 会释放一次。clone 的 descriptor 则被覆盖成 { NULL, 0 },销毁只调用无副作用的 free(NULL)。两份对象的 value equality 保持,ownership equality 被主动打破。

非空 identity 继续分配同样长度并复制字节。regex branch 继续使用 strdup(),因为编译表达式需要 NUL 结尾的完整字符串,并为 clone 创建独立 regex object。修复没有扩大普通路径的分配次数,只有零长度非正则 clone 多执行一次返回 NULL 的 helper。

直接把条件改为检查 encoded.ptr 也可以避开当前 defect,却会在 NULL 空值时跳过覆盖,而安全性仍依赖 memcpy() 复制的恰好是 NULL。上游选择无条件 else,让每一种非正则状态都经过同一个 normalization point,未来加入新的内部空表示时也更稳健。

禁止空 raw-hex prefix 会缩小触发面,却破坏已存在的身份语法,并遗漏其他可能产生零长度非 NULL chunk 的构造路径。修正 clone contract 直接处理根因:一枚承诺独立生命周期的 clone 不能继承原对象的独占 allocation。

上游为历史树提供三份补丁:4.3.3–5.1.1、5.1.2–6.0.1、6.0.2–6.0.6。跨度不同源于源码上下文与方法宏演化,核心分支变化一致。手工回移时应使用对应 patch,并在下游差异造成 hunk offset 后重新核对函数语义。

strongSwan 6.0.7 是首个包含修复的上游发布。发行版不必把主版本升到 6.0.7 才能安全;Ubuntu 24.04 的固定包仍基于 5.9.13,Debian bookworm 固定包仍基于 5.9.8。运营验收要对照发行版 CVE tracker 的 package revision,而非用裸 upstream version 一刀切。

6.2 回归测试直接检查指针独立,fuzzer 则补全对象使用阶段

test_clone_empty()identification_create_from_string("@#") 开始。测试确认对象创建成功,分别读取原对象与 clone 的 encoding,断言两边长度都是零。它没有把“零长度”当作完成标准,而是继续比较指针。

关键断言是 a_enc.ptr != b_enc.ptr || (!a_enc.ptr && !b_enc.ptr)。allocator 若为原对象返回 unique pointer,clone 必须是另一个地址或 NULL;allocator 若本来返回 NULL,两边都为 NULL 也安全。测试因此同时覆盖 glibc 与标准允许的另一种实现。

销毁顺序固定为 clone 在前、原对象在后。这与一个常见失败现场相似,也能让脆弱版本在第二次 destructor 暴露问题。更完整的下游套件可以增加反向顺序、把两个对象分别加入 auth config 与 authenticator、再在 sanitizer 下清理,验证 owner 组合而非仅验证裸对象。

旧版 fuzz_ids.c 的生命周期过短:从输入创建 identity,然后立即销毁。它覆盖 parser 和单对象 destructor,永远不会调用有缺陷的 clone。新增 harness 先调用 hash、equals、matches 与 wildcard 检查,再遍历 identity parts,随后 clone 并销毁 clone。

fuzzer 还使用正常与极小 buffer 格式化 identity,最后销毁原对象。这样的调用序列能发现 value-specific formatter、enumerator 与 clone/destroy 交互。@# 两字节不需要 dictionary 或长时间变异,一旦 clone 被加入 harness,allocator diagnostic 很快就能报告 double-free。

这次改动说明 fuzz target 的覆盖率数字不足以代表对象协议。parser 行覆盖很高、样本数量很多,也可能从未执行复制、比较、序列化和双 owner 清理。对拥有 interface method table 的 C 对象,harness 应按真实生命周期调用所有公开方法,并改变销毁顺序。

回归环境最好启用 ASan/UBSan 与平台原生 allocator 检查。ASan 提供 allocation/free 栈,glibc 给出部署相关行为;两者互补。对 appliance 使用的定制 libc,还应运行原生 build,确认 malloc(0)free() 语义,以及补丁没有引入 ABI 差异。

验证还要覆盖正常 identity:FQDN、RFC822、IPv4/IPv6、非空 raw ID_KEY_ID、regex 与 wildcard。clone 后 type、encoding、match、hash 和格式化应保持一致,指针所有权按类型独立。这样才能证明安全变化没有改写认证匹配结果。

三份旧分支 patch 的函数宏、regex 支持和测试目录不同,回移不能只摘生产一行。每个维护分支都应在自己的框架里保留同一 pointer invariant,并用正反两种销毁顺序验证;测试还要至少在一种会为零尺寸请求返回非 NULL 的 allocator 上运行,避免平台偶然行为掩盖父版本缺陷。

fuzz corpus 应保留 @#、typed empty hex、正常 raw hex、奇数 nibble、separator 与带 NUL 数据,并确认普通 clone、regex clone、clone destroy 和 original destroy 都真正执行。生命周期动作可以扩展到 auth config add/clone/purge 等真实 owner 组合,但每次 transfer 都要遵守 API contract,不能让 harness 自己制造无关的 double-free。

修复后的负面断言同样重要:@# 仍应得到零长度 ID_KEY_ID,EAP 交换以正常身份不匹配或认证失败结束,而不是 parser error。这样才能证明补丁修的是所有权,而非靠拒绝语法绕过;格式化、part enumerator 和极小 buffer 也应保留,以维持 clone 前后的真实对象使用时间。

最后把结果绑定到产物。CI source revision、补丁清单、SBOM、package changelog、二进制 build-id 与部署 digest 应能相互指向;一句“test_clone_empty passed”既不能证明生产映像包含同一代码,也不能证明运行进程已经换到它。

CVE-2026-47895 修复与回归图:源码将 else if encoded.len 改为 else,test_clone_empty 断言原对象与克隆的零长度指针不同或同时为 NULL,fuzz harness 依次执行解析、哈希匹配、part 枚举、clone 销毁、格式化与原对象释放。
移动端可横向滑动查看补丁、断言与 fuzz 生命周期
图 4:修复保留身份值语义,只改变资源归属;单元测试直接写出 ownership invariant,fuzzer 则保证未来输入能走到 clone 与两次销毁。

7 网关修复要以运行进程为终点

strongSwan 常被嵌入发行版 package、容器、SD-WAN appliance、云 VPN image 或厂商固件。仓库里出现固定版本,只完成了供应链的一半;旧进程、旧容器副本和漏升的 HA 节点仍可接收 IKE。处置应从资产清单一路走到运行中 build-id、认证回归和隧道恢复。

临时缓解可降低窗口风险:停用 EAP/XAuth、把暴露移到已修复节点、在确认配置后取消本地 EAP-Identity request,并关闭不需要的 RADIUS group attribute parsing。它们都会改变认证能力或信任路径,需要以正式补丁收尾。

7.1 上游版本与发行版回移要分别验收

直接使用 upstream release 的环境应升到 6.0.7 或更高版本。自行维护 4.x、5.x、6.0.0–6.0.6 分支时,选取官方 CVE 目录中对应版本区间的 patch,记录 applied commit,并检查 clone_() 的非 regex 分支已经无条件赋值 clone->encoded = chunk_clone(this->encoded)

Ubuntu 的修复采用 backport。截至 2026 年 7 月 16 日,Canonical 列出的固定下限为:26.04 的 6.0.4-1ubuntu3.1、25.10 的 6.0.1-6ubuntu4.4、24.04 的 5.9.13-2ubuntu4.24.04.4、22.04 的 5.9.5-2ubuntu2.7。看到 5.9.x 并不能直接判为脆弱,必须比较完整 revision。

Debian tracker 同日显示 bookworm security 的 5.9.8-5+deb12u5、trixie security 的 6.0.1-6+deb13u6 与 forky/sid 的 6.0.7-1 已修复;bullseye 条目仍标记 vulnerable。旧生命周期产品还可能由 ELTS 或设备厂商维护,需查对应支持通道,不应套用其他 release 的版本号。

RPM 发行版、网络 appliance 与云镜像应以厂商公告为准。若公告只写“包含安全改进”而没有 CVE,可索取 source RPM、patch manifest 或构建证明。二进制字符串中的 6.0.x 只能说明基线,无法证明 downstream 是否回移一行修复。

运行态确认至少包含进程 PID、可执行文件路径、package revision、容器 image digest 与启动时间。更新 package 后,/proc/<pid>/exe 仍可能指向已删除 inode;这说明 daemon 没有重启。容器编排器也可能让旧 replica 因 drain 失败继续承载 UDP 500/4500。

HA 更新应一次只处理一位 active peer。先在 standby 安装固定 build、启动并完成健康检查,再迁移新建 IKE 流量;观察现有 SA 的 state synchronization 与重认证行为,随后升级原 active。若产品不支持无损状态迁移,应把 Child SA 重建时间纳入维护通知。

版本闭环之后运行正常认证矩阵:证书、PSK、每个 EAP method、XAuth、RADIUS fallback、reauth、MOBIKE 和配置中实际使用的 group mapping。安全补丁不应改变 value matching;一旦正常 identity 回归失败,应暂停 rollout 并独立定位原因。

失败路径同样需要验收。授权实验环境发送空 raw-hex identity,固定进程应拒绝认证并保持运行,日志中不应出现 allocator abort。反复执行并观察 RSS、SA count、worker pool 与 core directory,确认没有从 double-free 转为泄漏或卡死。

7.2 可执行处置清单应交付版本、配置、会话与证据四类结果

下面的步骤按变更工单可直接执行,每一项都有输入与验收结果。大型环境可把节点清单拆成批次,但不能把“已安装包”代替“运行进程已切换”。

  1. 建立节点级资产表。从 CMDB、云实例、Kubernetes workload、设备管理平台与监听 UDP 500/4500 的进程清单合并资产;每行必须包含主机或设备标识、HA 角色、环境、运行 PID、完整 strongSwan package revision、image digest、libc/allocator、外部可达网段与业务 owner,缺任一字段的节点不得标记盘点完成。
  2. 绘制认证入口。读取实际生效的 swanctl.conf、VICI 动态配置、旧 ipsec.conf 与 plugin 配置,为每条 connection 记录本地/远端 auth、EAP method、是否显式请求 Identity、XAuth backend、是否使用 xauth-eap、RADIUS delegation、Class/Filter-Id group parsing 以及 AAA server 地址;仅凭安装了某个 plugin 不得判断暴露。
  3. 确定修复下限。upstream build 要求版本不低于 6.0.7;Ubuntu 与 Debian 节点按本文列出的完整 package revision 或更新的官方 fixed revision 比较;设备固件必须取得厂商针对 CVE-2026-47895 的公告、补丁号或 source diff,无法证明 backport 的版本继续保持“待修复”状态。
  4. 先升级非活动节点。在 standby/低流量节点安装固定 package 或 image,明确重启 charon/charon-systemd,再用 PID 启动时间、/proc/<pid>/exe、build-id 和 package database 四项核对运行映像;发现 deleted inode、旧容器 digest 或旧 PID 时停止流量迁移。
  5. 执行身份回归。对生产使用的每个 EAP/XAuth/RADIUS 组合完成一次成功认证、一次错误凭据、一次空 identity、一次 @# 或等价 typed-empty-hex 失败清理;记录客户端结果、server log、IKE/Child SA 数量、进程存活与 sanitizer/allocator 输出,任何崩溃或持续内存增长都阻断上线。
  6. 验证高可用恢复。将新连接切向固定节点,测量 UDP 端口接管、IKE_SA_INIT、完整认证、Child SA 安装和业务流量恢复五个时间点;随后触发既有隧道 reauth/MOBIKE/DPD,确认身份后端没有因重连洪峰达到容量上限,并保存 failover 观测曲线。
  7. 滚动升级剩余节点。逐个 drain、修复、重启并重复运行态核对,不允许一次把所有 HA peer 下线;每批完成后从外部 vantage point 建立新隧道,并检查负载均衡、NAT-T 与路由策略确实把流量送到已修复实例,直到资产表中不存在旧 build。
  8. 回溯历史异常。在公告日前后至少覆盖组织日志保留窗口,检索 strongSwan 进程异常退出、glibc double-free/corruption、core、systemd restart 与 EAP/XAuth failure;以时间、源地址、IKE SPI、connection 和 RADIUS transaction 关联事件,保留原始 core 与 build symbols,避免只留下聚合告警。
  9. 关闭临时例外。若窗口期停用了 EAP、取消 Identity request、关闭 RADIUS group mapping 或收紧了 UDP 来源,在全节点固定并完成回归后按变更审批恢复必要功能;逐条验证功能与安全边界,把不再需要的高风险插件永久移出构建或配置。
  10. 形成审计证据包。归档官方公告、固定版本依据、节点前后版本、配置快照、变更单、测试流量授权、回归输出、HA 时间线和剩余例外 owner;报告结论必须区分“未使用受影响入口”“已临时隔离”“已运行固定代码”,只有最后一项可关闭补丁任务。

紧急窗口无法升级时,最强缓解是让受影响 daemon 停止接收不可信 EAP/XAuth。改用 RADIUS delegation 前,必须确认网关不本地请求 EAP-Identity、未启用 Class/Filter-Id group parsing,且 AAA 本身位于接受的信任边界。源地址 ACL、cookie 与速率限制只能压低机会和负载,不能修正 clone;自动重启还会让同一输入再次生效。

资产清单必须比较完整 package revision:Debian 系列保留 dpkg-query 结果,RPM 保留 NEVRA,容器保留 image digest,静态或自定义安装再结合运行版本与进程映射。设备固件若只显示产品号,应向厂商索取 CVE 修复矩阵、重启要求与 SBOM/补丁证明;没有维护通道的边界设备应进入替换或隔离计划。

回滚条件要在变更前写明:EAP 成功率下降、RADIUS group 丢失、HA 同步失败或 Child SA 无法恢复都可能触发回滚,但目标不能是让原脆弱节点重新公网承载。先切向另一固定节点,或临时关闭受影响入口;同时把空 raw identity clone/destroy 纳入持续回归,使后续 strongSwan、libc、编译器和 plugin 更新仍检查同一 ownership invariant。

上线观察至少跨过一次正常 reauth 周期,并比较变更前后的 IKE_SA_INIT、EAP 成功/失败、RADIUS latency、active SA、worker queue、RSS、restart 与 core。真实 IKE 认证比 UDP socket 探针更有意义:它能同时暴露 plugin、AAA 与 identity matching 回归,测试账户则应受限并避免把凭据写进命令行或日志。

关闭工单前,从负载均衡器、NAT-T、每个 HA member、灾备入口和可能的 anycast/DNS 路径反向抽样,建立外部隧道并核对接收节点的 PID/build-id。本文列出的发行版下限是 2026 年 7 月 16 日快照,合规系统应同步当前 tracker 并保存抓取时间,不能把旧 revision 固化成永久规则。

修复后若仍出现 heap abort,先比较地址、运行 build 与 clone 栈,不要自动判定补丁失效。6.0.7 还包含其他内存安全修复,设备 fork 也可能存在独立缺陷;保留这条 CVE 的源码级签名,才能把回归、旧进程和新问题分流。

暖纸手绘的 strongSwan 部署闭环:左侧健康节点继续承载青色隧道流量,另一节点经闸门排空到维护位;中间把两个身份抽屉共用的红色所有权卷替换成两卷独立青线,并让空身份经过 clone、双 owner 与分别析构的测试台;右侧节点重返集群,证据箱保存放大镜、无刻度时钟和两卷独立青线。
移动端可横向滑动查看部署闭环
图 5:一次只排空一个成员,在维护位证明旧共享所有权已经被两份独立资源取代,并让空身份的 clone 与分别销毁保持进程存活;节点通过认证、隧道和运行态核验后才回到集群。

8 沿着同一种代码气味继续审计

memcpy(clone, this, sizeof(...)) 是很有价值的相邻漏洞指纹,却不能直接等同于漏洞。完成主链后,我们在 strongSwan 6.0.7 固定树中枚举 clone method、整结构复制、条件式 chunk clone、chunk_from_hex() 调用与 identity 的下游持有者,再逐项核对结构体成员和 destructor。

目标是寻找独立的第二条链:外部可控输入产生特殊表示,clone 留下资源别名,两个 owner 可以分别析构,并形成可观察安全影响。只满足“写法相似”或“理论上有指针”的候选不会被命名为新漏洞。

8.1 四处整对象复制里,另外三处都能解释自己的所有权

固定树中,直接以 memcpy(clone, this, sizeof(private_*)) 复制完整对象的核心候选落在 identification.cseg_contract.cpts_comp_func_name.chost.c。四处都需要联合阅读结构定义、clone 与 destroy,grep 结果只负责生成候选。

pts_comp_func_name 保存 public method interface、vendor ID、component name 与 qualifier 等整数/枚举字段。对象内没有独占 heap pointer,clone 的 memcpy 复制的是标量与函数指针;destructor 只释放对象本身。两份对象没有可共同释放的下层资源,未形成同型 alias。

host.c 的 private object 把 IPv4/IPv6 sockaddr 放在内联 union 中,端口、family 与地址字节都属于对象存储。clone 分配同尺寸对象后 memcpy,destructor 只 free(this)。返回的 address chunk 指向各自对象内部,不是共享 heap allocation。

seg_contract.c 确实含有 linked_list_t *seg_envs,初看与 identity 更接近。clone 在 memcpy 的下一行立即执行 clone->seg_envs = linked_list_create(),覆盖浅拷贝来的 list pointer;其他成员为标量。clone 不复制当前 envelope 内容是该类型的业务语义,两位对象析构时各自销毁不同 list。

backtrace.c 也使用 memcpy,但目标是新分配对象尾部的 flexible frame array,复制源为 this->frames、目标为 clone->frames。地址数据本身只是调用栈 PC 值,destructor 只释放承载它们的新对象。这种“复制数组内容”与“复制拥有数组的指针”在文本搜索里容易混在一起。

network packet clone 提供了一个更好的条件示例。它以 this->data.ptr 判断是否存在底层缓冲,并在目标对象调用 chunk_clone(this->adjusted_data);长度为零但指针非 NULL 时仍会进入 normalization。sec_label 则同时深拷贝 binary encoding 与 NUL-terminated string。两者都让资源条件贴近所有权而非业务长度。

auth_cfg_t::clone_() 不做整结构复制。它枚举 rules,对 identity/group 调用各自 clone(),对 certificate 增加引用,对字符串执行 strdup(),对标量按值传递。CVE 会通过这里放大,因为 identity clone 本身有缺陷;容器 clone 的策略并未再制造一个独立 alias。

我们还沿 chunk_from_hex() 的调用点检查空输出。大量调用位于 CLI、配置加载、证书工具、TPM/PTS 与测试代码,输入信任边界和后续 owner 各不相同。单独得到 { ptr, 0 } 仍是合法资源状态;只有后续复制或条件式赋值把同一 pointer 交给两位 destructor,才构成同型问题。

本轮审计没有得到第二个可独立披露的漏洞。三处整对象复制候选均有可验证的安全原因,其他空 chunk 调用也没有同时满足远程控制、所有权别名和双析构。这个结果仍然有价值:它把搜索指纹、排除依据和剩余审计方向记录下来,后续版本可直接做增量比较。

审计分三层推进:先找显式 clone、整结构复制和直接成员赋值,再找条件式 chunk_clone(),最后从 destructor 反向枚举 free()chunk_free()、容器销毁与引用释放。候选只有同时满足不可信输入、稳定特殊表示、跨 clone/transfer 的多 owner,以及两条可执行危险清理路径,才从代码气味升级为漏洞链。

同一根因还要去重:另一 EAP method 抵达 identification_t::clone_(),只是 CVE-2026-47895 的新增入口;只有不同对象、allocation、修复点和影响链都独立成立,才可能成为相邻漏洞。6.0.7 release note 中其他 OOB read、NULL dereference 与 command injection 修复不共享这项 ownership 指纹,不能混入空 identity 结论。

clone 实现可以按 constructor 重建、逐字段深拷贝、引用计数、容器元素复制、flexible array copy 与整结构 memcpy 分类;最后一类优先级最高,却不是唯一风险。if (chunk.len) clone(chunk)if (ptr) strdup()if (count) alloc() 都应接受字段不一致状态测试,追问 constructor、错误路径与外部输入能否制造 zero/non-NULL 等表示。

静态规则可以把 destructor 会释放的成员作为 owned seed,要求整结构复制后的每条返回路径覆盖、增引用或显式标记 borrowed;debug build 则可用 allocation ID 断言 owned 地址不同,允许两边同时 NULL。宏、union 和条件编译会带来误报,但在对象 API 边界核对,仍比等待完整 IKE teardown 更早发现 constructor 引入的新状态。

后续增量审计只需在 private struct、constructor、clone、destructor 或 chunk helper 变化时重建 owner table,重点关注带 destructor 的 private pointer、特殊空表示与跨 task transfer。当前 6.0.7 树的同型候选已经逐项给出排除理由;若未来形成独立链,再用固定 commit、最小样本、owner graph、可确认影响和独立修复点证明它与本 CVE 的区别。

暖纸手绘的四站仓库审计:第一站把双抽屉共用的红线修成两条独立青线并分别送入剪切杯;第二站显示内联数据随各自柜体复制;第三站为浅拷贝后的对象换上全新空篮;第四站把柔性数组复制进第二只独立盒;四站证据汇入无文字笔记本和封存箱。
移动端可横向滑动查看仓库审计
图 6:整对象复制只负责产生候选。四站逐一把 clone 与 destructor 对账后,identity 的共享资源需要修复;内联字段、立即重建容器和独立柔性数组三类候选则各自给出了不形成双 owner 的排除证据。

8.2 结论:clone 的承诺必须覆盖值与生命周期

CVE-2026-47895 的决定性事实可以压缩为一句话:未认证 EAP 身份 @# 让 strongSwan 创建 { unique_ptr, 0 },旧 clone_() 先复制 unique pointer、再因长度为零跳过深拷贝,authenticator 与 auth config 最终对同一地址各执行一次 destructor。

它值得长篇复盘,因为每个局部动作都有背景。2007 年条件修复泄漏,2008 年语法支持二进制身份,2009 年 memcpy 减少重复赋值;组合后的安全前提却无人继续维护。代码评审若只看当前 diff,很难发现一条十七年前的条件已经换了含义。

面向维护者,最直接的规则是为 clone 建立成员级 ownership table。每个 pointer、chunk、string、container、compiled object 都要说明原对象是否独占、clone 如何取得独立资源、失败路径由谁清理。任何在整结构复制后保留的指针,都应有显式共享协议或随后的覆盖语句。

空值需要作为表示集合测试,而非单个值测试。{ NULL, 0 }{ unique_ptr, 0 }、空容器、interned pointer 与 borrowed view 在业务层可能相等,在 destructor 层可能完全不同。回归断言应观察 pointer 独立、引用计数或 owner flag,不能只比较长度和内容。

面向运营者,修复标准是 6.0.7 或发行版明确 backport,并确认运行中的所有 HA 节点已重启到固定映像。直接 EAP、内部 EAP、xauth-eap 和可选 RADIUS group parsing 决定窗口优先级;普通 XAuth、PPK 与 attribute certificate 路径可达性较低,也共享同一脆弱库代码。

面向检测与响应,allocator abort 是最可靠的现场入口。把 allocation、first free、second free 与 IKE/EAP 会话连接起来,能区分本 CVE、普通 heap corruption 和本地配置崩溃。网络中单独出现 @# 只是一条线索,进程版本、plugin、clone 时机与 core 才能给出结论。

strongSwan 的修复选择很干净:保留语法和认证逻辑,让所有非 regex identity clone 无条件通过已有 helper,零长度目标被规范化为 NULL。定向测试把“两个指针不同或同时为 NULL”写成不变量,fuzzer 则首次覆盖对象 clone 与双 owner 销毁。值语义和生命周期由此重新一致。

这封两字节的身份最终留下了一条普适经验:内容为空从来不等于资源为空。只要接口承诺返回一枚可以独立销毁的 clone,哪怕它没有携带一个字节,也必须拥有一条独立、可证明的结束路径。

产品团队可以把结论变成一条可执行门槛:安全边界上的 C 对象只要同时提供 constructor、clone 与 destroy,就必须覆盖空表示、多 owner 和反向销毁顺序,并把测试结果与构建产物绑定。研究团队则从同一不变量出发,先证明异常状态与 owner,再寻找不可信入口和影响,最后横向扫描同型实现;没有独立链时,排除理由同样要能复算。

当前固定版本、旧分支补丁以及 Ubuntu、Debian 回移都已给出修复路径。暴露节点无需等待利用细节成熟:更新版本、重启真实进程、完成认证回归并检索历史 crash。最终验收应能回答哪份远端字节创建对象、哪行 clone 改变 owner、哪两条 destructor 曾经汇合,以及哪一个运行 build 已经切断链路,而不是只留下一句“已打补丁”。

参考资料

  1. strongSwan:CVE-2026-47895 技术公告、影响范围、入口与缓解条件
  2. strongSwan 6.0.7 发布说明
  3. 上游修复提交 075323d8、单元回归与 fuzz harness diff
  4. strongSwan CVE-2026-47895 官方旧分支补丁目录
  5. 4.3.3–5.1.1 官方补丁
  6. 5.1.2–6.0.1 官方补丁
  7. 6.0.2–6.0.6 官方补丁
  8. 2007 年提交:cloning %any ID without zero-byte memleak
  9. 2008 年提交:支持 @#hex ID_KEY_ID
  10. 2009 年提交:以 memcpy 简化 identification_t clone
  11. strongSwan 身份解析与 raw hex 语法文档
  12. 6.0.7 identification.c:parser、clone 与 destructor
  13. 6.0.7 chunk.c:chunk_from_hex() 实现
  14. 6.0.7 chunk.h:chunk_clone() 与 chunk_free() 语义
  15. 6.0.7 eap_identity.c:EAP Identity 数据提取
  16. 6.0.7 eap_authenticator.c:server_process_eap() 与 apply_eap_identity()
  17. 6.0.7 auth_cfg.c:identity rule clone、purge 与 destroy
  18. 6.0.7 xauth-eap:username 到内部 EAP backend
  19. 6.0.7 eap-radius:peer clone 与 RADIUS group 属性
  20. 6.0.7 EAP-PEAP server identity 路径
  21. 6.0.7 EAP-TTLS server identity 路径
  22. 6.0.7 test_identification.c:test_clone_empty()
  23. 6.0.7 fuzz_ids.c:完整 identity 对象生命周期
  24. RFC 3748:Extensible Authentication Protocol 与 Identity method
  25. RFC 7296:IKEv2 交换与认证框架
  26. GNU C Library:零尺寸 malloc 返回唯一可释放指针
  27. Linux malloc/free 手册:free(NULL) 与 allocation 生命周期
  28. CVE.org:CVE-2026-47895 记录
  29. Ubuntu CVE tracker:固定 package revision
  30. Ubuntu USN-8407-1:影响与更新说明
  31. Debian security tracker:bookworm、trixie 与 unstable 状态
  32. GitHub:strongSwan 6.0.7 release tag

研究记录

9证据、对象与来源

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

9.1研究对象

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

CVECVE-2026-47895

strongSwan 身份对象克隆中的双重释放

修复版本strongSwan 6.0.7

2026 年 6 月 8 日发布的上游修复版本

修复提交075323d895f574424cfc4a5f491a1d388cdfda37

让全部非正则身份编码经过 chunk_clone()

最小输入40 23 (@#)

空 raw-hex ID_KEY_ID 表示

核心函数identification_t::clone_()

整对象复制后遗漏零长度非空指针的深拷贝

9.2事件时间

  1. 空编码克隆增加长度条件

    提交 418dbd624 为修复零字节泄漏而跳过长度为零的 chunk 克隆。

  2. 加入 @# 原始身份语法

    提交 86ab5636c 让空十六进制后缀可以抵达 chunk_from_hex()。

  3. clone() 改为整结构 memcpy

    提交 2147da40a 保留旧长度条件,同时先复制了 encoded 指针。

  4. 所有权修复合入

    提交 075323d895f5 修复分支并增加单元测试与 fuzz 生命周期。

  5. 公告与 6.0.7 发布

    strongSwan 公布远程入口、缓解条件和旧分支补丁。

  6. SOSEC 完成扩展源码复核

    沿 EAP、XAuth、RADIUS 与同型 clone 指纹完成整链路审计。

9.3来源与材料

  1. strongSwan:CVE-2026-47895 技术公告、影响范围、入口与缓解条件https://www.strongswan.org/blog/2026/06/08/strongswan-vulnerability-%28cve-2026-47895%29.html
  2. strongSwan 6.0.7 发布说明https://www.strongswan.org/blog/2026/06/08/strongswan-6.0.7-released.html
  3. 上游修复提交 075323d8、单元回归与 fuzz harness diffhttps://github.com/strongswan/strongswan/commit/075323d895f574424cfc4a5f491a1d388cdfda37
  4. strongSwan CVE-2026-47895 官方旧分支补丁目录https://download.strongswan.org/security/CVE-2026-47895/
  5. 4.3.3–5.1.1 官方补丁https://download.strongswan.org/security/CVE-2026-47895/strongswan-4.3.3-5.1.1_empty_id_clone.patch
  6. 5.1.2–6.0.1 官方补丁https://download.strongswan.org/security/CVE-2026-47895/strongswan-5.1.2-6.0.1_empty_id_clone.patch
  7. 6.0.2–6.0.6 官方补丁https://download.strongswan.org/security/CVE-2026-47895/strongswan-6.0.2-6.0.6_empty_id_clone.patch
  8. 2007 年提交:cloning %any ID without zero-byte memleakhttps://github.com/strongswan/strongswan/commit/418dbd624363d9712c0d883084962cf93ae01b2a
  9. 2008 年提交:支持 @#hex ID_KEY_IDhttps://github.com/strongswan/strongswan/commit/86ab5636c2c9085c70688910fc3cd1f90a38187d
  10. 2009 年提交:以 memcpy 简化 identification_t clonehttps://github.com/strongswan/strongswan/commit/2147da40a5d79ca7921c028d38fe9503feb42bfb
  11. strongSwan 身份解析与 raw hex 语法文档https://docs.strongswan.org/docs/latest/config/identityParsing.html
  12. 6.0.7 identification.c:parser、clone 与 destructorhttps://github.com/strongswan/strongswan/blob/6.0.7/src/libstrongswan/utils/identification.c
  13. 6.0.7 chunk.c:chunk_from_hex() 实现https://github.com/strongswan/strongswan/blob/6.0.7/src/libstrongswan/utils/chunk.c
  14. 6.0.7 chunk.h:chunk_clone() 与 chunk_free() 语义https://github.com/strongswan/strongswan/blob/6.0.7/src/libstrongswan/utils/chunk.h
  15. 6.0.7 eap_identity.c:EAP Identity 数据提取https://github.com/strongswan/strongswan/blob/6.0.7/src/libcharon/plugins/eap_identity/eap_identity.c
  16. 6.0.7 eap_authenticator.c:server_process_eap() 与 apply_eap_identity()https://github.com/strongswan/strongswan/blob/6.0.7/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c
  17. 6.0.7 auth_cfg.c:identity rule clone、purge 与 destroyhttps://github.com/strongswan/strongswan/blob/6.0.7/src/libstrongswan/credentials/auth_cfg.c
  18. 6.0.7 xauth-eap:username 到内部 EAP backendhttps://github.com/strongswan/strongswan/blob/6.0.7/src/libcharon/plugins/xauth_eap/xauth_eap.c
  19. 6.0.7 eap-radius:peer clone 与 RADIUS group 属性https://github.com/strongswan/strongswan/blob/6.0.7/src/libcharon/plugins/eap_radius/eap_radius.c
  20. 6.0.7 EAP-PEAP server identity 路径https://github.com/strongswan/strongswan/blob/6.0.7/src/libcharon/plugins/eap_peap/eap_peap_server.c
  21. 6.0.7 EAP-TTLS server identity 路径https://github.com/strongswan/strongswan/blob/6.0.7/src/libcharon/plugins/eap_ttls/eap_ttls_server.c
  22. 6.0.7 test_identification.c:test_clone_empty()https://github.com/strongswan/strongswan/blob/6.0.7/src/libstrongswan/tests/suites/test_identification.c
  23. 6.0.7 fuzz_ids.c:完整 identity 对象生命周期https://github.com/strongswan/strongswan/blob/6.0.7/fuzz/fuzz_ids.c
  24. RFC 3748:Extensible Authentication Protocol 与 Identity methodhttps://datatracker.ietf.org/doc/html/rfc3748
  25. RFC 7296:IKEv2 交换与认证框架https://datatracker.ietf.org/doc/html/rfc7296
  26. GNU C Library:零尺寸 malloc 返回唯一可释放指针https://sourceware.org/glibc/manual/2.42/html_node/Malloc-Examples.html
  27. Linux malloc/free 手册:free(NULL) 与 allocation 生命周期https://man7.org/linux/man-pages/man3/free.3.html
  28. CVE.org:CVE-2026-47895 记录https://www.cve.org/CVERecord?id=CVE-2026-47895
  29. Ubuntu CVE tracker:固定 package revisionhttps://ubuntu.com/security/CVE-2026-47895
  30. Ubuntu USN-8407-1:影响与更新说明https://ubuntu.com/security/notices/USN-8407-1
  31. Debian security tracker:bookworm、trixie 与 unstable 状态https://security-tracker.debian.org/tracker/CVE-2026-47895
  32. GitHub:strongSwan 6.0.7 release taghttps://github.com/strongswan/strongswan/releases/tag/6.0.7