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 single self-contained kernel module that builds and operates an
entire nested-virtualization stack from scratch inside the guest kernel. It
initializes VMX or SVM state directly using inline assembly, constructs nested
page tables and a minimal L2 guest image in kernel memory, launches a set of
kthreads with distinct racing roles, and races the host KVM shadow MMU into the
role-mismatch reuse path described in the technical analysis. The
VMX and SVM backends are unified behind a virt_ops vtable so that the same
exploit logic runs unmodified on both Intel and AMD.
The
Makefile sets OBJECT_FILES_NON_STANDARD_poc.o := y, which tells the
kernel build system to skip BTF (BPF Type Format) generation for this object.
This is required because poc.c contains raw VMX and SVM inline assembly
(including vmxon, vmlaunch, vmresume, vmrun, vmsave, vmload, and
clgi/stgi) that the objtool stack-validation and BTF passes cannot
analyse and would otherwise reject at build time.Thread Roles
Every kthread spawned bypoc.ko is assigned one of three roles at module
init:
nflood/amd
parameters:
R_WRITER (cpu 0)
Toggles
nest_pd[PDE_IDX] between a 2MB huge-page PTE and a 4KB
table PTE in a tight loop, separated by dwell cpu_relax() spins per
state. This creates the non-atomic PDE window that the faulters race into.R_FLOOD (Intel only)
Cycles through
NPRESS (200) pre-built EPT roots on every vmexit by
rewriting EPT_POINTER in the VMCS. Increases shadow page cache pressure
on the host to widen the race window.R_FAULT
Repeatedly enters the nested guest (L2) at the race RIP (
N_CODE or
N_CODE_RACE), triggering EPT/NPT violations that drive the host KVM
shadow MMU fetch path through the vulnerable kvm_mmu_get_child_sp().virt_ops Abstraction
The virt_ops struct provides a pluggable interface that allows the exploit
body (build_world, the kthread loop, vmexit_dispatch) to be written once
and executed identically on either backend:
m_init(), the backend is selected by the amd parameter:
Page Allocation Helpers
All kernel pages used by the module are allocated through two thin wrappers and tracked in a global array for deterministic cleanup:apg() wraps alloc_page(GFP_KERNEL | __GFP_ZERO), and apg_order() wraps
alloc_pages(GFP_KERNEL | __GFP_ZERO, order). Both call track() to record
the page pointer and order in pages[]/pord[]. free_pgs() iterates that
array and calls __free_pages(pages[i], pord[i]), then resets npg to zero.
VMX Path
vmx_cpu_on() — enabling VMX
vmx_cpu_on() performs the per-CPU VMX initialization:
- Sets
CR4.VMXE(bit 13) via a directmov %0,%%cr4to enable the VMX feature on this CPU. - Reads
IA32_FEATURE_CONTROL(MSR0x3a) and, if the lock bit is clear, writes both the lock bit and the VMXON-outside-SMX enable bit (0x5). - Allocates a zeroed page with
apg(), writes the VMCS revision identifier fromIA32_VMX_BASIC(MSR0x480) into its first dword, and callsvmxon_()— a thin inline wrapper around theVMXONinstruction:
VMX instruction wrappers
All VMCS manipulation uses inline wrappers that map directly onto the underlying VMX instructions:vmx_vcpu_run() — VMCS setup and guest entry
vmx_vcpu_run() allocates a VMCS page, stamps the revision ID, calls
VMCLEAR then VMPTRLD to make it current, and writes all required VMCS
fields for a 64-bit long-mode guest with EPT enabled (SEC_EPT). Host state
is saved by set_host_state() which reads the live descriptor tables, segment
selectors, CR0/CR3/CR4, and MSRs via sgdt, sidt, str, mov %cr*, and
rdmsr. Flood threads have their EPT_POINTER overwritten to press_root[0]
before entry.
Guest entry uses the run_guest() function, which is a noinline asm block
that saves callee-saved registers, writes H_RSP/H_RIP into the VMCS via
VMWRITE, executes VMLAUNCH, and on every subsequent vmexit calls
vmexit_dispatch() before executing VMRESUME:
vmexit_dispatch() — C-level vmexit handler
On every vmexit, the assembly stub calls vmexit_dispatch(). For flood
threads it increments flood_loops, selects the next EPT root from the
press_root[] cycle, and resets guest RIP/RSP/RFLAGS before returning 0
(resume). For faulter threads it calls next_grip() to choose between
N_CODE (non-race) and N_CODE_RACE (race) RIPs and likewise resets guest
state before resuming. Returning 1 signals the asm stub to exit the guest loop
entirely.
SVM Path
svm_cpu_on() — enabling SVM
svm_cpu_on() sets EFER.SVME (bit 12) via wrmsr_(MSR_EFER, ...),
allocates a zeroed host-save area page and writes its physical address to
MSR_VM_HSAVE_PA, then allocates separate per-CPU pages for the host-state
VMCB and the guest VMCB.
svm_vcpu_run() — VMCB setup and guest entry loop
svm_vcpu_run() zeroes the guest VMCB, configures the control area
(intercepts for VMRUN, VMMCALL, and HLT; ASID 1; NPT enabled via
VMCB_NP_CTL with SVM_NESTED_CTL_NP_ENABLE; nested_cr3 set to
the_root), and fills the save area with a complete 64-bit long-mode guest
state. Unlike the VMX path, SVM vmexit handling is a simple C loop: after each
svm_do_vmrun() returns, the exit code in c->exit_code is classified
(VMMCALL=0x81, NPF=0x400, HLT=0x78, error=0xffffffff) and
counters are incremented. The next RIP is set before each svm_do_vmrun()
call rather than inside a dispatch function.
svm_do_vmrun() — VMRUN wrapper
svm_do_vmrun() is a noinline asm function that performs the
CLGI/STGI-bracketed VMSAVE/VMLOAD/VMRUN sequence that AMD SVM
requires to atomically save/restore host and guest state:
+m) so
they survive the guest clobbering GPRs during VMRUN. The host RSP, RIP, and
RAX are automatically saved to VM_HSAVE_PA by the processor on VMRUN and
restored on VMEXIT.
build_world() — Page Tables and Guest Image
build_world() constructs all nested page-table structures and the L2 guest
image in a single function called from m_init():
- Allocates
low_va(512 pages, order 9) as the L2 physical address space andgreg_va(512 pages, order 9) as the shared PT/large-page page. - Allocates
q_va(1 page) as the probe page and fills it with0x4141414141414141. - Builds the nested PML4 → PDPT → PD → PT chain, installing
ptg(which shares its physical address withgreg_va) as both the target of a 2MB huge PTE atnest_pd[PDE_IDX]and as a page-table page pointed to by the same PDE when toggled to table mode. This is the gfn-match / role-mismatch geometry the exploit depends on. - Writes the L2 guest code (
ncode) — amovabs rax, GVA; mov rax,[rax]; vmcall/vmmcallsequence — atN_CODEandN_CODE_RACEoffsets withinlow_va. - On Intel, allocates
NPRESS(200) independent EPT hierarchies for the flood path and records their roots inpress_root[].
Diagnostic Output
With the defaultdiag=1, the module emits the following to the kernel log:
diag=2, faulter threads additionally log the first 0x4141 probe read
(confirming the race window was entered) and periodic SVM exit-code histograms.
Cleanup — m_exit()
The module exit function performs an orderly shutdown:
stop_all flag (checked on every vmexit and in every
writer/SVM loop iteration), sleeps 200 ms to let all kthreads observe the
flag and exit their guest loops, then calls kthread_stop() on every thread
and free_pgs() to release all allocated pages.