「KVM逃逸+Linux内核漏洞+CVE-2026-99999拆解:32字节泄露,打穿虚拟机边界」"

admin 2026-10-22 11:43:33 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

「KVM逃逸+Linux内核漏洞+CVE-2026-99999拆解:32字节泄露,打穿虚拟机边界」

原创

句芒安全实验室
句芒安全实验室

句芒安全实验室

2026年10月21日 13:44
上海

去阅读

2026 年 8 月 18 日,Vercel 为自家 Sandbox 挂出 100 万美元的悬赏,请人来打破它的隔离边界。

两周后结果公布:1285 份报告,付出 24.2 万美元,只认了一个 Critical。

那份 Critical 是第 3 天交的。9 月 4 日,报告进了 Linux 内核安全联系人和网络维护者的邮箱。10 月 14 日,修复进了 Linus 的主线树。

它叫 PwnEscape,编号 CVE-2026-99999,评分 9.3,CWE-787。

听起来像是 KVM 的事。补丁却打在 net/ipv4/ip_output.c,改的是 IPv4 网络栈。

这就是这篇要拆的东西:一个跑在虚拟机里的 guest,怎么在不碰 KVM 源码的前提下,让宿主内核自己把已经检查过的 IP 头改掉,然后把宿主内存当成 IP options 发回给自己。

先把事实钉住

项目 内容
名称 PwnEscape
编号 CVE-2026-99999
类型 CWE-787,越界写;实际路径是先越界读,再越界写
评分 cve.org 9.3 CRITICAL;Red Hat 8.8(CVSS 3.1)
位置 宿主 Linux 内核 IPv4 网络栈,不是 KVM
引入 2008 年(Linux 2.6.27,tun/tap 的 virtio 网络头支持)与 2010 年(packet socket 校验和卸载)
修复 6785011f8b16 ,Michael S. Tsirkin,2026-10-14 进主线
发现者 Paulos Yibelo,PWNAI Research
披露 2026-08-20 首份报告,2026-09-04 私报内核,2026-10-14 修复进主线

三条能力,原文分得很清楚:

  1. 泄露宿主内存。guest 持续发包,宿主内存就持续出现在发出去的包里。这一条 Vercel 说在自己的生产沙箱上演示过。
  2. 崩掉宿主。两个包,宿主整机宕,上面所有 VM 一起没。这一条在研究者自有的 KVM 和 Firecracker 上演示。
  3. 在宿主上以 root 执行代码。这一条同样在自有 KVM 和 Firecracker 上,四次 true-cold boot 跑通。

第三点最容易被标题党拿去做文章。原文自己写了一句限定:这不是对任何厂商生产环境里 root 命令执行的声明。泄露那一条才是 Vercel 生产环境确认的。

这个区分很重要。漏洞研究的可信度,很多时候就建立在这类句子上。

评分差异也值得看一眼。cve.org 给 9.3,攻击向量 AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H,不需要权限。Red Hat 给 8.8,把 PR 标成 L。差别在于\"谁能触发\":一个是不可信 guest,一个是本地攻击者。

Red Hat 的公告里写得更直接:本地攻击者也能触发,不跑虚拟机的系统里也有这段代码。所以受影响的不只是虚拟化宿主。

AlmaLinux 8、9、10 全部受影响,RHEL 7 到 10 的 kernel 和 kernel-rt 都挂在受影响列表上。代码在树里躺了大约 18 年。

三个字段,不是描述,是授权

Linux 算校验和不是每次都算。它可以带着一个\"还没算完\"的状态把包往下传。sk_buff 里记着三个值:

skb->ip_summed     CHECKSUM_PARTIAL  // 校验和还欠着
skb->csum_start    // 从哪开始累加
skb->csum_offset   // 从那里到输出字段的偏移
write location = csum_start + csum_offset

这套机制本身是正常的、文档里写着的功能。发送方向上,网卡或者软件辅助函数 skb_checksum_help() 把校验和算出来,写到指定字段。在格式正确的 TCP/UDP 流量里,那个字段就是传输层校验和。

问题在于,内核并不验证\"格式正确\"。

这三个值不是单纯的描述性元数据。它们授权了一次对包内存的写入。谁能影响它们,它们就必须在从进入到算完的每一步变换之后依然成立。

而 guest 恰好能影响它们。guest 通过 virtio 网卡发包时,可以带 VIRTIO_NET_HDR_F_NEEDS_CSUM,随包给出 csum_start 和 csum_offset。宿主收下,标成 CHECKSUM_PARTIAL,把这次写入推迟到后面某个消费点。

