Skip to content

Commit cc09e1b

Browse files
executor: sys/linux: implement SYZOS_API_NESTED_VMLAUNCH
Provide a SYZOS API command to launch the L2 VM using the VMLAUNCH (Intel) or VMRUN (AMD) instruction. For testing purposes, each L2->L1 exit is followed by a guest_uexit_l2() returning the exit code to L0. Common exit reasons (like HLT) will be mapped into a common exit code space (0xe2e20000 | reason), so that a single test can be used for both Intel and AMD. Vendor-specific exit codes will be returned using the 0xe2110000 mask for Intel and 0xe2aa0000 for AMD.
1 parent def6cb5 commit cc09e1b

File tree

3 files changed

+203
-1
lines changed

3 files changed

+203
-1
lines changed

executor/common_kvm_amd64_syzos.h

Lines changed: 200 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ typedef enum {
2929
SYZOS_API_ENABLE_NESTED = 300,
3030
SYZOS_API_NESTED_CREATE_VM = 301,
3131
SYZOS_API_NESTED_LOAD_CODE = 302,
32+
SYZOS_API_NESTED_VMLAUNCH = 303,
3233
SYZOS_API_STOP, // Must be the last one
3334
} syzos_api_id;
3435

@@ -78,6 +79,7 @@ struct api_call_3 {
7879
extern "C" {
7980
#endif
8081
GUEST_CODE static void guest_uexit(uint64 exit_code);
82+
GUEST_CODE static void nested_vm_exit_handler_intel(uint64 exit_reason, struct l2_guest_regs* regs);
8183
#ifdef __cplusplus
8284
}
8385
#endif
@@ -93,11 +95,13 @@ GUEST_CODE static void guest_handle_set_irq_handler(struct api_call_2* cmd);
9395
GUEST_CODE static void guest_handle_enable_nested(struct api_call_1* cmd, uint64 cpu_id);
9496
GUEST_CODE static void guest_handle_nested_create_vm(struct api_call_1* cmd, uint64 cpu_id);
9597
GUEST_CODE static void guest_handle_nested_load_code(struct api_call_nested_load_code* cmd, uint64 cpu_id);
98+
GUEST_CODE static void guest_handle_nested_vmlaunch(struct api_call_1* cmd, uint64 cpu_id);
9699

97100
typedef enum {
98101
UEXIT_END = (uint64)-1,
99102
UEXIT_IRQ = (uint64)-2,
100103
UEXIT_ASSERT = (uint64)-3,
104+
UEXIT_STOP_L2 = (uint64)-4,
101105
} uexit_code;
102106

103107
typedef enum {
@@ -196,6 +200,9 @@ guest_main(uint64 size, uint64 cpu)
196200
} else if (call == SYZOS_API_NESTED_LOAD_CODE) {
197201
// Load code into the nested VM.
198202
guest_handle_nested_load_code((struct api_call_nested_load_code*)cmd, cpu);
203+
} else if (call == SYZOS_API_NESTED_VMLAUNCH) {
204+
// Launch the nested VM.
205+
guest_handle_nested_vmlaunch((struct api_call_1*)cmd, cpu);
199206
}
200207
addr += cmd->size;
201208
size -= cmd->size;
@@ -538,6 +545,11 @@ GUEST_CODE static noinline void vmcb_write64(uint64 vmcb, uint16 offset, uint64
538545
*((volatile uint64*)(vmcb + offset)) = val;
539546
}
540547

548+
GUEST_CODE static noinline uint64 vmcb_read64(volatile uint8* vmcb, uint16 offset)
549+
{
550+
return *((volatile uint64*)(vmcb + offset));
551+
}
552+
541553
GUEST_CODE static void guest_memset(void* s, uint8 c, int size)
542554
{
543555
volatile uint8* p = (volatile uint8*)s;
@@ -713,9 +725,106 @@ GUEST_CODE static noinline void init_vmcs_control_fields(uint64 cpu_id, uint64 v
713725
vmwrite(VMCS_TPR_THRESHOLD, 0);
714726
}
715727

716-
// Empty for now.
728+
// This struct must match the push/pop order in nested_vm_exit_handler_intel_asm().
729+
struct l2_guest_regs {
730+
uint64 rax, rbx, rcx, rdx, rsi, rdi, rbp;
731+
uint64 r8, r9, r10, r11, r12, r13, r14, r15;
732+
};
733+
734+
// Common L2 exit reasons for Intel and AMD.
735+
typedef enum {
736+
SYZ_NESTED_EXIT_REASON_HLT = 1,
737+
SYZ_NESTED_EXIT_REASON_UNKNOWN = 0xFF,
738+
} syz_nested_exit_reason;
739+
740+
GUEST_CODE static void guest_uexit_l2(uint64 exit_reason, syz_nested_exit_reason mapped_reason,
741+
cpu_vendor_id vendor)
742+
{
743+
if (mapped_reason != SYZ_NESTED_EXIT_REASON_UNKNOWN) {
744+
guest_uexit(0xe2e20000 | mapped_reason);
745+
} else if (vendor == CPU_VENDOR_INTEL) {
746+
guest_uexit(0xe2110000 | exit_reason);
747+
} else {
748+
guest_uexit(0xe2aa0000 | exit_reason);
749+
}
750+
}
751+
752+
GUEST_CODE static syz_nested_exit_reason map_intel_exit_reason(uint64 reason)
753+
{
754+
volatile uint64 basic_reason = reason & 0xFFFF;
755+
// EXIT_REASON_HLT.
756+
if (basic_reason == 0xc)
757+
return SYZ_NESTED_EXIT_REASON_HLT;
758+
return SYZ_NESTED_EXIT_REASON_UNKNOWN;
759+
}
760+
761+
// This function is called from inline assembly.
762+
__attribute__((used))
763+
GUEST_CODE static void
764+
nested_vm_exit_handler_intel(uint64 exit_reason, struct l2_guest_regs* regs)
765+
{
766+
syz_nested_exit_reason mapped_reason = map_intel_exit_reason(exit_reason);
767+
guest_uexit_l2(exit_reason, mapped_reason, CPU_VENDOR_INTEL);
768+
}
769+
770+
extern char after_vmentry_label;
717771
__attribute__((naked)) GUEST_CODE static void nested_vm_exit_handler_intel_asm(void)
718772
{
773+
asm volatile(R"(
774+
// Save L2's GPRs. This creates the 'struct l2_guest_regs' on the stack.
775+
// The order MUST match the struct.
776+
push %%rax
777+
push %%rbx
778+
push %%rcx
779+
push %%rdx
780+
push %%rsi
781+
push %%rdi
782+
push %%rbp
783+
push %%r8
784+
push %%r9
785+
push %%r10
786+
push %%r11
787+
push %%r12
788+
push %%r13
789+
push %%r14
790+
push %%r15
791+
792+
// Prepare arguments for the C handler:
793+
// arg1 (RDI) = exit_reason
794+
// arg2 (RSI) = pointer to the saved registers
795+
mov %%rsp, %%rsi
796+
mov %[vm_exit_reason], %%rbx
797+
vmread %%rbx, %%rdi
798+
799+
// Call the C handler.
800+
call nested_vm_exit_handler_intel
801+
802+
// The C handler has processed the exit. Now, return to the L1 command
803+
// processing loop. VMX remains enabled.
804+
add %[stack_cleanup_size], %%rsp
805+
806+
// Jump to L1 main flow
807+
jmp after_vmentry_label
808+
)"
809+
810+
: : [stack_cleanup_size] "i"(sizeof(struct l2_guest_regs)),
811+
[vm_exit_reason] "i"(VMCS_VM_EXIT_REASON) : "memory", "cc", "rbx", "rdi", "rsi");
812+
}
813+
814+
GUEST_CODE static syz_nested_exit_reason map_amd_exit_reason(uint64 reason)
815+
{
816+
volatile uint64 basic_reason = reason & 0xFFFF;
817+
// #VMEXIT_HLT.
818+
if (basic_reason == 0x78)
819+
return SYZ_NESTED_EXIT_REASON_HLT;
820+
return SYZ_NESTED_EXIT_REASON_UNKNOWN;
821+
}
822+
823+
__attribute__((used)) GUEST_CODE static void
824+
nested_vm_exit_handler_amd(uint64 exit_reason, uint64 cpu_id, uint64 vm_id)
825+
{
826+
syz_nested_exit_reason mapped_reason = map_amd_exit_reason(exit_reason);
827+
guest_uexit_l2(exit_reason, mapped_reason, CPU_VENDOR_AMD);
719828
}
720829

721830
GUEST_CODE static noinline void init_vmcs_host_state(void)
@@ -969,4 +1078,94 @@ guest_handle_nested_load_code(struct api_call_nested_load_code* cmd, uint64 cpu_
9691078
}
9701079
}
9711080

