The public PoC implements the DoS path: it triggers a host kernel panic from guest actions alone, without requiring any freed memory to be reclaimed by a victim object. The panic is self-inflicted by the host kernel’s own corruption detection. Once the race window is entered and the direct-split shadow page is reused as an indirect shadow page, a chain of invariant violations unfolds in L0’s shadow MMU that terminates inDocumentation 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.
KVM_BUG_ON_DATA_CORRUPTION → BUG → host panic.
Step-by-Step Panic Sequence
Direct split page reused as indirect (role mismatch accepted)
The race lands a faulter inside the window where the stale direct-split shadow page (
direct=1) is still linked at sptep. The faulter calls kvm_mmu_get_child_sp(vcpu, sptep, gfn, /*direct=*/false, access). The check at [1] compares only gfn, finds a match, and returns -EEXIST — signaling the caller to reuse the existing page rather than allocating a new one with the correct role. The direct-split page is now being treated as an indirect shadow page.Leaf SPTE for gfn Q installed
The
FNAME(fetch) path proceeds to install leaf SPTEs into the reused shadow page. Because ptg[PRIME_IDX] points to q_pa, a leaf SPTE is installed at slot PRIME_IDX that maps the q probe page (gfn Q). The rmap is updated: the SPTE pointer is registered under the real gfn key — the actual gfn of the q page.Real gfn diverges from direct assumption
The reused page has no
shadowed_translation array (direct pages use sp->gfn + index to compute leaf gfns). When kvm_mmu_page_set_translation is called for the new leaf, it computes the expected gfn as sp->gfn + PRIME_IDX — which resolves to a gfn in the greg region — but the actual leaf gfn is Q (the q_pa frame). The two values diverge.WARN_ONCE fires at [6]
The divergence is caught by:This fires as a warning in the host kernel log —
"gfn mismatch under direct page 8a00 (expected 8b00, got 256e4)" — but execution continues. This is a symptom, not the fatal point.Rmap registered under real gfn key
Despite the mismatch warning, the rmap entry for the leaf SPTE is registered under the real gfn key (Q). The shadow MMU now has an inconsistent state: the shadow page’s direct computation says the leaf belongs to one gfn, but the rmap tracks it under another.
Another fault overwrites and drops the leaf
On the next pass through the same location — triggered by another faulter or a subsequent writer toggle —
mmu_set_spte calls drop_spte to clear the old SPTE before installing the new one.pte_list_remove looks up rmap under computed key — entry not found
drop_spte → rmap_remove → pte_list_remove looks up the rmap using the computed gfn key (sp->gfn + PRIME_IDX), not the real gfn key under which the entry was registered. The rmap bucket for the computed key is empty. pte_list_remove detects the inconsistency.Actual RHEL Panic Log
The following panic was triggered on RHEL (kernel6.12.0-211.26.1.el10_2.x86_64):
WARNING at mmu.c:689 is the WARN_ONCE at [6] (kvm_mmu_page_set_translation). The kernel BUG at mmu.c:1091 is the KVM_BUG_ON_DATA_CORRUPTION at [7] (pte_list_remove). Both fire on qemu-kvm threads (L0’s QEMU vCPU threads for L1), which confirms the panic originates entirely in L0’s in-kernel KVM, triggered by L1 guest actions.
PoC Output on the Guest Side
From the module’s perspective inside L1, the final output before the host goes down is:Why No Victim Object Is Needed
Unlike the full RCE path (see Full Escape), the DoS path requires no cross-cache heap manipulation, no reclamation of the freed shadow page by a victim object, and no control over write values. The panic is triggered entirely by the rmap accounting invariant being broken: the shadow MMU’s own integrity check (KVM_BUG_ON_DATA_CORRUPTION) detects the corruption and calls BUG(). This makes the DoS path highly reliable — a single successful race window entry is sufficient.