一个来自低信任侧的写地址,加上一个延迟执行的内核写操作。这是这套组合的第一层。

两套坐标,隔着一个以太网头

sk_buff 里有好几个\"包从哪里开始\"的概念:

skb->head            底层分配的起点
skb->data            当前数据指针,处理头部时会前移
skb->network_header  网络头在 head 里的位置
skb->csum_start      存下来的校验和起点
skb_headlen(skb)      当前线性区里有多少包数据

入口侧的历史检查用的是 skb_transport_offset(skb),这个偏移是相对当前 skb->data 视图的。它能挡住\"在当前视图里明显太靠前\"的校验和起点。

它证明不了另一件事:当包里还有额外链路层结构时,这个起点离网络头到底有多远。

原文把这两个不等式摆在一起:

transport_offset >= minimum_network_header_length
transport_offset - network_offset >= minimum_network_header_length

这两行不等价。差的就是 L2/L3 的坐标错位。前者是\"帧内偏移\",后者才是\"网络头之后\"。

入口检查发生在链路层头还在线性数据里的时候。后面处理 L3 时,以太网头被跳过了。检查通过了,写回位置却没有跟着重新对齐。

这就是第一个信任失败:低信任来源的校验和元数据进入了宿主包处理流程,却没有被约束到后面代码真正会用的那个头上。

检查在前,改写在中,消费在后

真正有意思的是顺序。

宿主入口看到一个正常的 IPv4 包,按 20 字节头解析。分片和路由基于这个状态做决策。之后,输出分片走到那个还欠着的校验和。

修复前的 ip_do_fragment() 顺序,简化后是这样:

if (skb->ip_summed == CHECKSUM_PARTIAL)
    skb_checksum_help(skb);   // 延迟写入,就在这里落地

iph = ip_hdr(skb);             // 之后才拿 IP 头指针
/* 分片逻辑从 iph->ihl 推导头长度 */

skb_checksum_help() 本身没问题。它在这里出问题,是因为畸形的元数据让它的写入落到了已经被解析成 IPv4 头的那些字节上。

给泄露定形的是 IPv4 的 ihl:4 个 bit,单位是 32 位字。正常 IPv4 包的第一个字节同时装版本号和 IHL。在研究者的受控场景里,它可以被写成从 0x45 变成 0x4f。

版本号那 4 个 bit 没变,还是 4。IHL 从 5 变成 15,头长度从 20 字节变成 60 字节。

包视图 首字节 IHL 解释出的 IPv4 头长度
已解析状态 0x45 5 words 20 字节
延迟写回之后 0x4f 15 words 60 字节

前面那些阶段从来没有认可过一个 60 字节的头。它们是在 20 字节头上做的决策。后面的消费者把这个被改过的字段当成了权威。

原文把这个叫\"语义上的 time-of-check/time-of-use\",但不是线程之间那种竞态。执行可以是完全确定的。隔开检查和使用的那个\"时间\",是一串正常的网络操作,中间夹着一次受攻击者影响的延迟写。

这是这套组合的第二层,也是最值得记住的一层。

32 字节是怎么出去的

Linux 的包缓冲区不要求所有字节都在一块连续的线性内存里。sk_buff 有一个线性头,后面可能跟着非线性分片数据。

这个研究场景里,重组之后,逻辑上的线性数据长度只有 28 字节:

20 字节   原始 IPv4 头
 8 字节   第一个分片里可用的载荷
────────
28 字节   skb 的逻辑线性头

延迟的校验和操作让分片逻辑相信 IPv4 头有 60 字节。于是它把原来 20 字节头之后的 40 个字节当成 IPv4 options。

这 40 个字节里,8 个确实在线性头里,但它们本来是载荷。接下来的 32 个字节不属于这个包的逻辑线性数据。

skb_headlen(skb) = 28,后面的拷贝长度是 60。差额是 32 字节。

这里必须说清楚一个限定。原文明确写了:这是对 sk_buff 有效逻辑线性包数据的越界读,不一定越过整个分配器对象或某个 slab 的边界。宿主完全可能在线性数据之后还物理映射着分配的尾部空间。

读到那些字节,并且把它们塞进一个要发出去的 IP 头,仍然越过了机密性边界。它们是宿主内存,不是发送方作为逻辑包提供的字节。