1081+
GUEST_CODE static noinline void
1082+
guest_handle_nested_vmentry_intel(struct api_call_1* cmd, uint64 cpu_id, bool is_launch)
1083+
{
1084+
uint64 vm_id = cmd->arg;
1085+
uint64 vmx_error_code = 0;
1086+
uint8 fail_flag = 0; // Will be 1 if EITHER CF or ZF is set
1087+
1088+
nested_vmptrld(cpu_id, vm_id);
1089+
1090+
if (is_launch) {
1091+
asm volatile(R"(
1092+
// Attempt to launch the L2 guest.
1093+
vmlaunch
1094+
// Set AL to 1 if CF=1 (VMfailValid)
1095+
setc %%al
1096+
// Set BL to 1 if ZF=1 (VMfailInvalid)
1097+
setz %%bl
1098+
or %%bl, %%al)"
1099+
: "=a"(fail_flag)
1100+
:
1101+
: "rbx", "cc", "memory");
1102+
} else {
1103+
asm volatile(R"(
1104+
// Attempt to resume the L2 guest.
1105+
vmresume
1106+
// Set AL to 1 if CF=1 (VMfailValid)
1107+
setc %%al
1108+
// Set BL to 1 if ZF=1 (VMfailInvalid)
1109+
setz %%bl
1110+
or %%bl, %%al)"
1111+
: "=a"(fail_flag)
1112+
:
1113+
: "rbx", "cc", "memory");
1114+
}
1115+
asm volatile(".globl after_vmentry_label\nafter_vmentry_label:");
1116+
if (fail_flag) {
1117+
// VMLAUNCH/VMRESUME failed, so VMCS is still valid and can be read.
1118+
vmx_error_code = vmread(VMCS_VM_INSTRUCTION_ERROR);
1119+
guest_uexit(0xE2E10000 | (uint32)vmx_error_code);
1120+
} else {
1121+
// This path is only taken if VMLAUNCH/VMRESUME truly succeeded (CF=0 and ZF=0)
1122+
// and the L2 guest has run and exited.
1123+
guest_uexit(UEXIT_STOP_L2);
1124+
}
1125+
}
1126+
1127+
GUEST_CODE static noinline void
1128+
guest_run_amd_vm(uint64 cpu_id, uint64 vm_id)
1129+
{
1130+
uint64 vmcb_addr = X86_SYZOS_ADDR_VMCS_VMCB(cpu_id, vm_id);
1131+
volatile uint8* vmcb_ptr = (volatile uint8*)vmcb_addr;
1132+
uint8 fail_flag = 0;
1133+
1134+
asm volatile(
1135+
"mov %1, %%rax\n\t" // Load VMCB physical address into RAX
1136+
"vmrun\n\t" // Launch or resume L2 guest
1137+
"setc %0\n\t"
1138+
: "=q"(fail_flag)
1139+
: "m"(vmcb_addr)
1140+
: "rax", "cc", "memory");
1141+
1142+
if (fail_flag) {
1143+
// VMRUN failed.
1144+
guest_uexit(0xE2E10000 | 0xFFFF);
1145+
return;
1146+
}
1147+
1148+
// VMRUN succeeded and we have a VM-exit.
1149+
uint64 exit_reason = vmcb_read64(vmcb_ptr, VMCB_EXIT_CODE);
1150+
nested_vm_exit_handler_amd(exit_reason, cpu_id, vm_id);
1151+
guest_uexit(UEXIT_STOP_L2);
1152+
}
1153+
1154+
GUEST_CODE static noinline void
1155+
guest_handle_nested_vmlaunch_amd(struct api_call_1* cmd, uint64 cpu_id, uint64 vm_id)
1156+
{
1157+
guest_run_amd_vm(cpu_id, vm_id);
1158+
}
1159+
1160+
GUEST_CODE static noinline void
1161+
guest_handle_nested_vmlaunch(struct api_call_1* cmd, uint64 cpu_id)
1162+
{
1163+
uint64 vm_id = cmd->arg;
1164+
if (get_cpu_vendor() == CPU_VENDOR_INTEL) {
1165+
guest_handle_nested_vmentry_intel(cmd, cpu_id, true);
1166+
} else {
1167+
guest_handle_nested_vmlaunch_amd(cmd, cpu_id, vm_id);
1168+
}
1169+
}
1170+
9721171
#endif // EXECUTOR_COMMON_KVM_AMD64_SYZOS_H

executor/kvm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@
298298
#define VMCS_VM_ENTRY_INTR_INFO_FIELD 0x00004016
299299
#define VMCS_TPR_THRESHOLD 0x0000401c
300300
#define VMCS_SECONDARY_VM_EXEC_CONTROL 0x0000401e
301+
#define VMCS_VM_INSTRUCTION_ERROR 0x00004400
302+
#define VMCS_VM_EXIT_REASON 0x00004402
301303
#define VMCS_VMX_PREEMPTION_TIMER_VALUE 0x0000482e
302304
#define VMCS_CR0_GUEST_HOST_MASK 0x00006000
303305
#define VMCS_CR4_GUEST_HOST_MASK 0x00006002

sys/linux/dev_kvm_amd64.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ syzos_api_call$x86 [
116116
enable_nested syzos_api$x86[300, const[0, intptr]]
117117
nested_create_vm syzos_api$x86[301, syzos_api_vm_id]
118118
nested_load_code syzos_api$x86[302, syzos_api_nested_load_code]
119+
nested_vmlaunch syzos_api$x86[303, syzos_api_vm_id]
119120
] [varlen]
120121

121122
kvm_text_x86 [

0 commit comments

Comments
 (0)