aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
authorAlexander Potapenko <glider@google.com>2025-11-17 16:13:55 +0100
committerAlexander Potapenko <glider@google.com>2025-11-19 08:59:40 +0000
commita0dd64f852f58d267f78c25c35b3f3eae2b521c7 (patch)
tree9b3b4345aa6b28ba5498df41e5515fd846697d73 /executor
parent825f9f21aae8dd655126b50de70210bacbd330f9 (diff)
executor: x86: implement SYZOS_API_ENABLE_NESTED
Add vendor-specific code to turn on nested virtualization on Intel and AMD. Also provide get_cpu_vendor() to pick the correct implementation.
Diffstat (limited to 'executor')
-rw-r--r--executor/common_kvm_amd64_syzos.h118
-rw-r--r--executor/kvm.h3
2 files changed, 121 insertions, 0 deletions
diff --git a/executor/common_kvm_amd64_syzos.h b/executor/common_kvm_amd64_syzos.h
index fd43b9daa..875cedbe5 100644
--- a/executor/common_kvm_amd64_syzos.h
+++ b/executor/common_kvm_amd64_syzos.h
@@ -26,6 +26,7 @@ typedef enum {
SYZOS_API_IN_DX = 130,
SYZOS_API_OUT_DX = 170,
SYZOS_API_SET_IRQ_HANDLER = 190,
+ SYZOS_API_ENABLE_NESTED = 230,
SYZOS_API_STOP, // Must be the last one
} syzos_api_id;
@@ -81,6 +82,7 @@ GUEST_CODE static void guest_handle_wr_drn(struct api_call_2* cmd);
GUEST_CODE static void guest_handle_in_dx(struct api_call_2* cmd);
GUEST_CODE static void guest_handle_out_dx(struct api_call_3* cmd);
GUEST_CODE static void guest_handle_set_irq_handler(struct api_call_2* cmd);
+GUEST_CODE static void guest_handle_enable_nested(struct api_call_1* cmd, uint64 cpu_id);
typedef enum {
UEXIT_END = (uint64)-1,
@@ -88,6 +90,11 @@ typedef enum {
UEXIT_ASSERT = (uint64)-3,
} uexit_code;
+typedef enum {
+ CPU_VENDOR_INTEL,
+ CPU_VENDOR_AMD,
+} cpu_vendor_id;
+
__attribute__((naked))
GUEST_CODE static void
dummy_null_handler()
@@ -170,6 +177,9 @@ guest_main(uint64 size, uint64 cpu)
} else if (call == SYZOS_API_SET_IRQ_HANDLER) {
// Set the handler for a particular IRQ.
guest_handle_set_irq_handler((struct api_call_2*)cmd);
+ } else if (call == SYZOS_API_ENABLE_NESTED) {
+ // Enable nested virtualization.
+ guest_handle_enable_nested((struct api_call_1*)cmd, cpu);
}
addr += cmd->size;
size -= cmd->size;
@@ -398,4 +408,112 @@ GUEST_CODE static noinline void guest_handle_set_irq_handler(struct api_call_2*
set_idt_gate(vector, handler_addr);
}
+GUEST_CODE static cpu_vendor_id get_cpu_vendor(void)
+{
+ uint32 ebx, eax = 0;
+
+ asm volatile(
+ "cpuid"
+ : "+a"(eax), "=b"(ebx)
+ : // No explicit inputs, EAX is handled by +a.
+ : "ecx", "edx");
+
+ if (ebx == 0x756e6547) { // "Genu[ineIntel]".
+ return CPU_VENDOR_INTEL;
+ } else if (ebx == 0x68747541) { // "Auth[enticAMD]".
+ return CPU_VENDOR_AMD;
+ } else {
+ // Should not happen on AMD64, but for completeness.
+ guest_uexit(UEXIT_ASSERT);
+ return CPU_VENDOR_INTEL; // Default to Intel if unknown.
+ }
+}
+
+GUEST_CODE static inline uint64 read_cr4(void)
+{
+ uint64 val;
+ asm volatile("mov %%cr4, %0" : "=r"(val));
+ return val;
+}
+
+GUEST_CODE static inline void write_cr4(uint64 val)
+{
+ asm volatile("mov %0, %%cr4" : : "r"(val));
+}
+
+GUEST_CODE static noinline void wrmsr(uint64 reg, uint64 val)
+{
+ asm volatile(
+ "wrmsr"
+ :
+ : "c"(reg),
+ "a"((uint32)val),
+ "d"((uint32)(val >> 32))
+ : "memory");
+}
+
+GUEST_CODE static noinline uint64 rdmsr(uint32 msr_id)
+{
+ uint64 msr_value;
+ asm volatile("rdmsr" : "=A"(msr_value) : "c"(msr_id));
+ return msr_value;
+}
+
+GUEST_CODE static noinline void
+nested_enable_vmx_intel(uint64 cpu_id)
+{
+ uint64 vmxon_addr = X86_SYZOS_ADDR_VM_ARCH_SPECIFIC(cpu_id);
+ uint64 cr4 = read_cr4();
+ cr4 |= X86_CR4_VMXE;
+ write_cr4(cr4);
+
+ uint64 feature_control = rdmsr(X86_MSR_IA32_FEATURE_CONTROL);
+ // Check if Lock bit (bit 0) is clear.
+ if ((feature_control & 1) == 0) {
+ // If unlocked, set Lock bit (bit 0) and Enable VMX outside SMX bit (bit 2).
+ feature_control |= 0b101;
+ asm volatile("wrmsr" : : "d"(0x0), "c"(X86_MSR_IA32_FEATURE_CONTROL), "A"(feature_control));
+ }
+
+ // Store revision ID at the beginning of VMXON.
+ *(uint32*)vmxon_addr = rdmsr(X86_MSR_IA32_VMX_BASIC);
+ uint8 error;
+ // Can't use enter_vmx_operation() yet, because VMCS is not valid.
+ asm volatile("vmxon %1; setna %0"
+ : "=q"(error)
+ : "m"(vmxon_addr)
+ : "memory", "cc");
+ if (error) {
+ guest_uexit(0xE2BAD0);
+ return;
+ }
+}
+
+GUEST_CODE static noinline void
+nested_enable_svm_amd(uint64 cpu_id)
+{
+ // Get the Host Save Area (HSAVE) physical address for this CPU.
+ // The HSAVE area stores the host processor's state on VMRUN and is restored on VMEXIT.
+ uint64 hsave_addr = X86_SYZOS_ADDR_VM_ARCH_SPECIFIC(cpu_id);
+
+ // Set the SVM Enable (SVME) bit in EFER. This enables SVM operations.
+ uint64 efer = rdmsr(X86_MSR_IA32_EFER);
+ efer |= X86_EFER_SVME;
+ wrmsr(X86_MSR_IA32_EFER, efer);
+
+ // Write the physical address of the HSAVE area to the VM_HSAVE_PA MSR.
+ // This MSR tells the CPU where to save/restore host state during VMRUN/VMEXIT.
+ wrmsr(X86_MSR_VM_HSAVE_PA, hsave_addr);
+}
+
+GUEST_CODE static noinline void
+guest_handle_enable_nested(struct api_call_1* cmd, uint64 cpu_id)
+{
+ if (get_cpu_vendor() == CPU_VENDOR_INTEL) {
+ nested_enable_vmx_intel(cpu_id);
+ } else {
+ nested_enable_svm_amd(cpu_id);
+ }
+}
+
#endif // EXECUTOR_COMMON_KVM_AMD64_SYZOS_H
diff --git a/executor/kvm.h b/executor/kvm.h
index ec6fdccc3..ab6c44601 100644
--- a/executor/kvm.h
+++ b/executor/kvm.h
@@ -215,14 +215,17 @@
#define X86_SEL_TSS64_CPL3 ((29 << 3) + 3)
#define X86_SEL_TSS64_CPL3_HI (30 << 3)
+// Model-Specific Registers (MSRs).
#define X86_MSR_IA32_FEATURE_CONTROL 0x3a
#define X86_MSR_IA32_VMX_BASIC 0x480
#define X86_MSR_IA32_SMBASE 0x9e
#define X86_MSR_IA32_SYSENTER_CS 0x174
#define X86_MSR_IA32_SYSENTER_ESP 0x175
#define X86_MSR_IA32_SYSENTER_EIP 0x176
+#define X86_MSR_IA32_EFER 0xc0000080
#define X86_MSR_IA32_STAR 0xC0000081
#define X86_MSR_IA32_LSTAR 0xC0000082
+#define X86_MSR_VM_HSAVE_PA 0xc0010117
#define X86_MSR_IA32_VMX_PROCBASED_CTLS2 0x48B
#define X86_NEXT_INSN $0xbadc0de