然后 IPv4 分片逻辑用这个被改过的头形状生成出站分片。最后发出去的包里,这些字节以 IP options 的样子出现在外面。

这就是两句话的区别:

  • \"这里可能读过了缓冲区\"
  • \"宿主把发送方逻辑包之外的数据发了出去\"

前者是一个内部状态。后者是一个外部可观察的产物。

证明为什么要做对照

原文在证据设计上花了篇幅,这一点值得抄下来。

他们把\"身份组\"和\"活跃组\"分开。身份组里原始头长度不变;活跃组里后面的操作改变了它的解释。他们检查了三件事:最终发出去的包结构上是否合法;已知逻辑数据之后是否出现了非零字节;这些字节是否与发送方自己放的守护数据不同。

他们还用了一个独立的应用层回执,和原始抓包观察并列。两个信号一起看,才能区分\"宿主真的改写并发出了\"和\"这是构造包或者抓包的假象\"。

第一轮有边界的泄露,每个活跃组复现 32 字节。原文说他们把这些内容当作敏感数据处理,不需要保留无关的宿主原始内存来证明边界被越过。

另外他们在研究者自有的、可丢弃的 Linux 6.12 和 7.x 宿主上确认了一次宿主可用性故障。

从读到写:64 字节

一个可复现的、攻击者可控的宿主内存泄露,在多租户环境里已经很严重。它暴露了 guest 本来不该看到的一个信任域里的信息。它也改变了漏洞研究的难度:一个能用的读原语,可以帮你确认宿主内存状态,让更多内存操纵的假设变得可测试。

在自有的 KVM 实验环境里,研究往前走了一步。用宿主内存观察加上受控的分配行为,他们演示了选定的 64 字节宿主内核写。用这个能力,在一个有边界的实验链条里执行了一个宿主 root 助手程序,并把一个宿主生成的标记返回给 guest。四次独立的 true-cold boot 完成了这条链。

所以原文用了 \"guest-to-host escape\" 这个说法。它不是一个从越界读推出来的结论,也不是对任何厂商生产环境 root 命令执行的声明。

分配器敏感的写链、对象生命周期控制、还有针对不同 hypervisor 的适配,原文说要另写一篇。他们还预测:较弱配置上的利用会在几周内出现,默认 hypervisor 配置上的利用会在一个月内出现。

这个预测能不能成立,我不知道。但它至少说清了从 32 字节到代码执行之间还差什么。

补丁堵了五个点,没堵源头

上游接受的修复是 6785011f8b16,标题就叫 ipv4: validate checksum_start before completing checksum。

Tsirkin 在 include/net/ip.h 里加了一个辅助函数:

static inline int ip_check_csum_start(const struct sk_buff *skb)
{
    if (unlikely(skb->csum_start <
                 skb->network_header + ip_hdrlen(skb)))
        return -EINVAL;
    return 0;
}

核心不变量就一句:

checksum start >= IPv4 network-header start + parsed IPv4 header length

在 csum_offset 非负的正常情况下,这保证了最终的校验和输出位置不会落进 IPv4 头里。

它不证明那个位置是合法的 TCP 或 UDP 校验和字段。这是一个独立的协议有效性问题。补丁只做了保护已经被信任的 IPv4 网络头所需要的那一步。

修复加在了五个 IPv4 校验和完成的消费点上,六个文件被改。每个点上的关键新顺序是:先校验校验和起点,再完成校验和。

if (skb->ip_summed == CHECKSUM_PARTIAL) {
    err = ip_check_csum_start(skb);
    if (!err)
        err = skb_checksum_help(skb);
    if (err)
        goto fail;
}

补丁自己的提交信息写得很清楚:这是纵深防御措施,更完整的入口校验还需要更多工作。原文也写了,最初的讨论里考虑过没有 VM 的 TUN/AF\_PACKET 场景。

所以这条修复的形状是\"堵消费点\",不是\"堵源头\"。

补丁小,不等于问题完。这里有三件事没做完:

  1. 入口侧没有完整校验 csum_offset 是否真的落在 TCP/UDP 校验和字段上。
  2. TUN / AF\_PACKET 场景没有完整重写。原始讨论里提到过。
  3. 以后新加第六个消费点,不会自动继承这个检查。

第三条是运维最该记住的。一个检查加在五个地方,第六个地方要人记得加。

分发层面的现实也值得看一眼。截至 10 月 16 日 AlmaLinux 的公告,RHEL 8、9、10 都标着受影响、没有修复,AlmaLinux 自己把上游修复回移到三个分支,放在 testing 仓库里请社区验证。公告明确写了:没有 workaround,补丁就是修好的内核。

