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:
The bug goes off on L0 when L0 shadows the nested page tables that L1 built for L2. L0 is the victim; L1 is the attack surface; L2 is the trigger mechanism.
Exploit Steps
1
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.2
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.3
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.4
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.