Skip to main content

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:
LevelRole
L0Bare-metal Intel or AMD KVM host — the panic target
L1The guest instance allocated to the attacker — the kernel module is loaded here
L2The nested guest the module brings up with raw VMX/SVM inside L1
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.

Build docs developers (and LLMs) love