漏洞

KVM 只认房号,没看身份牌——重走 Januscape(CVE-2026-53359

Januscape(CVE-2026-53359)让 KVM/x86 在同一 GFN 改变 role 后继续复用旧 shadow page,留下可能活过页面的 rmap 状态;提供不受信嵌套虚拟化的平台应重启 L0 至供应商当前支持且包含修复的内核,直接跟随 kernel.org 者截至 2026 年 7 月 27 日采用 7.1.5、6.18.40、6.12.98、6.6.145 或 6.1.178,并在源码、L2、迁移与日志验收前暂停相关调度。

手绘嵌套机房围住一块悬挂的双色内存面板;检查员手持两枚黄铜标记,一根砖红色反向映射细线从面板横穿房间,延伸到一组档案抽屉。
文章导航

研究依据复核 Linux CNA 记录、固定的上游与稳定分支 KVM/x86 shadow MMU 修复、Januscape 公开研究、oss-security 讨论及防御性嵌套虚拟化回归证据

来源Linux CNA 记录 / Linux 上游与稳定分支源码 / Januscape 公开研究 / oss-security / SOSEC 源码复核

2026 年 7 月 6 日公开的 Januscape 演示让一台 L1 客体通过嵌套虚拟化状态触发 L0 宿主 panic。故障落在宿主内核 arch/x86/kvm/mmu/mmu.c:L1 先让一个 guest frame 充当 2 MiB 映射的目标,又让同一 frame 充当 L2 页表;旧版 KVM 看见 GFN 相同,便把原来的 direct shadow page 继续交给新的 indirect walk。错误发生时页面仍然存活,随后出现的 rmap 残留与宿主 UAF 才把身份错误变成生命周期破坏。

公开 PoC 证明宿主 panic。研究者还报告已完成 guest-to-host 代码执行,并公开了一个值固定、页内位置受控的写入原语;完整逃逸链没有发布。到达缺陷需要攻击者控制 L1 内核或等价的 VMX/SVM 状态,能够启动 L2 并编排 nested EPT/NPT。公网监听、普通应用账户和普通 SSH 权限无法单独给出这项能力;产品是否向不受信 L1 暴露 nesting 才决定漏洞路径是否可达。

后文固定使用四个对象。GFN 是 guest frame number,用来标识客体物理页;shadow page 是 L0 KVM 管理的影子页表页;SPTE 是 shadow page 内的一条映射项;rmap 是从 GFN 反向找到相关 SPTE 的索引。一次 L2 访问的字面顺序是:L2 虚拟地址经过 L2 页表得到 L2 guest physical address,L1 的 nested EPT/NPT 再把它解释为 L1 guest physical address,L0 随后通过自己的 shadow walk 与宿主内存映射得到最终 host page。Januscape 改坏的是 L0 为这段组合翻译复用 shadow page 的决定。

1 先判定 L0、nesting 与调度资格

安全工单首先需要三个答案:哪一台物理宿主正在执行 KVM/x86,哪一种运行内核包含完整 role 修复,哪些 L1 获得了创建 L2 的能力。给 L1 或 L2 更新内核不会改变 L0 的 kvm_mmu_get_child_sp();QEMU 设备包也不承载这条 shared shadow-MMU 分支。升级对象、重启对象和验收对象都是物理宿主。

Intel 节点通常从 /sys/module/kvm_intel/parameters/nested 读取当前开关,AMD 节点读取 /sys/module/kvm_amd/parameters/nested。当前值还要与 /etc/modprobe.d、内核命令行、initramfs、宿主镜像配方、控制面 capability、实例 flavor 和调度标签一起记录。sysfs 描述已加载模块,持久配置决定下一次加载;两者共同构成当前窗口与下一次启动的真实状态。

1.1 代码与处置图:role 不相等时必须拒绝旧 child

1.2 嵌套地址翻译把 legacy shadow MMU 拉回关键路径

L0 拥有真实 CPU 与内存,L1 是运行在它上面的客体,L2 则由 L1 再启动。Intel 用 VMX/EPT 表达这层能力,AMD 用 SVM/NPT。L1 可以为 L2 提供一套看似物理的地址空间,却没有权力直接安装真实硬件映射;L0 必须把 L2 地址、L1 nested translation 与宿主 backing page 合并。

普通 L0→L1 翻译可以使用硬件 EPT/NPT 和 direct TDP MMU。L1 提供给 L2 的 nested translation 进入 guest_mmu,其 root role 为 non-direct;KVM 因而仍要执行 legacy shadow walk。ept=1npt=1 或启用 TDP MMU 描述常规路径,nesting 能力与 non-direct guest MMU 才描述 Januscape 的可达路径。

能力来源可能藏在服务层。L1 既可以直接持有 /dev/kvm,也可能通过 libvirt、Incus/LXD、特权 sidecar 或平台 VMM API 创建 L2。资产记录应同时覆盖设备 ACL、服务授权、客体可见 VMX/SVM CPUID、镜像类型、租户信任和调度资格。一次合法 L2 启动测试能够验证控制面声明是否与实际暴露一致。

一台宿主还可能同时承载普通 VM 与 nested VM。能够触发的主体局限于获得 nesting 的 L1,宿主 panic 却会中断同节点的全部工作负载。优先级应把运行内核、nesting、L1 内核控制能力与共驻关键业务放在同一张资产记录中。

手绘三层机房剖面:底层 L0 真实宿主掌管物理内存,中层 L1 租户机器维护嵌套页表,上层 L2 发出访问;蓝色地址线必须返回底层旧账房完成 shadow 翻译。
图 1:L2 地址先由 L1 的 nested EPT/NPT 解释,再由 L0 的 non-direct guest MMU 落到真实宿主页。漏洞对象位于 L0 管理的 shadow page。

2 同一 GFN 可以先后承担两种互斥 role

struct kvm_mmu_page 同时保存 gfnrole。标题中的“房号”类比只承担一次入门工作:GFN 说明页面围绕哪个 guest frame 建立,role 说明页面内的 SPTE 应怎样解释。role 包含 level、access、quadrant、passthrough 与 direct 等字段;这些位会改变查找、翻译记录和 teardown 算法。

Januscape 保持 GFN 相等,并让用途改变。第一种用途来自 2 MiB huge mapping 被拆成 4 KiB 映射,KVM 建立 direct=1 的 shadow page。第二种用途来自 L1 把同一 frame 改成下一层页表,KVM 应建立 direct=0 的 indirect shadow page。客体物理页没有永久的“数据页”或“页表页”硬件标签,当前 page-table walk 才定义它的用途。

2.1 direct 拆分页用位置推导 leaf GFN

当 nested PDE 表示 2 MiB leaf,而目标 GFN 已经被 shadowed,disallow_lpage 会阻止继续安装大页。kvm_mmu_hugepage_adjust() 把 fault 降为 4 KiB,KVM 为这段连续区域建立 direct shadow page。第零个 SPTE 对应 base GFN,后续条目按照 index 与 level 计算偏移。

2010 年提交 2032a93d66fa 让 direct MMU page 不再为每个条目分配冗余 GFN 数组。优化成立的前提很具体:页面在使用期间保持 direct,安装与删除都使用同一个“base 加 index”规则。Linux CNA 将该提交列为受影响设计起点,因为后来的错误复用会破坏这项前提。

direct role 还携带 level 与 access。level 决定 index 要跨过多少 guest frame,access 决定被 shadow 的权限。下游逻辑读取这些字段选择算法,因此 role 是对象身份的一部分,而非仅供调试输出的标签。

2.2 indirect 页保存每次 walk 得到的真实翻译

L1 把 nested PDE 从 huge leaf 改成 page-table pointer 后,同一 frame 装着下一级客体页表项。每个条目可以指向任意 GFN,index 已经无法推出目标。正确的 indirect shadow page 使用 direct=0,并保存 walk 解析出的翻译;新分支使用 shadowed_translation[],较旧分支使用 gfns[] 一类结构。

direct 与 indirect 页分别采用两套自洽算法。direct 删除通过 base 与 index 重建 GFN,indirect 删除取回 leaf 安装时保存的翻译。把 direct 页交给 indirect walk 后,安装方记录客体页表真正指向的 Q GFN,页面身份仍要求删除方按连续区间计算。错误由此跨过一次 fault,进入之后的生命周期。

role.word 把完整语义压成可比较值。公开构造最明显的差异是 direct 位,level、access 与 quadrant 也可能改变页面含义。上游最终比较整个 word,让复用条件与完整身份保持一致,也让未来的其他 role 差异获得同一保护。

手绘内存页工作台:同一枚黄铜房号牌连接两种身份,一侧是连续分格的 direct 大页拆分图,另一侧是指向不同位置的 indirect 页表图,两张身份牌在中央交叠。
图 2:同一 GFN 先对应 direct 大页拆分页,随后对应 indirect 客体页表。前者从位置推导 GFN,后者保存实际 walk 的翻译。

3 GFN-only 复用把新 walk 送进旧 direct child

kvm_mmu_get_child_sp() 在 shadow walk 需要下一层页面时决定复用或获取 child。existing child 复用是 page-fault 热路径优化:父 SPTE 已经指向正确页面时,函数返回 ERR_PTR(-EEXIST),调用方继续沿现有链接运行;其余情况交给 kvm_mmu_get_shadow_page() 查找或建立符合请求的页面。

3.1 漏洞父提交在构造 requested role 以前返回 -EEXIST

漏洞父提交的第 2459–2470 行依次检查父 SPTE present、SPTE 不是 large leaf、child 可解析、child GFN 等于请求 GFN。四项成立便返回 -EEXISTkvm_mmu_child_role(sptep, direct, access) 位于该分支之后,请求者需要的 role 尚未参与判断。

在 Januscape 状态下,四项都真实成立。旧 direct child 仍由父 SPTE 指向,新 fault 请求同一个 GFN,child 也仍然分配。缺失条件是 child->role.word == requested_role.word。调用方沿链接继续,在 direct page 内安装属于 indirect walk 的 leaf SPTE;第一现场是一枚存活对象被按错误语义使用。

相邻提交 0cb2af2ea66a 已经要求 existing child 的 GFN 匹配,用于关闭 unexpected-GFN UAF。Januscape 精确保留 GFN 相等,只改变 role。下游 backport 因而需要两项行为:不同 GFN 拒绝旧 child,同 GFN 且 role 不同也拒绝旧 child。

手绘旧门禁:门卫把两枚完全相同的黄铜房号牌叠在一起便放行,来客胸前却挂着与屋内旧住户不同颜色和图形的身份牌,门后连接着同一块 shadow page。
图 3:旧快速路径确认 present、non-large、child 与 GFN,requested role 尚未构造。相同 GFN 因而把 indirect 请求送入 direct child。

3.2 writer 与 faulter 让新 PDE 和旧链接同时可见

L1 可以改写自己提供给 L2 的 nested page table。KVM 的 page tracking 会观察写入并 zap 过期 shadow link;客体页表的新值可见与旧 child 完成清理属于两个先后事件。Januscape 的 writer 反复在 huge leaf 与 page-table pointer 之间切换同一 PDE,多条 faulter 持续让 L2 访问该地址并制造 shadow fault。

命中的顺序为:旧 huge 解释已经生成 direct child;writer 写入 table pointer;faulter 读取新的 table 解释;tracking 尚未从父 SPTE 移除 direct child;GFN-only 复用分支返回 -EEXIST。多条 faulter 通过调度机会与 MMU lock 竞争放大窗口。公开研究记录触发时间从数秒到数分钟,差异来自调度、vCPU 数与宿主速度。

MMU lock 在这里保护结构完整:父链接与 child 指针在观察时仍然有效。对象适配性需要额外的 role 检查。并发协议既要防止指针被撕裂,也要确认缓存对象仍符合当前请求;Januscape 缺少的是后一项语义验证。

生产宿主不适合运行公开 panic race 作为暴露测试。运行版本、供应商 backport、booted kernel、nesting 状态与无破坏的正常 L2 回归已经能够完成安全验收。确需复现时,应使用隔离、可丢弃且有带外恢复的研究节点。

手绘嵌套机房中,一名工人正在调整大型双色角色面板,另两人携带页表板穿过蓝色框架,中央传送带已推进更多板件,表现新任务与仍在场的旧结构发生交错。
图 4:faulter 已按新的 PDE 解释 fault,tracking 仍在清理旧 direct child。窗口只需覆盖一次缺少 role 检查的复用。

4 rmap 登记与删除选择了两个 GFN

rmap 用 guest frame 反查映射它的 SPTE。dirty logging、MMU notifier invalidation 与 memslot 更新都需要从 GFN 找回 shadow entries。每个 rmap entry 保存一个 SPTE 地址;该地址的有效期必须受承载它的 shadow page 生命周期约束。

4.1 leaf 安装使用实际 walk,teardown 使用页面 role

错误 direct child 中的新 leaf 来自 indirect walk。安装方已经解析出客体页表指向的 Q GFN,因此把 SPTE 地址加入 Q 的 rmap bucket。这个键准确描述当前映射。问题出现在删除阶段:承载 SPTE 的 kvm_mmu_page 仍保存 direct role,删除方据此重建另一枚 GFN。

kvm_mmu_page_get_gfn() 第 3270–3283 行展示了两条来源。存在 shadowed_translation 时,它读取安装阶段保存的翻译;缺少该数组时,它从 sp->gfn、index 与 level 推导结果。direct 页面进入后一条路径,所得 GFN 落在连续拆分区间,而 Q 可以位于任意 frame。

删除方查询推导出的 bucket,Q bucket 内的真实 entry 留下。第 3331–3360 行还展示了 translation 记录以及 gfn mismatch under direct page 警告。该字符串是高价值调查线索;发行版配置、清理顺序与崩溃持久化会影响它是否出现在日志中。

手绘账房里,赭色抽屉与深蓝抽屉各自保留一根砖红细线,上方细线接到中央映射板,一块页面板正被吊向拆除设备,表现两条已经无法相遇的归档路径。
图 5:同一 SPTE 以实际 Q GFN 登记,又以 direct 公式得到的 GFN 删除。teardown 查询错误 bucket,Q bucket 仍握着页内地址。

4.2 memslot teardown 让 stale SPTE 跨过页面生命周期

memslot 描述客体物理区间与宿主用户态 backing memory 的关系。删除、替换或重新配置 memslot 会 zap 相关 shadow pages、解除父子链接并把页面归还 allocator。正常 rmap entry 会在页面释放前全部摘除;Januscape 留下的 Q entry 仍指向旧 shadow page 内的 SPTE slot。

后续 dirty logging 或 MMU notifier invalidation 遍历 Q 的 rmap 时,会把该地址当作活 SPTE 解引用。旧页面此时可能已经由其他内核对象占用,形成宿主 use-after-free。某些清理顺序会更早在 pte_list_remove()KVM_BUG_ON_DATA_CORRUPTION 触发 panic;另一种顺序则让 stale address 存活到页面重用。

公开 write-up 对更强原语给出明确约束。孤立父指针清理可向重占用页中的一个 slot 写入 SHADOW_NONPRESENT_VALUE;公开 x86-64 分析中的值为 0x8000000000000000。攻击者影响页内 slot,写入值固定,跨缓存重用、成员对齐、架构与 allocator 行为共同决定后果。该原语已经建立宿主完整性风险,完整代码执行仍以研究者声明为证据层。

公开演示选择 host panic 作为可复核终点,避免发布完整堆布局。事故报告应分开记录三层结论:现场观察到的 panic 与共驻中断;源码与公开研究支持的 host UAF 和固定值写入;研究者报告且细节未公开的 guest-to-host 执行。三层证据分别服务于恢复、风险优先级与归因。

手绘双结局:左侧红线撞上内核一致性闸门,整座宿主机房熄灯;右侧旧房间已换成另一件内核对象,孤立机械臂只落下一枚固定深色印记,位置可变而印记形状不变。
图 6:公开 panic 与受约束固定值写入来自同一 stale rmap。前者建立可用性影响,后者建立宿主完整性影响及其公开约束。

5 完整 role.word 比较在复用点切断链条

主线提交 81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb 于 2026 年 6 月 19 日合入,文件统计为 6 行增加、4 行删除。它把修复放在最早能够同时看见 requested identity 与 stored identity 的位置。rmap、memslot teardown 与 allocator 无需增加 Januscape 专用恢复逻辑。

5.1 requested role 先构造,existing child 再判定

修复后的第 2459–2472 行在函数入口调用 kvm_mmu_child_role(sptep, direct, access)。复用分支保留 present、non-large、child 与 GFN 条件,并加入 child->role.word == role.word。两项身份都相等时,-EEXIST 继续服务热路径;role 不相等时,函数把同一 requested role 交给 kvm_mmu_get_shadow_page()

新的 indirect page 承接新 walk,leaf 安装与删除都依赖保存的实际翻译。旧 direct child 由 tracking 按原流程清理,Q 的 rmap entry 在页面结束前被摘除。竞态窗口仍可能出现,role-aware 决定使窗口内的缓存对象验证与当前语义一致。

完整 word 比较覆盖 direct、level、access、quadrant 等字段。披露构造只需要 direct 差异,上游却把复用契约定义为完整 role 相等。这项选择也方便后续审计:role constructor 负责产生规范身份,reuse site 只负责等值判断,无需分散维护一组“目前看起来重要”的位。

源码注释与 backport 说明应保留下游不对称的原因:direct page 从 base 与 index 推导 GFN,indirect page 保存 walk 的翻译。这个说明让未来的性能重构能够看见 role 检查保护的是 rmap 生命周期,而非一次特定 PoC 的偶然条件。

手绘修复门禁:门卫在明亮工作台上同时比对黄铜房号牌和完整多格身份牌;两者都相同的来客走向旧房间,身份牌不同的来客被引向一间新建 shadow room。
图 7:requested role 在复用判断前构造。existing child 只有在 GFN 与完整 role 都相等时继续使用,其他请求进入正确页面的获取路径。

5.2 下游验收要证明行为等价

发行版源码可能重排 helper、结构体与 fetch loop。验收应从 fault 入口走到 child 选择,再走到 leaf 安装、rmap 删除与 memslot teardown。字符串命中 role.word 只能定位候选补丁;真正结果是 same-GFN/different-role 请求拒绝 existing child,并用同一 requested role 获取替代页面。

相邻 unexpected-GFN 修复必须同时存在。完整接受条件是 stored GFN 等于 requested GFN,且 stored role word 等于 requested role word。任一条件失败都应停止沿旧 child 前进。旧分支若直接在 fetch loop 里对 present child 执行 continue,backport 需要覆盖那条等价捷径。

5.10/5.15 需要额外谨慎。Linux CNA 的固定地板没有包含这两条线;2026 年 7 月 24 日发布的上游 5.10.2615.15.212 ChangeLog 也没有列出 unexpected-role 修复主题或主线提交。该检查界定了公开发布记录,企业发行版的最终接受仍应依靠供应商公告、源包 diff 或可审计的等价行为,并与实际启动的包构建关联。

6 六条固定线、一个回归矩阵和一台节点的恢复过程

Linux CNA 给出的版本地板属于对应上游分支。企业内核常把补丁回补到外观较低的版本,版本字符串需要与 vendor advisory 或源码行为结合。包出现在仓库、包安装到磁盘、L0 已从该包启动是三个不同状态;只有第三个状态改变正在服务租户的 KVM MMU。

历史首个修复地板与当前部署目标需要分开。kernel.org 在 2026 年 7 月 27 日的发布清单显示,当前 stable/longterm 已前移到 7.1.5、6.18.40、6.12.98、6.6.145 与 6.1.178;直接跟随 kernel.org 的环境应采用这些版本或更新的受支持版本。7.2-rc5 属于 mainline 候选,不能替代生产支持策略。企业发行版采用供应商当前受支持、明确包含等效修复的包。

6.1 官方修复地板与提交锚点

上游分支首个确认修复版本稳定提交验收含义
6.1.y6.1.177b1337aae5e19达到该地板,或取得供应商等效回补证据
6.6.y6.6.1449291654d69e0existing child 同时比较 GFN 与完整 role
6.12.y6.12.952ad3afa40ac6安装后重启,并保存新 boot ID
6.18.y6.18.385e470998a23e所有迁移目的端达到同一结果
7.1.y7.1.31ae7d5a6db6c证明运行构建,不能只看候选包
mainline7.2-rc181ccda30b4e8作为行为基准,部署仍遵循供应商支持线
移动端可横向滑动核对分支、首个修复版本、提交与验收含义

上游 6.12.94 低于 6.12.y 地板,6.12.95 达到;一个命名为 5.x 的企业包也可能携带等效修复。资产看板应保存完整 package build、摘要与签名、供应商映射、安装时间、运行中的 uname -r、boot ID、CPU 厂商和 KVM 模块。镜像工厂、自动扩容源、灾备池与离线备用机需要进入同一变更。

6.2 回归矩阵直接观察对象身份与生命周期

测试格构造或输入修复后的期望失败说明
正常复用same GFN / same complete role保留 existing child 与 -EEXIST 热路径backport 可能让所有 fault 重建页面
Januscape 负对照same GFN / different role拒绝旧 direct child,以 requested role 获取正确页面unexpected-role 缺口仍可达
相邻 GFN 对照different GFN / linked child停止沿旧 child 前进并清理不匹配链接0cb2 unexpected-GFN 修复缺失
生命周期安装 leaf、切换 role、zap、删除 memslot登记与删除使用同一 GFN,页面结束后 bucket 为空stale rmap 或 shadow page 泄漏
正常 nested 功能实际提供的 VMX/SVM、L2 fault、dirty logging、迁移工作负载完成,KVM MMU 无新 WARN、BUG 或 lockup移植或平台兼容性回归
性能nested fault 延迟、分配、MMU lock 与 L2 吞吐same-role 复用保持,role mismatch 承担必要分配成本安全成立但热路径被错误扩大
移动端可横向滑动核对测试构造、修复期望与失败含义

矩阵将随机 panic 转成确定不变量。same-GFN/different-role 一格需要观察 existing direct child 被拒绝,并确认替代页面持有正确 role;生命周期一格继续走到 memslot teardown,确认 rmap bucket 已清空。调试构建、定向 selftest 或受控 instrumentation 都可以提供证据,生产节点无需承受公开 PoC 的崩溃终点。

把一台 canary 节点走完流程即可看清生产闭环。维护前导出节点 ID、boot ID、CPU 厂商、运行包、nesting 当前值与持久来源、承载的 L1、所有可迁移目的端和现有 KVM 异常;随后暂停新的 nested 调度,把 L1 迁往已验收池,处理设备直通等不可迁移例外。排空后安装修复包并重启,通过新 boot ID、包签名和源码或公告证明运行代码。

canary 重新加入池以前,运行平台真实支持的 VMX 或 SVM 正常回归,覆盖 L2 启停、缺页、合法 huge/table 转换、memslot、dirty logging 与迁移,并执行上表的身份和生命周期观察。日志与性能通过后,再按宿主池扩大部署;恢复顺序从 L0、模块参数、控制面 capability、实例 flavor 到租户调度。每个目的端单独验收,迁移不会继承源节点的安全状态。

若新内核出现驱动、迁移或合法 nested workload 回归,节点可以启动上一业务可用内核以便诊断,同时继续留在隔离池并关闭 nesting。兼容问题解决、修复内核重新通过矩阵以后,节点才能返回 nested 调度。滚动结束后,bootloader 默认项、监控与合规检查都应读取当前运行内核,并对旧内核启动产生告警。

手绘运维恢复工作台:工程师依次盘点三层节点、关闭嵌套开关、给底层宿主换上密封内核包、点亮重启灯、检查双身份门禁,再把经过验收的节点送回调度轨道。
图 8:一台节点的完整恢复包含能力冻结、排空、L0 升级与重启、源码行为和正常 L2 回归、目的端验收以及按层恢复调度。

7 宿主 panic 的调查结论要与证据强度一致

相关 panic 发生后,应先把节点移出调度,并保存 pstore、kdump、串口或 BMC 控制台、远程日志、完整 kernel build、boot ID、KVM 模块与参数、共驻实例、L1/L2 capability、memslot 操作和迁移历史。容量可以在已修宿主恢复,证据采集与业务恢复并行进行;节点自动重装或回池以前必须完成最易丢失数据的收集。

7.1 日志、可达条件与实际影响分别回答不同问题

gfn mismatch under direct pagepte_list_remove BUG 或相近 KVM MMU 栈能够把事件提升为高价值候选。版本与 boot 证明回答运行代码是否包含缺陷,nesting 与 L1 capability 回答谁能够抵达,dump 与调用栈回答这次崩溃经历了哪条路径,共驻与业务日志回答实际中断范围。结论应由这些事实组合得出。

日志窗口内没有出现线索时,准确结论是“现有数据窗口未观察到相关证据”。宿主 panic 可能中断本地持久化,pstore 或 kdump 也可能从未启用,历史记录还可能已经轮转。补丁关闭未来路径,却无法重建过去缺失的遥测。

nesting 曾经开放但缺少租户台账时,可以从 VM 配置、CPUID 暴露、设备 ACL、服务 API、镜像、作业记录与调度历史重建。仍无法确认的时间段保留为未知,并把今后的 capability 授予写入审计。可疑 L1 的身份与控制面记录应先保存,生产宿主无需再次运行 crash demonstrator。

归因报告应明确“已观察”“源码与公开研究支持”“研究者报告”三种语气。一次 panic 不能自动取得完整逃逸结论;同样,完整逃逸细节未公开也不降低宿主 UAF 与跨租户可用性风险的补丁优先级。证据分层让恢复负责人、漏洞管理者与取证人员使用同一事实集做不同决定。

7.2 关闭状态进入调度器、启动证明与崩溃遥测

长期控制可以收敛为三项可观察状态。第一,nested capability 只调度到源码行为、运行 build 与正常 L2 回归均通过的宿主;第二,任何内核、镜像或模块配置变化都会重新计算 eligible pool;第三,任何 KVM MMU panic 自动保存 pstore、kdump、boot ID 与共驻实例信息。补丁结论由此进入持续调度和事故遥测。

截至 2026 年 7 月 27 日,本报告确认的根因与验收不变量保持稳定:existing child 的 GFN 和完整 role 必须同时匹配,leaf 的 rmap 登记与删除必须使用同一翻译,页面释放前必须清除所有指向页内 SPTE 的 entry。后续稳定分支与供应商版本会继续变化,部署应采用当前受支持的修复包,并将供应商声明连到实际启动的 L0。

Januscape 最终留下的是一条具体的对象复用规则。地址或 ID 相同只能说明资源位置相同;类型、权限、generation、owner 或 role 决定对象能否继续沿用。KVM 在十行补丁中恢复了这项规则:同一 GFN 遇到新的 role 时取得正确 shadow page,rmap 随页面一同结束,宿主不再为一次过早复用承担 UAF。

研究记录

8证据、对象与来源

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

8.1研究对象

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

漏洞CVE-2026-53359

Januscape:KVM/x86 影子 MMU 意外角色复用导致的释放后使用

组件arch/x86/kvm/mmu/mmu.c

Intel VMX 与 AMD SVM 共用的 nested shadow paging 路径

关键函数kvm_mmu_get_child_sp

修复前 existing child 复用只比较 GFN,没有比较完整 role

相邻修复0cb2af2ea66ad8ff195c156ea690f11216285bdf

unexpected-GFN UAF 修复,不能替代本次 role 修复

主线修复81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb

提前构造 requested role 并要求 role.word 完整相等

日志线索gfn mismatch under direct page

需与受影响内核、nesting、KVM MMU 栈和客体归属共同判断

公开原语SHADOW_NONPRESENT_VALUE = 0x8000000000000000

重占用页内受约束固定值写入

修复地板6.1.177 / 6.6.144 / 6.12.95 / 6.18.38 / 7.1.3 / 7.2-rc1

仍用 5.10/5.15 基线时需要发行商等效回补证据

8.2事件时间

  1. 受影响设计起点

    提交 2032a93d66fa 让 direct MMU page 不再保存冗余 GFN 数组。

  2. 相邻 GFN 问题修复

    0cb2af2ea66a 关闭 unexpected-GFN 路径,same-GFN/different-role 缺口仍在。

  3. 漏洞报告与补丁形成

    研究者向内核安全团队报告 unexpected-role UAF,修复提交标注同日作者日期。

  4. 主线修复合入

    81ccda30b4e8 让 existing child 复用同时比较 GFN 与完整 role。

  5. CVE 分配与稳定线发布

    CVE-2026-53359 获得编号,多条维护分支给出修复版本。

  6. Januscape 公开

    embargo 结束后发布技术说明和 host-panic PoC,完整逃逸利用未公开。

  7. SOSEC 完成深度审计重写

    SOSEC 把固定源码行、首屏处置、确定性回归与 L0 生产恢复整合进双语报告。

8.3来源与材料

  1. CVE.org:CVE-2026-53359 官方记录https://www.cve.org/CVERecord?id=CVE-2026-53359
  2. NVD:CVE-2026-53359 记录https://nvd.nist.gov/vuln/detail/CVE-2026-53359
  3. Linux 漏洞父提交:GFN-only child 复用,第 2459–2470 行https://github.com/torvalds/linux/blob/c6f1b611c66f835180e3cb0d3a5df31a61f74d08/arch/x86/kvm/mmu/mmu.c#L2459-L2470
  4. Linux 修复提交:requested role 与完整 role 比较,第 2459–2472 行https://github.com/torvalds/linux/blob/81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb/arch/x86/kvm/mmu/mmu.c#L2459-L2472
  5. Linux 固定源码:kvm_mmu_page_get_gfn,第 3270–3283 行https://github.com/torvalds/linux/blob/81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb/arch/x86/kvm/mmu/mmu.c#L3270-L3283
  6. Linux 主线:unexpected-role 修复 81ccda30b4e8https://git.kernel.org/stable/c/81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb
  7. Linux:相邻 unexpected-GFN 修复 0cb2af2ea66ahttps://git.kernel.org/stable/c/0cb2af2ea66ad8ff195c156ea690f11216285bdf
  8. Linux:direct MMU page GFN 数组优化 2032a93d66fahttps://git.kernel.org/stable/c/2032a93d66fa282ba0f2ea9152eeff9511fa9a96
  9. Linux 6.1.y 稳定修复 b1337aae5e19https://git.kernel.org/stable/c/b1337aae5e194324e4810d561764e7793f8b3864
  10. Linux 6.6.y 稳定修复 9291654d69e0https://git.kernel.org/stable/c/9291654d69e08542de37755cebe4d5b02c3170d1
  11. Linux 6.12.y 稳定修复 2ad3afa40ac6https://git.kernel.org/stable/c/2ad3afa40ac6aa340dada122f9abfa46c0a6eb35
  12. Linux 6.18.y 稳定修复 5e470998a23ehttps://git.kernel.org/stable/c/5e470998a23e4c3d89ed24e8172cb22747e61efa
  13. Linux 7.1.y 稳定修复 1ae7d5a6db6chttps://git.kernel.org/stable/c/1ae7d5a6db6c190ce183e3098ca0e0846e14d462
  14. Januscape 公开研究与 host-panic PoC 仓库https://github.com/V4bel/Januscape
  15. Januscape 技术说明https://github.com/V4bel/Januscape/blob/main/assets/write-up.md
  16. oss-security:Januscape 披露https://www.openwall.com/lists/oss-security/2026/07/06/7
  17. Canonical:Januscape 缓解与资产盘点https://ubuntu.com/blog/januscape-linux-vulnerability-mitigations-available
  18. kernel.org:当前主线、稳定与 longterm 版本https://www.kernel.org/releases.json
  19. kernel.org:Linux 5.10.261 ChangeLoghttps://cdn.kernel.org/pub/linux/kernel/v5.x/ChangeLog-5.10.261
  20. kernel.org:Linux 5.15.212 ChangeLoghttps://cdn.kernel.org/pub/linux/kernel/v5.x/ChangeLog-5.15.212
  21. Google Security Blog:kvmCTF 项目说明https://security.googleblog.com/2024/06/virtual-escape-real-reward-introducing.html