AlmaLinux 抢在上游发行版前面发补丁,这个决定由他们的技术指导委员会 ALESCo 批的。理由是:一个让 guest 读、崩、接管宿主的内核缺陷,在树里躺了快二十年。

对运维来说,结论不是\"看到已修复就结束\"。要看这个补丁堵的是源头还是消费点,要看还有哪些入口没被覆盖。

延迟元数据信任反转

原文给这套机制起了个描述性的名字:deferred-metadata trust inversion,延迟元数据信任反转。

它由三次不同的信任转移叠出来:

来源信任:一个信任度更低的 guest 或进程,描述了一个宿主稍后会执行的操作。

解释信任:包在处理链路层头的时候换了表示形式,内核在某个特定布局下解析字段。

时间信任:一个延迟操作在另一个内核消费者使用之前,改掉了已经解析过的状态。

原文自己也标了边界:这个名字是对机制的描述。它的积木都是已知的,被利用的很多基础原理都是公开文档里写着的;新的、有后果的结果,是通过这些 Linux 网络交互拿到的一条具体的宿主边界利用链。

它不是说其他协议、操作系统或者产品有漏洞的证据。

这句话很重要,因为它把\"可复用的审计方法\"和\"已证实的漏洞清单\"分开了。

可以拿去用的五个问题

以后看任何虚拟化、容器、网络卸载、设备透传组件,先问这五句:

  1. 哪些字段是不可信侧填的? virtio 头、sockaddr、offload 参数、ioctl 参数。
  2. 它是不是\"只描述、不立刻用\"?CHECKSUM_PARTIAL、GSO、延迟释放、延迟映射。
  3. 包或者请求中间会不会被变形? 解封装、分片、NAT、隧道、协议转换、VLAN。
  4. 校验点和消费点是不是同一个阶段? 不是,就必须重新对齐元数据。
  5. 新加的消费点会自动继承检查吗? PwnEscape 的坑就是补在五处,第六处没有自动继承。

第三问是最容易被漏掉的。因为变形是正常功能,不是异常路径。以太网头被跳过、VLAN 被剥掉、包被分片、地址被 NAT 改写,每一步都是设计好的行为。每一步之后,元数据描述的到底是哪个包,需要重新回答一次。

哪些地方值得按这个模型去审

下面这些是审计假设,不是漏洞清单。原文明确说,它的结论不是其他协议、系统或产品有漏洞的证据。我按这个前提列。

TUN / TAP 与 AF\_PACKET。原文说这是没有 VM 的次要入口,最初的讨论里考虑过。这两个接口都能让用户态程序直接给内核递校验和元数据。

容器网络。veth、bridge、docker0,Kubernetes 的 CNI 做 NAT、隧道、GSO。容器共享宿主内核,距离比 VM 更近。不可信容器加老内核,是同一类延迟元数据的风险面。

智能网卡与卸载后端。SmartNIC 把校验和和 GSO 卸载交给硬件或者用户态后端,vhost-user 解析 virtio 网络头。如果后端信任 guest 给的 csum_start,再透传给内核,就是同一个模式。

设备透传。VFIO、vDPA 下 guest 能控制 descriptor、queue、命令元数据。设备后端如果把这些元数据当成可信,就是同一类问题。原文说这类目前没有看到 PwnEscape 同源的漏洞,但审计问题是一样的。

netfilter / nftables 规则引擎。规则匹配用的是旧包视图,后面的 mangling、SNAT、DNAT 改了头,下一条规则信的是改完的。这是策略类的\"延迟生效\"问题。

历史先例可以拿来类比,但不能混为一谈。runc 的 CVE-2019-5736、CVE-2024-21626,特权容器挂 /proc、/dev,共享内核加能力过大。它们不是校验和卸载,但都是\"低信任侧影响宿主某个延迟执行的动作\"。

AI 在这件事里到底做了什么

这是我看原文最在意的一段,因为它和标题党的说法差得很远。

原文的分工写得很清楚:

  • 人:定范围、定证据标准、判真假、搭实验环境、指挥调查。
  • GPT-5.6 Sol:源码级分析、探索攻击路径、质疑已有假设。
  • Omega-Pwn:pwn.ai 的实验性研究 harness,结合了他们自己的安全架构和自动化,以及 SingularityNET 和 BGI Labs 的研究者为这个项目开发的 OmegaClaw 的元素。相比传统 LLM harness,它支持更长的研究视野、持久化的战役状态、并行协作调查、以及跨复杂研究会话累积假设、失败实验和证据。

