Documentation Index
Fetch the complete documentation index at: https://mintlify.com/V4bel/Januscape/llms.txt
Use this file to discover all available pages before exploring further.
poc.c is a kernel module loaded inside the guest (L1). Inside L1, the module directly builds and runs its own nested guest (L2) using raw VMX (Intel) or SVM (AMD) instructions — no userspace VMM involved. Once L2 exists, the host (L0) is forced to shadow L1’s nested EPT/NPT page tables through the legacy shadow MMU. It is precisely in this shadowing path that the Januscape role-mismatch reuse fires, corrupting L0’s rmap accounting and triggering a host kernel panic. Because the bug lives in arch/x86/kvm/mmu/mmu.c, which is shared between the Intel VMX and AMD SVM paths, the exploit works identically on both architectures.
Level Structure
The three privilege levels involved are:| Level | Role |
|---|---|
| L0 | Bare-metal Intel or AMD KVM host — the panic target |
| L1 | The guest instance allocated to the attacker — the kernel module is loaded here |
| L2 | The nested guest the module brings up with raw VMX/SVM inside L1 |
Exploit Steps
build_world: Construct Nested Page Tables
The module allocates L1 physical pages and manually builds a 4-level nested EPT (Intel) or NPT (AMD) for L2. The critical geometry: a single physical page (
greg) is used simultaneously as the leaf of a 2 MB large-page entry and as the page table pointed to by the same PDE. This makes the gfn identical in both roles while the role.direct field differs — the precondition for the bug.Raw VMX/SVM Setup
The module calls
vmxon/vmlaunch (Intel) or sets SVM_NESTED_CTL_NP_ENABLE and calls vmrun (AMD) to bring up L2 entirely from the guest kernel. The L2 guest image is a minimal stub: movabs rax, GVA; mov rax,[rax]; vmcall — it reads one guest virtual address to induce a nested EPT/NPT violation, forcing L0’s shadow MMU fetch path.Race the PDE Toggle
A writer kthread on CPU 0 continuously toggles
nest_pd[PDE_IDX] between a 2 MB huge-page entry and a 4 KB page-table entry with a short spin between each write. Multiple faulter kthreads simultaneously run L2 in a tight loop, generating a constant stream of nested faults. The non-atomic window between L0 committing the new PDE value and calling kvm_page_track_write to zap the old shadow link is where the role-mismatch reuse at kvm_mmu_get_child_sp fires.Host Panic
When the race succeeds, a direct split shadow page (
direct=1) is reused as an indirect shadow page (direct=0). The leaf for gfn Q is installed, the real gfn diverges from the direct assumption (sp->gfn + index), and pte_list_remove cannot locate the rmap entry under the computed key. KVM_BUG_ON_DATA_CORRUPTION fires, and on distributions with CONFIG_BUG_ON_DATA_CORRUPTION=y (the default on RHEL), this is an immediate host kernel panic — taking down every tenant VM on the physical machine.Detailed Pages
Nested Page Tables
How
build_world() constructs the EPT/NPT geometry — the single page used as both a 2 MB leaf and a PT page, and how that creates the gfn match with role mismatch.Dual-Arch Design
Why the exploit triggers identically on Intel VMX/EPT and AMD SVM/NPT, and how the
virt_ops abstraction isolates the architecture-specific page-table bit patterns.Race Condition
The writer/faulter thread roles, the non-atomic PDE toggle window, and how contention over
mmu_lock widens the race to make it reliably exploitable.Host Panic
The step-by-step sequence from role-mismatch reuse through rmap divergence to
KVM_BUG_ON_DATA_CORRUPTION and the resulting L0 kernel panic.