brintos

brintos / linux-shallow public Read only

0
0
Text · 8.7 KiB · 3eb0313 Raw
289 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2#include "test_util.h"3#include "kvm_util.h"4#include "processor.h"5#include "vmx.h"6#include "svm_util.h"7 8#define L2_GUEST_STACK_SIZE 2569 10/*11 * Arbitrary, never shoved into KVM/hardware, just need to avoid conflict with12 * the "real" exceptions used, #SS/#GP/#DF (12/13/8).13 */14#define FAKE_TRIPLE_FAULT_VECTOR	0xaa15 16/* Arbitrary 32-bit error code injected by this test. */17#define SS_ERROR_CODE 0xdeadbeef18 19/*20 * Bit '0' is set on Intel if the exception occurs while delivering a previous21 * event/exception.  AMD's wording is ambiguous, but presumably the bit is set22 * if the exception occurs while delivering an external event, e.g. NMI or INTR,23 * but not for exceptions that occur when delivering other exceptions or24 * software interrupts.25 *26 * Note, Intel's name for it, "External event", is misleading and much more27 * aligned with AMD's behavior, but the SDM is quite clear on its behavior.28 */29#define ERROR_CODE_EXT_FLAG	BIT(0)30 31/*32 * Bit '1' is set if the fault occurred when looking up a descriptor in the33 * IDT, which is the case here as the IDT is empty/NULL.34 */35#define ERROR_CODE_IDT_FLAG	BIT(1)36 37/*38 * The #GP that occurs when vectoring #SS should show the index into the IDT39 * for #SS, plus have the "IDT flag" set.40 */41#define GP_ERROR_CODE_AMD ((SS_VECTOR * 8) | ERROR_CODE_IDT_FLAG)42#define GP_ERROR_CODE_INTEL ((SS_VECTOR * 8) | ERROR_CODE_IDT_FLAG | ERROR_CODE_EXT_FLAG)43 44/*45 * Intel and AMD both shove '0' into the error code on #DF, regardless of what46 * led to the double fault.47 */48#define DF_ERROR_CODE 049 50#define INTERCEPT_SS		(BIT_ULL(SS_VECTOR))51#define INTERCEPT_SS_DF		(INTERCEPT_SS | BIT_ULL(DF_VECTOR))52#define INTERCEPT_SS_GP_DF	(INTERCEPT_SS_DF | BIT_ULL(GP_VECTOR))53 54static void l2_ss_pending_test(void)55{56	GUEST_SYNC(SS_VECTOR);57}58 59static void l2_ss_injected_gp_test(void)60{61	GUEST_SYNC(GP_VECTOR);62}63 64static void l2_ss_injected_df_test(void)65{66	GUEST_SYNC(DF_VECTOR);67}68 69static void l2_ss_injected_tf_test(void)70{71	GUEST_SYNC(FAKE_TRIPLE_FAULT_VECTOR);72}73 74static void svm_run_l2(struct svm_test_data *svm, void *l2_code, int vector,75		       uint32_t error_code)76{77	struct vmcb *vmcb = svm->vmcb;78	struct vmcb_control_area *ctrl = &vmcb->control;79 80	vmcb->save.rip = (u64)l2_code;81	run_guest(vmcb, svm->vmcb_gpa);82 83	if (vector == FAKE_TRIPLE_FAULT_VECTOR)84		return;85 86	GUEST_ASSERT_EQ(ctrl->exit_code, (SVM_EXIT_EXCP_BASE + vector));87	GUEST_ASSERT_EQ(ctrl->exit_info_1, error_code);88}89 90static void l1_svm_code(struct svm_test_data *svm)91{92	struct vmcb_control_area *ctrl = &svm->vmcb->control;93	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];94 95	generic_svm_setup(svm, NULL, &l2_guest_stack[L2_GUEST_STACK_SIZE]);96	svm->vmcb->save.idtr.limit = 0;97	ctrl->intercept |= BIT_ULL(INTERCEPT_SHUTDOWN);98 99	ctrl->intercept_exceptions = INTERCEPT_SS_GP_DF;100	svm_run_l2(svm, l2_ss_pending_test, SS_VECTOR, SS_ERROR_CODE);101	svm_run_l2(svm, l2_ss_injected_gp_test, GP_VECTOR, GP_ERROR_CODE_AMD);102 103	ctrl->intercept_exceptions = INTERCEPT_SS_DF;104	svm_run_l2(svm, l2_ss_injected_df_test, DF_VECTOR, DF_ERROR_CODE);105 106	ctrl->intercept_exceptions = INTERCEPT_SS;107	svm_run_l2(svm, l2_ss_injected_tf_test, FAKE_TRIPLE_FAULT_VECTOR, 0);108	GUEST_ASSERT_EQ(ctrl->exit_code, SVM_EXIT_SHUTDOWN);109 110	GUEST_DONE();111}112 113static void vmx_run_l2(void *l2_code, int vector, uint32_t error_code)114{115	GUEST_ASSERT(!vmwrite(GUEST_RIP, (u64)l2_code));116 117	GUEST_ASSERT_EQ(vector == SS_VECTOR ? vmlaunch() : vmresume(), 0);118 119	if (vector == FAKE_TRIPLE_FAULT_VECTOR)120		return;121 122	GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_EXCEPTION_NMI);123	GUEST_ASSERT_EQ((vmreadz(VM_EXIT_INTR_INFO) & 0xff), vector);124	GUEST_ASSERT_EQ(vmreadz(VM_EXIT_INTR_ERROR_CODE), error_code);125}126 127static void l1_vmx_code(struct vmx_pages *vmx)128{129	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];130 131	GUEST_ASSERT_EQ(prepare_for_vmx_operation(vmx), true);132 133	GUEST_ASSERT_EQ(load_vmcs(vmx), true);134 135	prepare_vmcs(vmx, NULL, &l2_guest_stack[L2_GUEST_STACK_SIZE]);136	GUEST_ASSERT_EQ(vmwrite(GUEST_IDTR_LIMIT, 0), 0);137 138	/*139	 * VMX disallows injecting an exception with error_code[31:16] != 0,140	 * and hardware will never generate a VM-Exit with bits 31:16 set.141	 * KVM should likewise truncate the "bad" userspace value.142	 */143	GUEST_ASSERT_EQ(vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS_GP_DF), 0);144	vmx_run_l2(l2_ss_pending_test, SS_VECTOR, (u16)SS_ERROR_CODE);145	vmx_run_l2(l2_ss_injected_gp_test, GP_VECTOR, GP_ERROR_CODE_INTEL);146 147	GUEST_ASSERT_EQ(vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS_DF), 0);148	vmx_run_l2(l2_ss_injected_df_test, DF_VECTOR, DF_ERROR_CODE);149 150	GUEST_ASSERT_EQ(vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS), 0);151	vmx_run_l2(l2_ss_injected_tf_test, FAKE_TRIPLE_FAULT_VECTOR, 0);152	GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_TRIPLE_FAULT);153 154	GUEST_DONE();155}156 157static void __attribute__((__flatten__)) l1_guest_code(void *test_data)158{159	if (this_cpu_has(X86_FEATURE_SVM))160		l1_svm_code(test_data);161	else162		l1_vmx_code(test_data);163}164 165static void assert_ucall_vector(struct kvm_vcpu *vcpu, int vector)166{167	struct ucall uc;168 169	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);170 171	switch (get_ucall(vcpu, &uc)) {172	case UCALL_SYNC:173		TEST_ASSERT(vector == uc.args[1],174			    "Expected L2 to ask for %d, got %ld", vector, uc.args[1]);175		break;176	case UCALL_DONE:177		TEST_ASSERT(vector == -1,178			    "Expected L2 to ask for %d, L2 says it's done", vector);179		break;180	case UCALL_ABORT:181		REPORT_GUEST_ASSERT(uc);182		break;183	default:184		TEST_FAIL("Expected L2 to ask for %d, got unexpected ucall %lu", vector, uc.cmd);185	}186}187 188static void queue_ss_exception(struct kvm_vcpu *vcpu, bool inject)189{190	struct kvm_vcpu_events events;191 192	vcpu_events_get(vcpu, &events);193 194	TEST_ASSERT(!events.exception.pending,195		    "Vector %d unexpectedlt pending", events.exception.nr);196	TEST_ASSERT(!events.exception.injected,197		    "Vector %d unexpectedly injected", events.exception.nr);198 199	events.flags = KVM_VCPUEVENT_VALID_PAYLOAD;200	events.exception.pending = !inject;201	events.exception.injected = inject;202	events.exception.nr = SS_VECTOR;203	events.exception.has_error_code = true;204	events.exception.error_code = SS_ERROR_CODE;205	vcpu_events_set(vcpu, &events);206}207 208/*209 * Verify KVM_{G,S}ET_EVENTS play nice with pending vs. injected exceptions210 * when an exception is being queued for L2.  Specifically, verify that KVM211 * honors L1 exception intercept controls when a #SS is pending/injected,212 * triggers a #GP on vectoring the #SS, morphs to #DF if #GP isn't intercepted213 * by L1, and finally causes (nested) SHUTDOWN if #DF isn't intercepted by L1.214 */215int main(int argc, char *argv[])216{217	vm_vaddr_t nested_test_data_gva;218	struct kvm_vcpu_events events;219	struct kvm_vcpu *vcpu;220	struct kvm_vm *vm;221 222	TEST_REQUIRE(kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD));223	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX));224 225	vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);226	vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, -2ul);227 228	if (kvm_cpu_has(X86_FEATURE_SVM))229		vcpu_alloc_svm(vm, &nested_test_data_gva);230	else231		vcpu_alloc_vmx(vm, &nested_test_data_gva);232 233	vcpu_args_set(vcpu, 1, nested_test_data_gva);234 235	/* Run L1 => L2.  L2 should sync and request #SS. */236	vcpu_run(vcpu);237	assert_ucall_vector(vcpu, SS_VECTOR);238 239	/* Pend #SS and request immediate exit.  #SS should still be pending. */240	queue_ss_exception(vcpu, false);241	vcpu->run->immediate_exit = true;242	vcpu_run_complete_io(vcpu);243 244	/* Verify the pending events comes back out the same as it went in. */245	vcpu_events_get(vcpu, &events);246	TEST_ASSERT_EQ(events.flags & KVM_VCPUEVENT_VALID_PAYLOAD,247			KVM_VCPUEVENT_VALID_PAYLOAD);248	TEST_ASSERT_EQ(events.exception.pending, true);249	TEST_ASSERT_EQ(events.exception.nr, SS_VECTOR);250	TEST_ASSERT_EQ(events.exception.has_error_code, true);251	TEST_ASSERT_EQ(events.exception.error_code, SS_ERROR_CODE);252 253	/*254	 * Run for real with the pending #SS, L1 should get a VM-Exit due to255	 * #SS interception and re-enter L2 to request #GP (via injected #SS).256	 */257	vcpu->run->immediate_exit = false;258	vcpu_run(vcpu);259	assert_ucall_vector(vcpu, GP_VECTOR);260 261	/*262	 * Inject #SS, the #SS should bypass interception and cause #GP, which263	 * L1 should intercept before KVM morphs it to #DF.  L1 should then264	 * disable #GP interception and run L2 to request #DF (via #SS => #GP).265	 */266	queue_ss_exception(vcpu, true);267	vcpu_run(vcpu);268	assert_ucall_vector(vcpu, DF_VECTOR);269 270	/*271	 * Inject #SS, the #SS should bypass interception and cause #GP, which272	 * L1 is no longer interception, and so should see a #DF VM-Exit.  L1273	 * should then signal that is done.274	 */275	queue_ss_exception(vcpu, true);276	vcpu_run(vcpu);277	assert_ucall_vector(vcpu, FAKE_TRIPLE_FAULT_VECTOR);278 279	/*280	 * Inject #SS yet again.  L1 is not intercepting #GP or #DF, and so281	 * should see nested TRIPLE_FAULT / SHUTDOWN.282	 */283	queue_ss_exception(vcpu, true);284	vcpu_run(vcpu);285	assert_ucall_vector(vcpu, -1);286 287	kvm_vm_free(vm);288}289