KVM-XJ Hot Patch
dump_tool.py automatically finds offsets; how to use it? Just read the code.
Vulnerability Overview
CVE-2026-53359 (Januscape) is a use-after-free (UAF) vulnerability in the Linux kernel KVM shadow MMU.
- Lurking time: 16 years (August 2010 to July 2026)
- Affected range: All Intel/AMD x86 systems with nested virtualization enabled
- Severity: High; root users inside a VM can escape to the host and obtain host root privileges
- Exploitation: Host kernel panic (DoS) or full VM escape (no public PoC)
Vulnerability Principle
The kvm_mmu_get_page() function, when searching the hash table for a reusable shadow page table, only compares the gfn (guest physical frame number) and does not compare the role (page table role / MMU role), causing page tables with mismatched roles to be incorrectly reused, leading to UAF.
Before fix (vulnerable):
┌──────────────────────────────────────┐
│ kvm_mmu_get_page() │
│ Traverse hash table │
│ if (child->gfn == gfn) │
│ return child ← compares only gfn! │
│ Role mismatch leads to reuse → UAF!│
│ Allocate new page (safe) │
└──────────────────────────────────────┘
In the upstream kernel commit 81ccda30b4e8, a line comparing role was added:
// Before fix
if (... && spte_to_child_sp(*sptep)->gfn == gfn)
// After fix
if (... && spte_to_child_sp(*sptep)->gfn == gfn
&& spte_to_child_sp(*sptep)->role.word == role.word)
My Fix Method
Method: text_poke Binary Hot Patch (NOP Patch)
No kernel source modification, no function replacement; directly modifies the vulnerable instruction in the running kernel memory.
Principle
Binary level:
Offset 0x104: 49 39 47 28 cmp %rax, 0x28(%r15) ← compare gfn
Offset 0x108: 0f 84 9f 01 00 00 je +0x19f ← if match, jump to reuse
After patching:
Offset 0x108: 66 0f 1f 44 00 00 NOP × 6 ← do nothing
After fix:
┌──────────────────────────────────────┐
│ kvm_mmu_get_page() │
│ Traverse hash table │
│ if (child->gfn == gfn) │
│ NOP (jump erased, slides through)│
│ Allocate new page (forced safe path)│
│ → No reuse = No UAF = Vulnerability fixed│
└──────────────────────────────────────┘
Technical Implementation
- Find the address of
kvm_mmu_get_page via kallsyms_lookup_name
- Find the address of
text_poke (kernel code hot-patch API) via kallsyms_lookup_name
- Use
stop_machine to pause all CPUs, ensuring safe modification
- Use
text_poke to replace the 6-byte je instruction with a 6-byte NOP
- On unload, restore the original instruction using
text_poke
Why Not Use Other Methods
Impact Assessment
Deployment Requirements
| Condition | Description |
|---|
| Kernel version | Linux 4.18+ (CentOS 8 / RHEL 8 / Rocky 8 etc.) |
| Build environment | kernel-devel + gcc + make |
Compilation and Loading
# Compile
make
# Load hot patch
insmod KVM-XJ.ko
# Check status
dmesg | grep KVM-XJ
cat /sys/module/kvm_intel/parameters/nested # should still be 1
lsmod | grep KVM_XJ
# Unload (restore original code)
rmmod KVM_XJ
Adapting to Other Kernel Versions
Different kernel versions have different compilation optimizations; the offset of the je instruction also varies. Use dump_tool.py for automatic analysis:
# 1. Download kvm.ko from the host
scp root@host:/lib/modules/.../kvm.ko.xz .
xz -d kvm.ko.xz
# 2. Run the analysis tool
python dump_tool.py kvm.ko
# 3. The tool will output the patch offset; modify 0x108 in KVM-XJ.c accordingly
Verified Kernels
| Kernel Version | je Offset | Status |
|---|
| 4.18.0-496.el8.x86_64 | 0x108 | Tested |
| 4.18.0-358.el8.x86_64 | 0xe8 | Tested |
Notes
- If you don’t know how to use it or don’t understand the code, it is recommended not to use it.