Aoripus-LTD/Januscape-Hotfix

GitHub: Aoripus-LTD/Januscape-Hotfix

针对 KVM/x86 影子 MMU 逃逸漏洞 CVE-2026-53359 的零停机内核热修复工具,支持通过 ftrace 机制在不重启或迁移虚拟机的情况下在线应用安全补丁。

Stars: 5 | Forks: 1

# Januscape 热修复 — CVE-2026-53359 [简体中文](README.zh-CN.md) 针对 **CVE-2026-53359 (Januscape)** 的零停机 livepatch,这是 KVM/x86 影子 MMU 中的一个客户机到宿主机的 逃逸漏洞。 **无需重启或迁移 VM 即可应用上游修复。** - [概述](#overview) - [此热修复的作用](#what-this-hotfix-does) - [如何验证您的内核是否易受攻击](#how-to-verify-if-your-kernel-is-vulnerable) - [部署](#deployment) - [选项 A:一键热修复](#option-a-one-click-hotfix-recommended-zero-downtime) - [选项 B:手动修补内核](#option-b-manual-kernel-patching-compile-a-new-kernel) - [架构](#architecture) - [要求](#requirements) - [宿主机内核](#host-kernel) - [构建宿主机](#build-host) - [客户机(攻击面)](#guest-attack-surface) - [QEMU 版本与 PoC 可利用性](#qemu-version--poc-exploitability) - [Go 安装程序参考](#go-installer-reference) - [验证](#verification) - [故障排除](#troubleshooting) - [常见问题解答](#faq) - [项目结构](#project-structure) - [许可证](#license) - [参考资料](#references) ## 概述 Januscape 是 `kvm_mmu_get_child_sp()` 中的一个 **use-after-free** 漏洞,允许 KVM 客户机: - **DoS**:导致宿主机内核 panic,使所有共存 VM 宕机 - **逃逸**:在宿主机上实现代码执行(存在完整的逃逸漏洞利用, 但目前尚未公开发布) 该漏洞潜伏了 **16 年**(涵盖从 2010 年到 2026 年 6 月的所有内核)。 它同时影响 Intel (VMX/EPT) 和 AMD (SVM/NPT) —— 这是首个被证实的 跨架构 KVM 逃逸。 | | 详情 | |---|---| | **CVE** | CVE-2026-53359 | | **原始 PoC** | [github.com/V4bel/Januscape](https://github.com/V4bel/Januscape) | | **上游修复** | [commit 81ccda30b4e8](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=81ccda30b4e8) (于 2026-06-16 合并,主线) | | **受影响版本** | Linux ≥ 2.6.35 (2010-08-01) 至 pre-81ccda30b4e8 内核 | | **修复起始版本** | 主线 ≥ 2026-06-16;检查 `grep 'role.word' /proc/kallsyms` | | **触发条件** | 嵌套虚拟化(L1 使用原始 VMX/SVM 运行 L2) | | **权限要求** | 客户机 root 权限(云实例的标配) | | **架构** | Intel VMX/EPT + AMD SVM/NPT(仅限 x86,arm64 不受影响) | ### 此热修复的作用 该修复在影子页重用检查中添加了 **一个条件**: ``` // Before (vulnerable): reuses page if gfn matches only if (... && spte_to_child_sp(*sptep)->gfn == gfn) // After (fixed): also requires the role to match if (... && spte_to_child_sp(*sptep)->gfn == gfn && spte_to_child_sp(*sptep)->role.word == role.word) ``` 这可以防止直接拆分(direct-split)的影子页被错误地重用于 间接影子页遍历,这种情况会破坏反向映射并造成 use-after-free。 ### 如何验证您的内核是否易受攻击 ``` # 如果此操作返回空,则说明您的 kernel 未应用 upstream 补丁,需要 hotfix。 grep 'role.word' /proc/kallsyms ``` ## 部署 ### 选项 A:一键热修复(推荐,零停机) 这是一种 livepatch 方案 —— 在不重启或 迁移 VM 的情况下,将修复应用到正在运行的内核。 #### 预构建产物(相同内核版本) ``` # 加载 hotfix(零停机,无 VM 中断) insmod hotfix-$(uname -r)-x86_64.ko # 验证 dmesg | grep "PATCH ACTIVE" # Rollback(恢复原始函数) rmmod hotfix ``` #### 在目标宿主机上从源码构建 ``` # RHEL / CentOS yum install -y kernel-devel-$(uname -r) make gcc # Debian / Ubuntu apt install -y linux-headers-$(uname -r) build-essential # 构建并加载 cd kmod && make && insmod hotfix.ko ``` #### Go 安装程序(推荐用于集群部署) ``` cd installer go build -o januscape-hotfix . # 仅检查先决条件 ./januscape-hotfix check # 部署(自动检测 offsets,构建、加载、验证) ./januscape-hotfix deploy --force # Rollback ./januscape-hotfix rollback ``` ## 架构 ``` ┌─────────────────────────────────────────────────────┐ │ Go Installer │ │ detect offsets → build kmod → insmod → verify │ │ (BTF → DWARF → DB → default) │ └────────────────────┬────────────────────────────────┘ │ gfn_off=0x38 role_off=0x28 ▼ ┌─────────────────────────────────────────────────────┐ │ Kernel Module (hotfix.ko) │ │ │ │ Hook kvm_mmu_get_child_sp() via ftrace: │ │ Priority 1: register_ftrace_direct() [5.11+] │ │ Priority 2: ftrace IPMODIFY + SAVE_REGS [4.0+] │ │ │ │ Resolve internal KVM symbols at load time: │ │ kallsyms_lookup_name (direct or kprobe fallback) │ └─────────────────────────────────────────────────────┘ ``` ### Hook 机制选择 | 内核 | 方法 | 可靠性 | |--------|--------|-------------| | ≥ 5.14 | `register_ftrace_direct(ops, addr)` | 最佳 — 直接调用跳板 | | 5.11–5.13 | `register_ftrace_direct(ip, addr)` | 良好 — 直接调用跳板(旧 API) | | 4.0–5.10 | ftrace IPMODIFY + SAVE_REGS | 良好 — 通过 ftrace handler 进行 IP 重定向 | | < 4.0 | 不支持 | 需要 `CONFIG_DYNAMIC_FTRACE_WITH_REGS` | ### kallsyms_lookup_name 解析 | 内核 | 方法 | |--------|--------| | < 5.7 | `__symbol_get("kallsyms_lookup_name")` — 已导出 | | ≥ 5.7 | 对 `kallsyms_lookup_name` 使用 kprobe — 未导出,获取地址 | ## 要求 ### 宿主机内核 - **Linux ≥ 4.0**(用于支持 `FTRACE_OPS_FL_IPMODIFY`) - **`CONFIG_DYNAMIC_FTRACE=y`**(所有发行版内核默认开启) - **`CONFIG_KALLSYMS_ALL=y`**(RHEL/CentOS/Debian/Ubuntu 默认开启) - **`CONFIG_DYNAMIC_FTRACE_WITH_REGS=y`**(用于 < 5.11 内核) ### 构建宿主机 - 与**目标**内核匹配的 `kernel-devel` / `linux-headers` - `gcc`, `make` - `bpftool`(可选 — 用于自动偏移检测) ### 客户机(攻击面) - 向客户机暴露嵌套虚拟化(`kvm_intel.nested=1` 或 `kvm_amd.nested=1`) - 客户机拥有 root 权限(云实例的标配) ### QEMU 版本与 PoC 可利用性 在测试期间的一个意外发现:QEMU 版本会影响 PoC 能否**完全触发宿主机 panic**,即使漏洞信号 总能到达内核。 | QEMU 版本 | PoC 行为 | 详情 | |---|---|---| | **≥ 7.0** | 完全暴露 — PoC 触发宿主机 panic | 嵌套 VMX/SVM 直通已成熟;L1 的 `vmxon`/`vmrun` 到达易受攻击的 KVM 影子 MMU 路径并赢得了竞态。 | | **6.x** | 部分暴露 — VM 崩溃,宿主机存活 | PoC 的嵌套 VMX 操作**确实到达了 L0 KVM**(`dmesg` 显示 `nested_vmx_load_cr3` / `vmx_handle_exit`),证明逃逸信号穿过了边界。但是 QEMU 6.x 不完整的嵌套 VMX 模拟在 L0 上的竞态条件被满足之前就中止了 L1 VM。 | | **≤ 5.x** | 不太可能到达易受攻击的路径 | 前嵌套 VMX 时代的 QEMU;L1 根本无法执行 `vmxon`。 | **证据** — 在 QEMU 6.2.0 (EL8)、L0 内核 6.19.2-elrepo、L1 内核 6.8.0-55-generic (Ubuntu 24.04) 上测试: ``` # ── L1(测试 VM)── $ dmidecode -s system-product-name KVM # ← confirmed: this is a VM $ grep -E 'vmx|vmcs|shadow' /proc/cpuinfo | head -3 vmx flags: vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid shadow_vmcs pml tsc_scaling # ← nested VMX features present $ sudo rmmod kvm_intel && sudo modprobe kvm_intel nested=1 $ lsmod | grep kvm kvm_intel 487424 0 # ← nested KVM works inside the VM kvm 1404928 1 kvm_intel $ cd Januscape-main && make && sudo rmmod kvm_intel && sudo insmod poc.ko [*] poc step 1/4: backend=VMX/EPT ready [*] poc step 2/4: nested page tables + L3 guest image built [*] poc step 3/4: launching 8 kthreads (1 writer + 7 faulters) [*] poc step 4/4: race live -- host DoS triggering # ← VM crashes shortly after # SSH connection lost # ── L0(宿主机)── $ qemu-system-x86_64 --version QEMU emulator version 6.2.0 # ← QEMU 6.x $ dmesg | grep 'nested_vmx\|vmx_handle_exit' | tail -5 ? vmx_get_segment+0xc5/0x130 [kvm_intel] ? nested_vmx_load_cr3+0xab/0x160 [kvm_intel] # ← ESCAPE SIGNAL REACHED vmx_handle_exit+0xe/0x40 [kvm_intel] # L0 KVM! ? nested_vmx_load_cr3+0xab/0x160 [kvm_intel] vmx_handle_exit+0xe/0x40 [kvm_intel] $ uptime # ← host stayed up, no panic 18:23:41 up 7 days, 3:14, 1 user ``` ## Go 安装程序参考 ``` januscape-hotfix [flags] Commands: deploy Detect, build, and apply the hotfix check Dry-run: validate prerequisites only rollback Remove the hotfix (rmmod) status Show current hotfix state build Build kernel module only (no deploy) Flags: --force, -f Skip confirmation prompts --all Build for all installed kernel-devel packages ``` ### 偏移检测优先级 ``` BTF (bpftool) → DWARF (vmlinux debuginfo) → Offset Database → Defaults ``` 这些结构体字段偏移量是 `struct kvm_mmu_page` 所需的: | 字段 | 典型偏移量 | 含义 | |-------|---------------|---------| | `gfn` | `0x38` (6.x) / `0x30` (4.x) | 客户机页帧号 | | `role.word` | `0x28` (6.x) / `0x20` (4.x) | 影子页角色 | 要在您的内核上进行验证: ``` pahole -C kvm_mmu_page /usr/lib/debug/lib/modules/$(uname -r)/vmlinux \ | grep -E 'gfn|role' ``` ## Shell 脚本(简单的替代方案) ``` # 无需 Go 快速部署 ./apply.sh # Interactive deploy ./apply.sh --force # Non-interactive ./apply.sh --rollback # Remove hotfix ./apply.sh --status # Check state ./apply.sh --check # Prerequisites only ``` ### 选项 B:手动修补内核(编译新内核) 如果您倾向于通过重新编译内核而不是实时 模块来进行永久性修复,请直接将上游补丁应用到您的内核源码树: ``` # 1. 下载并应用 upstream 修复 cd /path/to/linux-source curl -L 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/?id=81ccda30b4e8' | patch -p1 # 2. 验证更改(仅一个文件,6 处插入,4 处删除) git diff arch/x86/kvm/mmu/mmu.c # 3. 按照您所使用发行版的流程重建并安装您的 kernel # (RHEL/CentOS:make rpm-pkg;Debian/Ubuntu:make bindeb-pkg) ``` 完整的修复是一个 10 行的 diff: ``` --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c - union kvm_mmu_page_role role; + union kvm_mmu_page_role role = kvm_mmu_child_role(sptep, direct, access); - if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep) && - spte_to_child_sp(*sptep) && spte_to_child_sp(*sptep)->gfn == gfn) + if (is_shadow_present_pte(*sptep) && + !is_large_pte(*sptep) && + spte_to_child_sp(*sptep) && + spte_to_child_sp(*sptep)->gfn == gfn && + spte_to_child_sp(*sptep)->role.word == role.word) return ERR_PTR(-EEXIST); - role = kvm_mmu_child_role(sptep, direct, access); return kvm_mmu_get_shadow_page(vcpu, gfn, role); ``` ## 验证 ``` # 1. 模块已加载 lsmod | grep hotfix # 2. Livepatch 在 dmesg 中活跃 dmesg | grep "PATCH ACTIVE" # 3. Sysfs livepatch(如果使用 livepatch API) cat /sys/kernel/livepatch/hotfix/enabled # 4. kallsyms 中的 symbol 已 patch grep januscape /proc/kallsyms ``` ## 故障排除 | 症状 | 可能原因 | 修复方法 | |---------|-------------|-----| | `kvm_mmu_get_child_sp not in kallsyms` | 未设置 `CONFIG_KALLSYMS_ALL` | 检查内核配置;使用仅 kprobe 的回退方案 | | `register_ftrace_direct: -22` | 未找到函数或已被修补 | 验证 `grep kvm_mmu_get_child_sp /proc/kallsyms` | | `insmod: Unknown symbol` | 未加载 KVM 模块 | 先执行 `modprobe kvm` | | 加载后 dmesg 中出现 `gfn mismatch` | 结构体偏移量错误 | 手动指定:`insmod hotfix.ko gfn_off=0x?? role_off=0x??` | | 模块已加载但 livepatch 未激活 | 在热修复之后才加载的 KVM | 卸载热修复,加载 KVM,重新加载热修复 | | 未找到 `kernel-devel` | 缺少 headers | 安装匹配的 `kernel-devel-$(uname -r)` | ## 常见问题解答 ### 我需要重启吗? **不需要。** 这是一个 livepatch。使用 `insmod` 应用,使用 `rmmod` 移除。 不会中断任何 VM。 ### 这会影响正在运行的 VM 吗? **不会。** 此修复仅更改了用于 *未来* 影子 MMU 遍历的 影子页重用逻辑。不会触及现有的影子页。 ### 内核版本低于 4.0 怎么办? 不支持。4.0 之前的内核缺少 `FTRACE_OPS_FL_IPMODIFY`。如果您 仍在运行 RHEL 7 (3.10),则需要基于 `text_poke` 的方案(另一个 独立项目)或升级内核。 ### arm64 会受到影响吗? **不会。** Januscape (CVE-2026-53359) 仅适用于 x86。但是,arm64 KVM 宿主机 应检查是否存在单独的 [ITScape (CVE-2026-46316)](https://github.com/V4bel/ITScape) 漏洞。 ### 如果我的内核已经包含了上游修复怎么办? 安装程序会检测到这种情况: ``` grep 'role.word' /proc/kallsyms && echo "Already patched" ``` ### 我可以构建一次并部署到多台机器上吗? 是的。在具有匹配 kernel-devel 的宿主机上构建: ``` cd kmod && make KDIR=/path/to/target-kernel-headers # 将 hotfix.ko 分发到 kernel 相同的宿主机 ``` 或者使用 `make all-kernels` 为所有已安装的内核版本进行构建。 ### QEMU 6.x 部分掩盖了 PoC — 我安全吗? **不安全。** 即使在 QEMU 6.x 上,PoC 的嵌套 VMX 信号也会到达宿主机 KVM (通过显示 `nested_vmx_load_cr3` 跟踪的 `dmesg` 得到证实)。VM 崩溃是因为 QEMU 6.x 的嵌套 VMX 模拟不完整,但 逃逸信号已经越过了边界进入易受攻击的 KVM 路径。 一个更复杂的触发序列可以无视 QEMU 版本赢得竞态。请升级 QEMU 并修补 KVM —— 不要将任何一方作为 替代另一方的借口。 ## 项目结构 ``` . ├── kmod/ │ ├── hotfix.c # Universal kernel module (4.x–6.x) │ ├── offsets_db.h # Struct offset database (fallback) │ └── Makefile # Kernel module build system ├── installer/ │ ├── main.go # Go installer (deploy/check/rollback/status) │ └── go.mod # Go module definition ├── apply.sh # Shell-based deploy alternative ├── .gitignore └── README.md ``` ## 许可证 GPL v2,如 Linux 内核源码树 [COPYING](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/COPYING) 文件中所述。 **版权所有 (c) 2026 AORIPUS (BEIJING) TECHNOLOGY CO., LTD. & GEELINX LTD.** 联系:master@aoripus.com ## 参考资料 - [原始 PoC — V4bel/Januscape](https://github.com/V4bel/Januscape) - [上游修复 — commit 81ccda30b4e8](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=81ccda30b4e8) - [lore.kernel.org 上的补丁](https://lore.kernel.org/all/20260617134425.440091-1-pbonzini@redhat.com/) - [oss-security 披露](https://www.openwall.com/lists/oss-security/2026/07/06/1) - [Google kvmCTF](https://security.googleblog.com/2024/06/virtual-escape-real-reward-introducing.html)
标签:EVTX分析, KVM, Web报告查看器, 内核热修复, 日志审计, 漏洞补丁, 系统运维, 虚拟化安全, 身份验证强制