1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2026, Red Hat, Inc. 4 * 5 * Test that vmx_leave_smm() validates vmcs12 controls before re-entering 6 * nested guest mode on RSM. 7 */ 8 #include <fcntl.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <sys/ioctl.h> 13 14 #include "test_util.h" 15 #include "kvm_util.h" 16 #include "smm.h" 17 #include "hyperv.h" 18 #include "vmx.h" 19 20 #define SMRAM_GPA 0x1000000 21 #define SMRAM_STAGE 0xfe 22 23 #define SYNC_PORT 0xe 24 25 /* 26 * SMI handler: runs in real-address mode. 27 * Reports SMRAM_STAGE via port IO, then does RSM. 28 */ 29 static u8 smi_handler[] = { 30 0xb0, SMRAM_STAGE, /* mov $SMRAM_STAGE, %al */ 31 0xe4, SYNC_PORT, /* in $SYNC_PORT, %al */ 32 0x0f, 0xaa, /* rsm */ 33 }; 34 35 static inline void sync_with_host(u64 phase) 36 { 37 asm volatile("in $" __stringify(SYNC_PORT) ", %%al \n" 38 : "+a" (phase)); 39 } 40 41 static void l2_guest_code(void) 42 { 43 sync_with_host(1); 44 45 /* After SMI+RSM with invalid controls, we should not reach here. */ 46 vmcall(); 47 } 48 49 static void guest_code(struct vmx_pages *vmx_pages, 50 struct hyperv_test_pages *hv_pages) 51 { 52 53 /* Set up Hyper-V enlightenments and eVMCS */ 54 wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); 55 enable_vp_assist(hv_pages->vp_assist_gpa, hv_pages->vp_assist); 56 evmcs_enable(); 57 58 GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages)); 59 GUEST_ASSERT(load_evmcs(hv_pages)); 60 prepare_vmcs(vmx_pages, l2_guest_code); 61 62 GUEST_ASSERT(!vmlaunch()); 63 64 /* L2 exits via vmcall if test fails */ 65 sync_with_host(2); 66 } 67 68 int main(int argc, char *argv[]) 69 { 70 gva_t vmx_pages_gva = 0, hv_pages_gva = 0; 71 struct hyperv_test_pages *hv; 72 struct hv_enlightened_vmcs *evmcs; 73 struct kvm_vcpu *vcpu; 74 struct kvm_vm *vm; 75 struct kvm_regs regs; 76 int stage_reported; 77 78 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX)); 79 TEST_REQUIRE(kvm_has_cap(KVM_CAP_NESTED_STATE)); 80 TEST_REQUIRE(kvm_has_cap(KVM_CAP_HYPERV_ENLIGHTENED_VMCS)); 81 TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_SMM)); 82 83 vm = vm_create_with_one_vcpu(&vcpu, guest_code); 84 85 setup_smram(vm, vcpu, SMRAM_GPA, smi_handler, sizeof(smi_handler)); 86 87 vcpu_set_hv_cpuid(vcpu); 88 vcpu_enable_evmcs(vcpu); 89 vcpu_alloc_vmx(vm, &vmx_pages_gva); 90 hv = vcpu_alloc_hyperv_test_pages(vm, &hv_pages_gva); 91 vcpu_args_set(vcpu, 2, vmx_pages_gva, hv_pages_gva); 92 93 vcpu_run(vcpu); 94 95 /* L2 is running and syncs with host. */ 96 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); 97 vcpu_regs_get(vcpu, ®s); 98 stage_reported = regs.rax & 0xff; 99 TEST_ASSERT(stage_reported == 1, 100 "Expected stage 1, got %d", stage_reported); 101 102 /* Inject SMI while L2 is running. */ 103 inject_smi(vcpu); 104 vcpu_run(vcpu); 105 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); 106 vcpu_regs_get(vcpu, ®s); 107 stage_reported = regs.rax & 0xff; 108 TEST_ASSERT(stage_reported == SMRAM_STAGE, 109 "Expected SMM handler stage %#x, got %#x", 110 SMRAM_STAGE, stage_reported); 111 112 /* 113 * Guest is now paused in the SMI handler, about to execute RSM. 114 * Hack the eVMCS page to set-up invalid pin-based execution 115 * control (PIN_BASED_VIRTUAL_NMIS without PIN_BASED_NMI_EXITING). 116 */ 117 evmcs = hv->enlightened_vmcs_hva; 118 evmcs->pin_based_vm_exec_control |= PIN_BASED_VIRTUAL_NMIS; 119 evmcs->hv_clean_fields = 0; 120 121 /* 122 * Trigger copy_enlightened_to_vmcs12() via KVM_GET_NESTED_STATE, 123 * copying the invalid pin_based_vm_exec_control into cached_vmcs12. 124 */ 125 union { 126 struct kvm_nested_state state; 127 char state_[16384]; 128 } nested_state_buf; 129 130 memset(&nested_state_buf, 0, sizeof(nested_state_buf)); 131 nested_state_buf.state.size = sizeof(nested_state_buf); 132 vcpu_nested_state_get(vcpu, &nested_state_buf.state); 133 134 /* 135 * Resume the guest. The SMI handler executes RSM, which calls 136 * vmx_leave_smm(). nested_vmx_check_controls() should detect 137 * VIRTUAL_NMIS without NMI_EXITING and cause a triple fault. 138 */ 139 vcpu_run(vcpu); 140 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_SHUTDOWN); 141 142 kvm_vm_free(vm); 143 return 0; 144 } 145