3 天拿到第一个原语,API 成本低于 700 美元。

AI 不是\"自动打出宿主 root\"。它是把源码遍历和假设生成加速了。证据阶梯还是人来定边界:泄露、写、root 是三个不同的证据声明。源码审查本身不证明泄露,泄露本身不证明写,实验里的执行结果也不会变成生产环境执行的声明。

原文还写了一句我觉得最该被引用的话:这套循环更接近于

模型或者研究者提出一个不变量违规 → 构建一个有边界的测试 → 检查对照和证伪案例 → 保存证据 → 和有经验的研究者迭代 → 请独立评审者攻击这个结论 → 实现并重测一个利用

他们用这个循环搜索更大的空间,同时不为证明标准降级。

这句话是这篇文章里最实用的部分。它说的不是\"AI 会打洞\",而是\"AI 让搜索空间变大,证明标准不变\"。

隔离边界不等于 hypervisor 边界

最后说一个观念上的事。

流行的心智模型是:VM 隔离住在虚拟机监视器里。很大一部分确实住在那里。

但一个受攻击者控制的工作负载,不只和 arch/x86/kvm/ 打交道。它和一个完整的宿主操作系统打交道,里面有大量为性能优化过的、高度可组合的接口。其中一些接口是性能契约。

如果元数据跨过了信任边界,每一个延迟消费者都必须继承正确的不变量。如果包在这些不变量被检查之后还能被变形,那么这些不变量必须在新的表示形式下依然成立。

这个 bug 看起来像一个畸形的校验和提示,最后变成内存泄露和 guest 到宿主的逃逸原语。问题不在于 IPv4 有多难解析。问题在于 Linux 的不同部分被允许信任同一个包的不同版本。

收口

一句话公式:

低信任侧提供元数据
+ 宿主延迟写回
+ 中间包被重新解释
= 信任反转

给运维的一句话:别看到\"已修复\"就结束。看补丁堵的是源头还是消费点,看还有哪些入口没被覆盖,看你自己的内核在不在受影响范围里。

给做沙箱的人一句话:隔离边界不是一个文件,是一串接口。你审了 VMM,只审了其中一段。

参考:

  • 原文:https://pwn.ai/blog/pwnescape
  • 上游修复:6785011f8b16,ipv4: validate checksum_start before completing checksum
  • CVE 记录:CVE-2026-99999
  • Vercel 挑战总结:https://vercel.com/blog/findings-from-the-1-million-dollar-vercel-sandbox-challenge
  • AlmaLinux 公告:https://almalinux.org/blog/2026-10-16-pwnescape-cve-2026-99999/

「KVM逃逸+Linux内核漏洞+CVE-2026-99999拆解:32字节泄露,打穿虚拟机边界」" 网络安全文章

「KVM逃逸+Linux内核漏洞+CVE-2026-99999拆解:32字节泄露,打穿虚拟机边界」"

本文深度拆解CVE-2026-99999漏洞,该漏洞位于宿主Linux内核IPv4网络栈而非KVM,通过guest可控的校验和元数据实现越界读写,可泄露宿主内存、崩溃宿主乃至执行代码。漏洞存在约18年,修复补丁6785011f8b16仅堵住五个消费点未堵源头,影响RHEL及AlmaLinux多版本,运维需关注补丁覆盖范围。
这个圈子,需要愿意较真的人" 网络安全文章

这个圈子,需要愿意较真的人"

本文作者以白帽子视角讲述在src平台提交漏洞后遭遇评级争议的亲身经历,指出规则模糊、审核标准不一、申诉流程冗长等问题,强调系统是否核心与漏洞风险应独立评估,呼吁平台执行规则一致、减少白帽子额外沟通成本,并提及AI普及后src生态变化及平台整顿无效报告现象。
中国战略科技力量最早的6个国家实验室" 网络安全文章

中国战略科技力量最早的6个国家实验室"

本文系统梳理中国最早获批的6个国家实验室,涵盖同步辐射、正负电子对撞、核物理、材料与海洋等领域,详述其核心装置、历史沿革与最新进展,并延伸介绍2017年后20余家新型国家实验室体系,展现中国战略科技力量从单点突破走向体系化协同的演进脉络。
评论:0   参与:  0