When the role-mismatch reuse described in the root cause fires, the shadow MMU begins using aDocumentation 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.
direct=1 shadow page as if it were direct=0. This confusion does not immediately crash the kernel — it propagates silently through parent pointer accounting until the shadow page is eventually zapped. At that point, the cleanup path writes a fixed constant into a memory location that may have already been freed and reallocated as a different kernel object. This is the Januscape use-after-free write primitive.
This page covers the full use-after-free path that underlies the complete guest-to-host escape. For the simpler DoS path that relies on KVM’s own rmap integrity check — which requires neither memory reclamation nor value control — see the DoS Path page. The public PoC implements the DoS path, not this one.
How Role Confusion Breaks Shadow Page Lifetime
Astruct kvm_mmu_page is reference-counted through its parent SPTE links. When a shadow page sp is installed as a child of a parent shadow page parent_sp, a pointer from the parent’s SPTE slot back to sp is tracked in sp->parent_ptes. When sp is zapped, the kernel walks sp->parent_ptes and clears every parent SPTE that points to it.
Role confusion corrupts this accounting in two ways:
- Wrong shadow page is kept alive. The
direct=1page is referenced via the parent SPTE that should be pointing to adirect=0page. Thedirect=0page that should have been installed is either not created or its reference count is skewed. - Orphaned parent pointer. When the
direct=1page is eventually zapped and freed, the parent SPTE in another shadow page still holds a raw pointer into the now-freed page’s memory. This is the orphaned parent pointer that enables the subsequent write.
direct=1 shadow page’s backing spt memory — a 4KB page that held 512 SPTEs — has been returned to the buddy allocator and may be claimed by any kernel subsystem.
The UAF Write Path
When the shadow MMU later cleans up the structure containing the orphaned parent pointer, it calls into the zap path for the parent shadow page:[4], the kernel believes it is writing the “non-present” sentinel value into one slot of a shadow page that it owns. But sptep points into the spt backing page of a shadow page that has already been freed. If that page has since been reclaimed by a different kernel subsystem (the victim object), WRITE_ONCE at [5] writes directly into that victim’s memory.
The Written Value
The constant written isSHADOW_NONPRESENT_VALUE, defined as:
0x8000000000000000. The bit layout is architecturally significant: bit 63 of an x86 PTE is the NX (no-execute) bit when EFER.NXE is set, and it is also used as a KVM sentinel for non-present shadow PTEs. As a 64-bit value to corrupt kernel heap memory, it is a large, non-zero constant with a single bit set.
What the Attacker Controls
The guest controls one dimension of this write:| Dimension | Guest control |
|---|---|
| Written value | Fixed: 0x8000000000000000 on x86-64 |
| Target page | Determined by which shadow page is freed (limited influence) |
| Target offset within page | Chosen: which slot (0–511) within the 4KB spt page receives the write, controlled by the index of the orphaned parent SPTE |
Cross-Cache Attack Requirements
For the UAF write to reach a useful victim object, the freedspt page must be reclaimed by a slab cache that allocates objects covering the target slot offset. Because spt is a full 4KB page allocated directly from the page allocator (not a slab), the attacker must execute a cross-cache attack: arrange for the right slab cache to refill from the page allocator and claim the specific page that was freed.
This requires careful object selection based on:
- Fixed-value alignment: The 8 bytes at the chosen slot offset in the victim object must be a field where
0x8000000000000000is a meaningful corruption. On x86-64, this could be a pointer field (clearing the lower bits effectively nulls it if bit 63 is masked), a flag word, a length field, or a refcount — depending on the target structure. - Allocation timing: The victim objects must be sprayed after the
sptpage is freed, so that one of them lands on the freed page. - Architecture dependence: Because
SHADOW_NONPRESENT_VALUEis0on 32-bit x86, the primitive only carries significance on x86-64. The AMD and Intel paths differ in the shadow page geometry that reaches the UAF, so each architecture requires its own exploitation strategy.
Primitive Summary
This is a constrained write-what-where primitive with the following properties:- Write value:
0x8000000000000000(fixed, architecture-dependent) - Write width: 8 bytes (
u64) - Target: One 8-byte slot within a 4KB page that was formerly a KVM shadow page
spt, after it has been reclaimed by a victim kernel object - Repeatability: The bug can be triggered multiple times before the host is destabilized, allowing spray-and-pray slot selection
- Kernel version dependency: Exploitation strategy varies by kernel version due to slab layout and object field offsets