漏洞

Linux IPv6 fraggap 记账错误如何导致越界写(CVE-2026-53362

CVE-2026-53362 是本机 UDPv6 splice 发送路径中的内核越界写;永久处置要求安装适用修复并启动该内核,随后用软件包身份、启动内核、boot ID 和行为回归共同验收。

手绘技术封面:脆弱的 IPv6 报文装配缺少 carry 的线性预留并写入 skb_shared_info,修复后的装配提前预留该段空间。
文章导航

研究依据复核 Linux CNA JSON、773ba4fe9104、ce650a166335、736b380e28d0 三个固定的上游及稳定分支 Git 对象与对应源码树、RFC 8200、五条稳定分支回移,以及截至 2026 年 7 月 27 日的 Red Hat、Ubuntu、Debian、SUSE 和 NVD 状态

来源Linux CNA JSON、不可变的上游及稳定分支 Git 对象、Linux 源码与网络文档、RFC 8200、发行版跟踪页及 SOSEC 源码复核

1 启动已修复内核才算收口:漏洞发生在本机 UDPv6 splice 发送路径

CVE-2026-53362 会让本地进程在组装 UDPv6 数据报时,把上一枚 skb 的分片遗留字节写过新 skb 的线性边界并进入 skb_shared_info。Linux CNA 给出的上游修复起点是 6.1.1776.6.1446.12.956.18.387.1.37.2-rc1;发行版内核必须按供应商公告和精确包谱系判断。安装 fixed 包只是发布动作,只有该内核已经启动并通过行为验收,主机才达到永久关闭条件。

入口由本机 splice()、普通 IPv6 UDP 套接字、持续的 UDP cork、scatter-gather 能力和特定分片几何共同组成。Red Hat 已公开确认可将破坏发展为任意内核读写、凭据覆盖、SELinux 绕过和容器逃逸,因此共享内核承载非可信租户的宿主应进入第一处置批次。互联网边界防火墙无法覆盖发送前的本机装配过程。

补丁和固定源码能够证明越界写及其目的结构;强提权链来自 Red Hat 的公开研究结论。本文把这两层证据分别标注,并以固定 Git 对象复核内存布局、版本历史和修复性质。

1.1 源码锚点与处置工单:__ip6_append_data() 的分页分支少预留了 fraggap

缺陷位于 net/ipv6/ip6_output.c::__ip6_append_data()。修复前的父提交在第 1664–1672 行fraggap 留在 pagedlen,却没有把它加入 alloclen;修复提交 736b380e28d0第 1664–1672 行把同一长度移入线性预留。

旧分支随后在第 1683–1692 行允许 splice-page 的负 copy 状态继续,修复版本在第 1683–1689 行删除了该例外。旧版本的 skb_put()、carry 复制和源 skb 裁短集中在第 1717–1733 行;修复后的对应序列位于第 1714–1730 行。这三组对照把分配、守卫、写入和旧对象裁短连成同一条可复核路径。

永久修复

对象
宿主机正在运行的内核,以及构建它的精确发行版 source package 或固定源码提交。
动作
采用供应商适用的修复包;严格跟随上游 stable tree 的构建分别以 6.1.1776.6.1446.12.956.18.387.1.37.2-rc1 为起点。完成重启或经供应商证明覆盖该函数变化的 live patch。
负责人
内核/平台团队负责包、启动与回退,工作负载负责人负责 canary 业务验收。

临时控制及代价

控制
按环境限制非特权用户命名空间、IPv6 套接字或 splice 权限;在已测试的具体路由上,可移除有效 NETIF_F_SG 以避开分页分支。
代价
命名空间限制会影响 rootless 容器和沙箱;路径阻断会影响应用;取消 scatter-gather 会增加复制、CPU 占用和时延并降低吞吐。
执行期
工单必须写入负责人、宿主池、验证方法和绝对到期日。offload 与路径控制需要持续强制并监测漂移,其到期事件是 fixed 内核成功启动。

验收证据

身份
保存供应商公告或源码证明、已装包、仓库与签名、启动内核标识、boot ID、启动时间和节点镜像。
行为
隔离插桩测试应观察到线性预留随 fraggap 增加、页面归属等量减少、carry 最高写入地址位于 skb_shinfo() 之前,并同时通过 KASAN、正常 UDPv6 发送及负对照。
关闭条件
源码、安装、运行与行为四项都指向修复状态;容器节点还要证明非可信工作负载只回到已验收宿主。

回退与撤除

回退
候选 fixed 内核发生业务回归时,排空节点并启动另一枚已知良好的 fixed 构建;无法立即完成时继续执行临时控制和隔离。
撤除
确认 fixed boot、boot ID、行为回归和节点调度证据后,撤销 offload、命名空间或路径限制,并验证恢复后的吞吐与 rootless 工作流。
回退边界
生产回退只接受另一枚已知良好的 fixed 构建;已知脆弱内核会重新打开缺陷。

1.2 先分清 skb 的线性 head、非线性 frags 与 skb_shared_info

struct sk_buff 保存网络报文的管理信息。它指向一块连续的 head 缓冲,其中从 datatail 是当前线性内容,tailend 是可用空间。大块负载还可以留在外部页面里,由非线性 frags 描述。skb_shared_info 紧接在 head 缓冲的 end 之后,保存分片数量、offload 状态和页面描述符;它是控制元数据,不属于报文字节。

分片切点落在旧 skb 内部时,切点之后的 fraggap 必须先从旧 skb 读出,再复制到新 skb 的线性 head,最后裁短旧 skb。新 skb 若只给头部留位置,carry 的第一个越界字节便会抵达后面的 skb_shared_info

[发送者] -> [旧 skb] -> [carry: fraggap] -> [新 skb 线性 head] -> |end| [skb_shared_info]
五对象小图:发送者影响报文内容与长度;旧 skb 提供 carry;新 skb 必须为 carry 预留线性空间;其 end 后方就是共享控制元数据。

1.3 splice_to_socket() 产生内部标志,MSG_MORE 让同一数据报继续累积

入口从 fs/splice.c::splice_to_socket() 开始。它遍历管道缓冲、取得页面引用,并用每轮最多十六项的 bio_vec 数组描述页面、页内偏移与长度。十六项限制单次批处理;循环仍可在同一套接字和同一 UDP cork 上消费后续数据,因此数据报总长可以跨过路径 MTU。

函数构造内核自己的 msghdr 并设置 MSG_SPLICE_PAGES。面向用户态的 sendmsg() 入口会清理这类内部标志,相同位值无法由应用直接注入。应用可通过常规 splice() 选择这条路线;splice_to_socket() 随后调用 sock_sendmsg(),再由 udpv6_sendmsg() 进入 UDPv6 协议发送。当调用带 SPLICE_F_MORE 或管道还有后续缓冲时,内核再加入 MSG_MORE,使同一枚逻辑数据报继续累积。

触发需要本地执行和普通 IPv6 UDP 套接字,无需 raw socket。seccomp、LSM、命名空间策略与 IPv6 配置能够改变可达性;容器共享宿主内核,因此资产判断要落到节点当前启动的内核。现场关联应同时保留管道与 socket 描述符、进程和命名空间身份、实际路由设备、MTU 与接口能力。

手绘 splice 入口:管道页面被整理成有限的 bio_vec 批次,内核为消息加入内部标志,再交给 UDPv6 套接字发送路径。
图 1:管道提供受引用页面,splice_to_socket() 分批并加入内部标志,MSG_MORE 让后续批次继续进入同一 UDPv6 数据报。

2 cork 把几批页面压成一枚再也装不进单帧的数据报

UDP 常被讲成“一次调用对应一枚数据报”,cork 则允许发送者分几次添入字节,由内核保留路由、头部和累计长度,最后放出完整数据报。这里的 MSG_MORE 让同一数据报继续累积,页面负载因而越过 IPv6 路径 MTU,从一枚 skb 变成分片链。队列历史还会改变头部归属,所以两次长度相同的提交,也可能因到达时队列是否为空而得到不同分配状态。

udpv6_sendmsg() 根据套接字 UDP cork 选项或当前消息的 MSG_MORE,得到本地变量 corkreq,据此判断写入是否立即结束。函数还区分一枚已经挂起的数据报与全新数据报;前者会沿用首批字节建立的状态。后面的错配发生在这种连续写入中。

第一批贡献承担传输层头部。写队列一旦已有 skb,ip6_append_data() 给后续数据传入的 transhdrlen 就是零,因为 UDP 头已经由首批承担。这是单枚逻辑数据报避免每片重复预留传输头的正常做法。脆弱的页面公式一面减去这个零,一面忘记遗留的 fraggap,零值因而把分类矛盾暴露得格外清楚。

路由提供有效 MTU,IPv6 输出据此判断逻辑数据报能在每枚 skb 中容纳多少内容;扩展头链、分片策略和设备能力都会改变结果。页面支撑的字节仍是应用负载,此时已经受协议几何约束。分配器必须给出两个准确数字:skb 线性 head 真正要预留多少字节,以及页面描述符无需复制便能代表多少字节。

内存破坏要求累计长度跨过多 skb 分片门槛。小数据报不会产生上一枚 skb 的尾部搬运,fraggap 保持为零。错误公式始终存在于源码中,触发则依赖 continuation、分片与非零 carry 共同形成的状态转换。

2.1 udpv6_sendmsg() 让分次提交保持为同一次逻辑写入

新数据报到来时,udpv6_sendmsg() 先解析目的地址、流、路由与 cork 参数,再调用 ip6_append_data();若已有待完成帧,它带着早先字节所在的写队列进入 append。两条路会汇入同一底层函数,却携带不同历史。只看最终函数签名,无法知道传输头和首批扩展头是否在上一次调用中已经消耗。

挂起状态属于套接字,因此并发与错误处理在运维上仍有意义。UDP 会串行化 cork 写路径、追踪累计长度,并在 append 或最终 push 失败时拆除不完整状态。完全串行的发送者仍能稳定触发缺陷:continuation、页面分支和分片条件同时成立时,线性容量与页面归属会对同一段数据给出矛盾分类。

ip6_append_data() 是一层整理头部贡献的包装,然后才委托 __ip6_append_data()。队列为空,它转交数据报开头所需的传输头长度;已有 skb,它便传零。队列状态已经编码“本 skb 是开头还是延续”。这个零只省掉传输头,后续分片头仍需预留。

continuation 中的每枚后续 skb 仍包含 IPv6 Fragment 头及相关扩展头材料。归零的是 transhdrlenfragheaderlen 保持非零。此时线性分配仍覆盖头部,却可能恰好少掉 carry 的长度。

最后一批不再携带 MSG_MORE 时,UDP 才把挂起帧推向输出。错误复制早在 append 过程中完成,驱动和对端尚未参与。校验和、分段、释放或设备交接可能是第一个读取损坏元数据的环节,所以崩溃栈常指向更下游,而真正的因果栈早先已经经过 udpv6_sendmsg()

手绘 UDPv6 cork 转换:第一批数据携带传输头,后续管道页面继续加入,合并后的数据报按路径 MTU 划分成多枚 IPv6 分片。
图 2:cork 让多批页面维持为同一枚数据报。首批承担 UDP 头,后续批次沿用这次写入,直到 IPv6 输出不得不创建下一枚 skb。

2.2 五道门把消息留在专用的页面分配分支

__ip6_append_data() 不会仅因最初系统调用是 splice 就选择页面分配。相关分支要求存在有效负载,排除不兼容的 HDRINCL 模式,确认输出设备声明 NETIF_F_SG,还要求复制器正是 ip_generic_getfrag,并看到内核生成的 MSG_SPLICE_PAGES。任何一项缺失,同一批逻辑字节都可能落入另一套安全的分配计算。

NETIF_F_SG 表示输出路径能够用分散的页面片段描述数据,不必把全部内容复制进 skb head。虚拟和软件设备同样可以声明此能力,它并不证明最终帧会交给某一块物理网卡。暴露测试应在目标命名空间里查询实际路由选中的设备,因为目的地址、路由或接口变化,都可能让分配器走进另一条分支。

getfrag == ip_generic_getfrag 把优化限制在已知的复制契约中。隧道或转换路径的专用回调可能需要查看、改写甚至合成字节,不能沿用同一页面捷径。可达性由套接字历史、标志、设备能力、辅助函数身份与分片几何共同决定。

普通非分页分支把 alloclen 设为 fraglen;后者已经体现 datalen,而 datalen 又包含 fraggap,所以线性区足够容纳遗留字节。错误集中在优化分支对正确总量的拆分:实际要复制的 carry 被归入页面长度,线性容量因此不足。

此时只差非零 carry:管道页面提供特殊迭代器,内核赋予来源标志,cork 延续同一数据报,设备允许 scatter-gather,通用 helper 保留快捷路径。IPv6 分片接着在已经建好的 skb 上选择对齐边界;旧 skb 越过边界的一小段就是暴露分类错误的 fraggap

设备能力必须在 skb 实际选中的出口观察,不能拿资产系统里的“主网卡”代替。容器路由常经过 veth、bridge、tunnel 或 loopback,它们的 NETIF_F_SG 集合可能与物理适配器不同。若以关闭 offload 暂时降险,应核验叠加后的有效能力、评估强制线性复制的性能代价,并防止重启、驱动重载或编排再次打开设置。

3 旧 skb 留下一条由对齐刻度塑成的窄片,新 skb 必须把它接住

分片用的尺子不能在任意字节落刀。IPv6 用八字节为单位编码非末片的偏移,因此负载切口必须落在这张网格上。cork 已经装好的 skb 可能越过新选中的切线,Linux 不能丢掉尾端内容,只能把它们搬到下一片的数据开头,紧随该片的线性头部。这次搬运只是两个 skb 布局之间的过桥动作,接收端不会看到重复字节;旧对象与新对象必须在不改变数据报顺序的前提下完成交接。

源码把这段伸出的内容叫作 fraggap。它表示一次临时归属转换:旧 skb 尾部的字节成为新 skb 内容,成功复制后旧 skb 随即缩短。每个字节在逻辑数据报中只出现一次,变化的是它在内核对象之间的存放位置。

页面支撑的应用负载仍会产生一段需要线性空间的内容。协议头进入新 skb head,fraggap 也由 Linux 直接复制到同一区域。字节的输入来源只说明原始表示;即将执行的写操作决定最终存储类别和所需容量。

切线会按此刻的有效 MTU 与头部长度重新计算。ip6_append_data_mtu() 帮助纳入路由和分片限制,append 循环再求出按协议单位对齐的 maxfraglen。扩展头组成、路由 MTU 或现有 skb 长度哪怕只改变一项,相同的应用输入也可能产生不同 carry。

这里不存在一个到处通用的“神奇长度”。非零 fraggap 来自旧 skb 实际长度、对齐后最大值与当前头部几何之间的关系。回归测试在这个关系附近扫过若干尺寸,直接验证内存范围。源码性质比任何单个报文夹具更可靠:每复制一字节 carry,线性区就必须预留一字节。

3.1 maxfraglen 把 MTU、分片头空间与 RFC 8200 八字节网格合到一把尺上

append 循环通过 ip6_append_data_mtu() 得到工作 MTU,扣除头部义务后再对可分片部分向下对齐。复核源码中的核心表达式是 ((mtu - fragheaderlen) & ~7) + fragheaderlen - sizeof(struct frag_hdr)。位掩码把字节数压到八的整数倍,后面的加减则从对齐后的负载视角回到循环采用的 skb 长度口径。

fragheaderlen 表示这次构造里必须位于可分片字节之前的头部,sizeof(struct frag_hdr) 则对应 IPv6 Fragment 头自身。若把任何一项误当成负载,切线就会平移,表面上仍是八的倍数,线上偏移却已经错误。公式的形状正对应 RFC 8200 对不可分片头、Fragment 头和以八字节单位编码偏移的可分片部分所作区分。

源码注释说明,分片头预留与对齐在相关比较中合计消耗八到十五个字节。该范围解释可用最大值相对“MTU 减头部”估算的对齐差异;实际 carry 由旧对象已经实现的长度计算。

得到 maxfraglen 后,上一枚 skb 超过边界时,代码计算 fraggap = skb->len - maxfraglen。正数表示需要把旧 skb 多出的字节搬入下一片,零表示无需搬运,负数由循环外围条件处理。

这段算术发生在本机 IPv6 发送端:内核按 RFC 8200 几何切割一枚将要发出的逻辑数据报。非可信本地工作负载能够影响数据内容和长度,宿主内核据此分配新 skb。

扩展头与路由变化会改变切分尺子,缺陷仍来自 carry 的存储分类。额外的不可分片头会增加 fragheaderlen,隧道或虚拟链路会降低有效 MTU,两个数据报之间的路由刷新也可能换设备。实验记录必须保存当时的完整头链与目的路由;只把某台实验机的应用长度抄到另一台,可能得到零 carry,进而误判内核已经修复。

3.2 skb_copy_and_csum_bits() 搬走窄片,pskb_trim_unique() 再合上旧帧

新 skb 分配完毕、头部铺好后,函数调用 skb_copy_and_csum_bits(),从旧 skb 读取 carry 范围,并写到新对象的线性尾端。helper 能跨越 skb 的不同存储形式读取,同时更新校验和状态;目的地却只是由新 skb head 推导出的普通指针。它相信调用者已经保证那段地址确实可写。

写入起点位于 fragheaderlen 和可能存在的 transhdrlen 之后。非首枚 skb 的传输头长度为零,分片头仍在,所以 carry 会排在新页面负载之前。插图使用深蓝头部、琥珀 carry 和紫色页面表现物理排序;页面描述符记录 carry 时,新 skb 的线性容量并不会相应增加。

carry 复制成功后,pskb_trim_unique() 把上一枚 skb 缩到 maxfraglen。“unique”约束防止修改仍被其他所有者共享、且对方期待旧长度的对象。先复制成功再裁短,确保逻辑数据报没有丢字节;复制若失败,错误清理也必须保留足够状态来丢弃半成品,不能假装交接已经完成。

校验和处理解释了 helper 选择与操作顺序。数据改变对象归属时仍需保持正确 checksum 语义;函数可以准确读取并计算恰好 fraggap 字节的校验和,同时因调用者尺寸模型错误而写出目的分配。目的容量必须由调用者在写入前保证。

交接约束可以直接写成两项:skb_copy_and_csum_bits() 写入前,新 skb 的线性 tailroom 必须容纳 fragheaderlen + transhdrlen + fraggap;页面描述符只能覆盖剩余的 datalen - transhdrlen - fraggap。旧分支维持总量,却同时违反两种分类。下一章对照这两项计算。

手绘三步 fraggap 搬运:八齿对齐梳在旧报文框上标出切线,双手抬起越出的琥珀色窄片,新帧在深蓝头部后接住它,旧帧随后裁到刻线。
图 3:carry 是一次真实的线性复制,随后源 skb 才被裁短。进入目的对象后,这些字节只属于新 skb 的线性区域。

4 两本账保持总量一致,却把 carry 分给不同存储类别

datalen 已经包含 fraggap,预期输出总长始终正确。错误发生在总量拆成线性分配与页面长度时:线性侧少了 carry,页面侧等量增加。只检查总字节数或发送成功会漏过这种问题;有效判据要在复制前检查存储归属,在复制后核对最高写入位置。

相关循环先计算 datalen = length + fraggap。其中 length 是尚待放置的新贡献,加数则承认上一枚 skb 有内容要带过来。后面的 fraglen 再依据本片可表示的数量推导。这个上层合计只说本轮共有多少字节,没有说明 carry 要住在哪里,所以总数正确与存储分类危险可以同时成立。

修复以前,页面分支使用 alloclen = fragheaderlen + transhdrlenpagedlen = datalen - transhdrlen。前式只替 skb head 的头部预留空间,后式把其余内容连同 carry 全部交给页面描述符。实现随后把 carry 按值复制进线性区,没有为这些字节增加新的页分片。两条表达式与紧接着发生的动作描述了不同现实。

代码还用同组变量推导本地 copy。脆弱状态下,copy = datalen - transhdrlen - fraggap - pagedlen;代入旧 pagedlen,结果正好化成 -fraggap。负值直接暴露分类矛盾:页面长度已经包含这些字节,复制路径仍把它们作为 carry 写入线性区。

随后,分配代码以 skb_put(skb, fraglen - pagedlen) 扩大可写线性尾部。pagedlen 偏大,扩出的范围便只覆盖头部,没有包括琥珀窄片。后来的搬运采用真实且为正的 fraggap 作为写入长度;目的指针走过所有 carry 字节,skb tail 却没有同步跨过那一段。

skb 依据线性 end 标记和尾随控制数据解释物理布局,没有额外类型信息可以补回漏掉的容量。调用者少申请 fraggap 后,skb_shared_info 仍紧接在过短数据区之后。修复因此必须在分配前统一存储分类。

这是一个守恒成立、位置错误的问题。令 L 表示线性归属,P 表示页面归属,旧代码和补丁都满足 L + P = total。安全性质还要求互斥放置:经线性指针写入的每个字节只属于 L,由分片描述符代表的每个字节只属于 P,两集合没有重叠。旧分支中的 fraggap 破坏了这次划分。

4.1 脆弱算式让 copy 为负,真正的 carry 写入却仍为正

把旧公式写在同一块板上,矛盾就清晰了:同一个 fraggap 先加进 datalen,随后留在 pagedlen 里,计算 copy 时又被明确减去。负结果描述页面挂载完成后还剩多少普通迭代器内容;独立的 carry 操作仍以正数长度从旧 skb 搬运数据。

datalen = length + fraggap
alloclen = fragheaderlen + transhdrlen
pagedlen = datalen - transhdrlen
copy = datalen - transhdrlen - fraggap - pagedlen
     = -fraggap

把 carry 记作 G、头部记作 H、新页面负载记作 P。物理构造要求线性预留 H + G,页面归属为 P;旧分支只预留 H,却把页面长度登记为 P + G。两类空间仍合计为 H + P + G,总长断言会通过,目的线性区却短了 G

更早的代码把负 copy 当作错误。后来一次 splice 专用变更发现,MSG_SPLICE_PAGES 路径可能通过挂页来消费迭代器数据,因此允许该状态继续。在漏掉 carry 的分类仍然存在时,这个例外放行执行,随后到达另一次独立、长度为正的 fraggap 复制。

非分页分支可作为源码内部的对照组。它直接从 fraglen 设置 alloclen,而该长度包含为本 skb 选中的全部贡献;所有负载都线性复制时,carry 自然有空间。比较相邻分支是一种有效审计方法:若普通路径预留完整分片,优化路径将其拆开,后者就必须证明每个字节只落入一种存储类别。

只在 skb_copy_and_csum_bits() 周围寻找长度检查,很容易漏掉这种问题。请求的读取长度相对源 skb 完全有效,指针计算也符合预期,逻辑数据报总长一致;破损契约发生在更早的分配阶段。审阅需要同时核对源范围和目的容量,确认分配器已经预留写操作将要触达的全部字节。

C 的整数类型无法表达存储类别。alloclenpagedlendatalenfraglen 都是以字节为单位的标量,编译器无法辨认线性容量与外部引用内容。注解、更准确的 helper 名称或带类型包装能降低混淆,回归断言则应放在分类产生并被 skb_put() 消费的语义接缝处。

有符号性还需单独审查。copy 要能表示负余量,守卫才能发现分类矛盾;分配长度必须避免包装成巨大的无符号请求。本次负值来自确定的代数结果并流入 splice 例外,单纯报告 overflow 或 truncation 的静态分析可能看不见它;正确分类后断言 copy >= 0 更贴近设计性质。

4.2 补丁只搬动一次 fraggap,总长不变,物理归属重新一致

提交 736b380e28d0 直接改正分类:页面分支现在计算 alloclen = fragheaderlen + transhdrlen + fraggap,并计算 pagedlen = datalen - transhdrlen - fraggap。逻辑数据报总长保持不变,同一项 fraggap 从页面归属移入线性容量,与实际复制目的地一致。

alloclen = fragheaderlen + transhdrlen + fraggap
pagedlen = datalen - transhdrlen - fraggap

守恒让这次修改容易复核。固定后的两种空间相加仍得到 fragheaderlen + datalen,补丁没有复制或丢弃负载;改变的是归属证明:页面描述符只覆盖仍留在页面中的字节,所有写在头部之后的内容都进入 skb head 预留。无论 fraggap 为零还是正数,或当前 skb 是否承担 transhdrlen,这条性质都成立。

pagedlen 改正后,fraglen - pagedlen 恰好增加 carry 的长度。随后的 skb_put() 会在任何目的写入发生前,让 tail 越过头部和 carry;skb helper 和后续检查以此划定有效线性数据。复制现在终止于 skb_end_pointer(skb) 以内,尾部控制结构仍在报文之外。

补丁还从负 copy 检查里删除 MSG_SPLICE_PAGES 例外,并移除解释该状态的旧注释。页面归属不再包含 fraggap 后,合法专用路径不需要让 copy 跌到零以下。删掉例外增强了修复:未来算术若再次漂移,更可能立即报错停下,不会把矛盾状态继续当作正常输入。

手绘修复对比:脆弱状态把 carry 计入页面长度却复制到不足的线性区域;修复状态在线性区域预留 carry,并等量减少页面长度。
图 4:修复改变存储分类并保持数据报总长不变,使线性容量与真实复制范围一致。

5 skb_shared_info 紧接在线性报文边界之后

Linux 官方网络文档把 skb head 缓冲画成连续布局:headroom、线性报文数据、可增长的 tailroom,以及数据区末端之后的共享控制元数据。carry 跨过的正是这个对象内边界,随后进入解释非线性负载的结构。相邻关系由 skb 布局确定,具体字段偏移则随架构和构建配置变化。

skb_end_pointer(skb) 标出 head 数据区末端,skb_shinfo(skb) 从同一位置解析 shared-info 地址。网络栈借此稳定存放页面分片描述符与 offload 状态,同时维持紧凑的 skb 管理对象。线性预留偏小时,漏掉的字节会直接抵达这段元数据。

struct skb_shared_info 以若干紧凑控制字段开头,之后才是分片数组。当前定义包含 flags、元数据长度、nr_fragstx_flags、GSO 参数、frag_list 关系以及页面分片描述符。第一个超出线性范围的 carry 字节进入控制状态;架构和配置决定它具体覆盖哪个字段。

本地发送者可以影响数据报内容,分片几何决定其中哪一段成为 carry,因此写入内容受长度与头部状态共同约束。源码差异证明连续越界写,任意地址写仍需额外的塑形与受损元数据消费路径。可控内容进入 skb 解释字段已经足以要求尽快修补宿主内核。

第一处可见故障可能发生在 __ip6_append_data() 返回以后。受损分片计数会误导遍历,变化的 offload 状态会影响 checksum 或分段,所有权标志破坏则可能改变释放行为。最终崩溃任务可能是 softirq、驱动 worker 或清理线程,发起 splice() 的进程也可能已经退出;这种时间距离符合元数据延迟消费的机制。

struct sk_buff 是保存指针、长度、协议字段和 head 引用的独立管理对象。skb_shared_info 位于所分配 head 缓冲末端,让克隆出的多个 skb 头共享非线性数据状态。本 CVE 的 carry 跨过 head 数据范围;崩溃记录应同时保留 skb 管理对象与 head 缓冲地址。

5.1 skb_shinfo() 把第一个越出字节映到共享报文状态

官方 head 布局可读成四段连续区间:data 之前的 headroom,从 data 到 tail 的线性内容,从 tail 到 end 的空闲 tailroom,最后是 skb_shared_info。正常情况下,skb_put() 只在允许范围内推进 tail;脆弱分支推进得太短,原始 carry 目的指针却继续越过分配器认定的线性报文空间。

skb_shinfo(skb) 实际锚定在 skb_end_pointer(skb),坏写目的地与共享元数据之间的距离正是少预留的长度。补丁通过两个算术项让所有头部与完整 carry 都在物理 end 以前获得线性容量。

最前面的 flags 描述 skb 克隆之间共享的性质与非线性存储。其中相关位 SKBFL_SHARED_FRAG 会影响分片引用如何处理,附近的 tx_flags 参与发送时间戳等流程。carry 并不一定要写到指针才能改变后续控制流;结构开头的位域与计数已经能决定网络核心会执行哪些输出和清理操作。

nr_frags 告诉后续代码,分片描述符数组中有多少项有效。消费者据此限制页面数据遍历、构建 scatter-gather 列表、计算长度并释放引用。若覆盖使计数升高或降低,后续阶段可能跳过仍被拥有的页面,或把未初始化区域当作描述符。具体结果取决于写入值与消费者,但字段契约本身已经说明其安全含义。

GSO 大小、分段数、类型和链接分片关系位于结构更深处;可达范围取决于 fraggap、实际布局与前方字段。源码确认一段连续越界进入结构开头。现场应保存 shared-info 原始字节、内核配置和匹配的布局信息,避免把另一版本的字段偏移套到当前样本。

克隆语义进一步放大共享区的重要性。数个 skb 头可以指向同一 head 及其页面描述符,引用计数和 flags 决定何时允许改写或释放。受损的 SKBFL_SHARED_FRAG 或相邻计数,可能在原发送者已经看不到对象之后继续影响它。这里确认的是共享状态的持续影响范围;具体 use-after-free 链仍需要独立证据。

5.2 源码确认越界写,Red Hat 确认提权链

KASAN 构建能在写入发生处截住问题,并给出最干净的因果栈;生产内核可能直到分片遍历页故障、释放时 refcount 告警、GSO 状态异常或驱动报错才暴露。调查者应把这些下游症状与近期本地 UDPv6 splice 活动、实际启动内核、接口 MTU、命名空间及 skb 分配细节关联。

Red Hat 公告明确写道,该缺陷可以串成任意内核读写、凭据覆盖、SELinux 绕过和容器逃逸;其评级为 Important,并称修复覆盖所有受影响的 Red Hat 产品。这项供应商公开结论把处置优先级提升到本地提权与容器边界破坏。其他发行版仍按各自布局、补丁栈与实际部署包核验。

同一公告把演示过的容器路线与创建网络命名空间的能力联系起来,在默认 RHEL 10 上通常可经非特权用户命名空间取得。设置 user.max_user_namespaces=0 能缩小这条路线,同时会破坏 rootless Podman 和部分沙箱。该设置属于有业务代价的临时暴露控制,永久关闭仍要求启动 fixed 内核。

源码确认的入口始于本地 splice()MSG_SPLICE_PAGES 由内核生成。Linux CNA 在 7 月 18 日补充 CVSS 3.1 评分 7.8 HIGH,向量为 AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H;SUSE 的产品评分包含采用网络攻击向量的 8.59.0。这些分数来自不同发布者和产品判断,排查与遥测仍按本文复核的本地进入机制执行。

这些结论的证据强度不同:写入尾随 skb_shared_info 由补丁、CNA 与源码共同确认,字段含义来自固定版本结构定义和官方 skb 文档,强提权效果由 Red Hat 公开确认。截至 7 月 27 日,已复核的 CNA、NVD 及 Red Hat、Ubuntu、Debian、SUSE 页面均未报告在野利用;这个结论只覆盖这些官方页面。NVD 已在 7 月 22 日完成 Initial Analysis,新增通用 Linux 内核 CPE 范围与 NVD-CWE-noinfo,NIST CVSS 仍为 N/A。

若政策允许,crash dump 扩展可只打印 skb 的 head、data、tail、end 偏移和少数 shared-info 字段,省略报文内容。把 tail 与“头部加 carry”的预期高水位比较,并标记不可能的 nr_frags。相较于大范围 tracing,这种定向收集显著减少敏感数据,同时保留判断越界所需的几何信息;还须保存符号 build ID 及配套 BTF 或 debug-info 身份。

手绘 skb 内存剖面:脆弱状态让 carry 越过线性区末端并进入 skb_shared_info,修复状态先在线性区预留 carry,外部页面仍由分片描述符引用。
图 5:线性报文空间结束之处就是共享分片与发送控制元数据的起点,缺少的线性容量直接决定越界距离。

6 一张决策表连接三次提交、六个上游起点与供应商产品状态

上游历史有三个独立角色。773ba4fe9104 在 2022 年引入分页分配拆分并漏掉 carry 的线性份额;ce650a166335 在 2023 年允许 splice-page 的负 copy 状态继续,使后面的正长度 carry 复制可达;736b380e28d0 在 2026 年把 fraggap 移入 alloclen、移出 pagedlen,并删除旧例外。定向修复保留零拷贝与合法 splice 行为,恢复每个字节只有一种物理归属的性质。

五个 stable backport 是独立 Git 对象:14200d435af965fb14cbebb046f201f8b4c36374fb9edf72e9eacf19281e。它们与主线对象共同形成六个分支内修复起点。发行版可独立采用引入、可达性和修复提交,因此产品判断要绑定精确 source package、补丁栈、架构与 flavor;裸版本排序只适用于确知严格跟随相应上游分支的构建。

手绘三段提交历史:第一段建立页面分配拆分,第二段允许 splice-page 的负 copy 状态继续,第三段把 fraggap 移入线性预留并删除例外。
图 6:引入、可达性与修复属于三次提交;下游核验需要同时识别它们,才能判断精确源码包的状态。
层级对象或权威入口截至 2026 年 7 月 27 日的核验结论主机判定与动作
上游引入773ba4fe9104 · 2022-07-18 · v6.0-rc1分页优化少记 carry 的线性预留。检查发行版源码是否含等价拆分;缺少该代码可支持 code absent。
上游可达性ce650a166335 · 2023-08-03 · v6.6-rc1splice-page 的负 copy 状态继续执行。与引入提交组合时形成 CNA 描述的越界路径;下游可能独立回移。
上游修复736b380e28d0 及五个 stable 对象首次修复版本为 6.1.1776.6.1446.12.956.18.387.1.37.2-rc1自建上游内核按对应分支起点判断;发行版仍按包谱系判断并重启。
Red HatRHSB-2026-009Resolved、Important;RHEL 10 受影响且修复已发布,RHEL 9 不受影响。采用适用 erratum,重启 RHEL 10 宿主并保存 NEVRA、启动内核和 boot ID。
Ubuntu官方 CVE 跟踪页Medium;通用 26.04 与 24.04 为 vulnerable,通用 22.04 及更老版本为 not affected;flavor 状态各异。逐一解析精确 source package、发行版与 cloud/HWE/OEM flavor。
Debian安全跟踪页与 DSA-6381-1bookworm 6.1.176 和 trixie 6.12.94 为 vulnerable;bookworm-security 6.1.177、trixie-security 当前 6.12.96、forky 7.1.3 与 sid 7.1.4 为 fixed;trixie 的固定起点是 6.12.95,bullseye 不含脆弱代码。保留仓库、源码包和启动证据,区分修复与 code absent。
SUSE产品跟踪页总体 Pending、Important;产品行混合为 Released、In progress 与 Affected;CVSS 8.5/9.0 为 SUSE 评分。选中精确产品、模块和内核行,将 In progress 或 Affected 主机保持在处置队列。
NVDNVD 条目7 月 22 日完成 Initial Analysis,新增通用 Linux 内核 CPE 与 NVD-CWE-noinfo;NIST CVSS 仍为 N/A,Linux CNA 评分为 7.8 HIGH。NVD 的通用 CPE 范围不能替代发行版包谱系;保留检索时间和评分发布者。

这张表承担全文唯一的版本与产品判定。状态是带日期的公开快照;供应商更新 flavor、仓库或产品行时,重新打开仍为 vulnerable、pending 或 unknown 的资产。已审 source package 还要连接到二进制签名、安装记录与当前启动内核,才具备主机级证明。

7 一台主机依次通过已安装、正在运行、boot ID 与行为四道验收

主机级处置以一份资产记录贯穿全程。记录先绑定 machine ID、节点池、租户信任、工作负载、发行版 source package 与供应商结论,再按下面顺序推进;任何一步缺证据都保持 pending。容器镜像更新不会改变宿主的 __ip6_append_data(),Pod 和租户必须映射到节点当前启动的内核。

  1. 已安装。包管理器记录 fixed 构建的完整标识、仓库、签名、架构与 flavor,并与公告或已审源码相连。镜像频道、版本锁、phased update 和 initramfs 结果也写入同一记录。
  2. 正在运行。重启后读取运行内核标识、/proc/version_signature 或供应商等价信息,并核对 bootloader 实际加载的镜像。fixed 包与旧运行内核并存时,状态仍是 pending。
  3. boot ID。保存 /proc/sys/kernel/random/boot_id、启动时间、machine ID、镜像 digest 与 rollout revision。新 boot ID 将本次运行状态同安装前的旧内核切开,也防止重建节点继承另一台机器的关闭记录。
  4. 行为。canary 在隔离插桩内核上扫描分片边界,要求 fraggap 进入线性预留、页面长度等量减少、carry 保持在 skb_shinfo() 前,并通过 KASAN、普通 UDPv6、隧道、容器网络与负对照。
  5. 恢复调度。调度器证明脆弱节点已经排空,非可信工作负载只返回完成上述验收的节点;临时 offload、命名空间和路径控制此时才撤除。

live patch 只有在供应商明确覆盖该 CVE 或精确函数变化、模块已在当前 boot 激活并可审计时,才替代重启步骤。候选 fixed 构建发生网络回归时,节点应排空并启动另一枚已知良好的 fixed 构建;临时控制继续执行,直至新 fixed boot 完成。

手绘主机验收链:固定源码与供应商结论连接到已装软件包,随后连接运行内核、boot ID、行为回归和调度器恢复记录。
图 7:一台主机的关闭证据按顺序连接源码或公告、已装包、运行内核、boot ID、行为结果和工作负载恢复;任何缺口都会把状态留在 pending。

8 结案只保留负责人、证据与临时控制撤除

负责人。内核或平台团队拥有 fixed 包、启动内核、bootloader 与回退;工作负载负责人拥有 canary 业务验收和节点恢复;安全负责人追踪 unknown、临时控制到期与供应商证明。每个宿主池都保留生成资产队列的查询条件,任何节点启动批准集合之外的内核时自动重开事件。

证据。主机记录连接第 6 章的供应商或源码结论、第 7 章的已装包、运行内核、boot ID、行为结果以及排空和恢复调度。fixed、vulnerable、code absent 与 unknown 描述源码状态;缺少其中任一主机级收据时,状态保持 pending。

撤除。fixed 内核已经启动、carry 高水位保持在 skb_shinfo() 之前、普通 UDPv6 与负对照通过、非可信工作负载只返回已验收节点以后,撤销命名空间、IPv6、splice 或 offload 限制,并确认吞吐、时延、rootless 容器和沙箱恢复。候选内核发生业务回归时,排空节点并转向另一枚已知良好的 fixed 构建,临时控制继续生效。

本地 pipe-to-socket splice 经内核生成的 MSG_SPLICE_PAGES、持续的 UDP cork 和 IPv6 分片对齐抵达脆弱分支;错误分类把 carry 计入页面长度,却按线性指针写进 skb_shared_info。固定提交在 __ip6_append_data() 分配前恢复线性预留和负 copy 守卫。源码对象、供应商包、当前 boot 与行为结果指向同一修复状态以后,事件关闭。

研究记录

9证据、对象与来源

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

9.1研究对象

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

CVECVE-2026-53362

Linux IPv6 页面分配越界写入 skb_shared_info

源码文件net/ipv6/ip6_output.c

受影响的 IPv6 输出实现

入口路径splice_to_socket -> sock_sendmsg -> udpv6_sendmsg

本地管道页面进入 UDPv6 cork 的路线

引入提交773ba4fe9104a64a54d1c00f0fb6ffb95def2b03

在 v6.0-rc1 之前出现的页面零拷贝分配拆分

可达性提交ce650a1663354a6cad7145e7f5131008458b39d4

在 v6.6-rc1 之前出现的 splice 负 copy 继续执行

修复提交736b380e28d0480c7bc3e022f1950f31fe53a7c5

主线 fraggap 归属修复

内存分界skb_end_pointer / skb_shared_info

线性报文空间结束后紧接共享控制元数据

9.2事件时间

  1. 页面分配拆分合入

    提交 773ba4fe9104 为 IPv6 零拷贝输出避免部分复制,同时留下存储分类错配。

  2. splice 继续执行合入

    提交 ce650a166335 允许 splice-page 的负 copy 状态继续,使潜伏算术变为可触发破坏路径。

  3. 主线修复合入

    提交 736b380e28d0 把 fraggap 计入线性分配,并从页面长度移除。

  4. Linux CNA 记录发布

    CVE-2026-53362 记录非特权可达性、稳定分支修复以及写入尾随 skb_shared_info 的结果。

  5. NVD 完成初始分析

    NVD 加入通用 Linux 内核 CPE 范围与 NVD-CWE-noinfo;NIST CVSS 仍为 N/A。

  6. SOSEC 完成源码与供应商状态复核

    SOSEC 核对主要源码历史、Linux CNA 评分、当前供应商包状态、机制图与修复验收。

9.3来源与材料

  1. CVE-2026-53362 的 Linux CNA 记录https://www.cve.org/CVERecord?id=CVE-2026-53362
  2. NVD 初始分析、CPE、CWE 与评分归属https://nvd.nist.gov/vuln/detail/CVE-2026-53362
  3. 主线 fraggap 记账修复 736b380e28d0https://git.kernel.org/stable/c/736b380e28d0480c7bc3e022f1950f31fe53a7c5
  4. IPv6 页面零拷贝引入提交 773ba4fe9104https://git.kernel.org/linus/773ba4fe9104a64a54d1c00f0fb6ffb95def2b03
  5. splice-pages 可达性变更 ce650a166335https://git.kernel.org/linus/ce650a1663354a6cad7145e7f5131008458b39d4
  6. Linux 6.1 稳定分支修复对象https://git.kernel.org/stable/c/14200d435af9a9eeb444f529fc2f689a236b7962
  7. Linux 6.6 稳定分支修复对象https://git.kernel.org/stable/c/65fb14cbebb0cd0eff903a22d33537ddc8b95769
  8. Linux 6.12 稳定分支修复对象https://git.kernel.org/stable/c/46f201f8b4c39633a1fa3dc12459f506d470993d
  9. Linux 6.18 稳定分支修复对象https://git.kernel.org/stable/c/6374fb9edf72c67a118a2c214a0dddd04c921e0a
  10. Linux 7.1 稳定分支修复对象https://git.kernel.org/stable/c/e9eacf19281ea2498b36291b56c9606118c2d74e
  11. 修复版本 fs/splice.chttps://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/splice.c?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5
  12. 修复版本 net/socket.chttps://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/socket.c?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5
  13. 修复版本 net/ipv6/udp.chttps://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv6/udp.c?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5
  14. 修复版本 net/ipv6/ip6_output.chttps://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv6/ip6_output.c?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5
  15. 修复版本内核 socket 内部标志https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/socket.h?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5
  16. 修复版本 sk_buff 与 skb_shared_info 定义https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/skbuff.h?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5
  17. Linux sk_buff 几何文档https://docs.kernel.org/networking/skbuff.html
  18. Linux splice(2) 接口手册https://man7.org/linux/man-pages/man2/splice.2.html
  19. Linux sendmsg(2) 接口手册https://man7.org/linux/man-pages/man2/sendmsg.2.html
  20. Linux UDP 套接字手册https://man7.org/linux/man-pages/man7/udp.7.html
  21. RFC 8200 IPv6 规范https://datatracker.ietf.org/doc/rfc8200/
  22. Red Hat RHSB-2026-009 安全公告https://access.redhat.com/security/vulnerabilities/RHSB-2026-009
  23. Red Hat 内核勘误 RHSA-2026:34911https://access.redhat.com/errata/RHSA-2026:34911
  24. Red Hat 严重性分级指南https://access.redhat.com/security/updates/classification
  25. Ubuntu CVE-2026-53362 软件包跟踪页https://ubuntu.com/security/CVE-2026-53362
  26. Debian CVE-2026-53362 软件包跟踪页https://security-tracker.debian.org/tracker/CVE-2026-53362
  27. Debian 安全公告 DSA-6381-1https://www.debian.org/security/2026/dsa-6381
  28. SUSE CVE-2026-53362 产品跟踪页https://www.suse.com/security/cve/CVE-2026-53362.html
  29. fraggap 修复公开补丁讨论https://patch.msgid.link/[email protected]