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报告查看器, 内核热修复, 日志审计, 漏洞补丁, 系统运维, 虚拟化安全, 身份验证强制