1 // SPDX-License-Identifier: GPL-2.0-only
2 #include "test_util.h"
3 #include "kvm_util.h"
4 #include "processor.h"
5
6 #include <signal.h>
7 #include <string.h>
8 #include <sys/ioctl.h>
9 #include <sys/time.h>
10
11 #include "kselftest.h"
12
13 #define VCPU_ID 0
14
15 static struct kvm_vm *vm;
16
guest_ud_handler(struct ex_regs * regs)17 static void guest_ud_handler(struct ex_regs *regs)
18 {
19 /* Loop on the ud2 until guest state is made invalid. */
20 }
21
guest_code(void)22 static void guest_code(void)
23 {
24 asm volatile("ud2");
25 }
26
__run_vcpu_with_invalid_state(void)27 static void __run_vcpu_with_invalid_state(void)
28 {
29 struct kvm_run *run = vcpu_state(vm, VCPU_ID);
30
31 vcpu_run(vm, VCPU_ID);
32
33 TEST_ASSERT(run->exit_reason == KVM_EXIT_INTERNAL_ERROR,
34 "Expected KVM_EXIT_INTERNAL_ERROR, got %d (%s)\n",
35 run->exit_reason, exit_reason_str(run->exit_reason));
36 TEST_ASSERT(run->emulation_failure.suberror == KVM_INTERNAL_ERROR_EMULATION,
37 "Expected emulation failure, got %d\n",
38 run->emulation_failure.suberror);
39 }
40
run_vcpu_with_invalid_state(void)41 static void run_vcpu_with_invalid_state(void)
42 {
43 /*
44 * Always run twice to verify KVM handles the case where _KVM_ queues
45 * an exception with invalid state and then exits to userspace, i.e.
46 * that KVM doesn't explode if userspace ignores the initial error.
47 */
48 __run_vcpu_with_invalid_state();
49 __run_vcpu_with_invalid_state();
50 }
51
set_timer(void)52 static void set_timer(void)
53 {
54 struct itimerval timer;
55
56 timer.it_value.tv_sec = 0;
57 timer.it_value.tv_usec = 200;
58 timer.it_interval = timer.it_value;
59 ASSERT_EQ(setitimer(ITIMER_REAL, &timer, NULL), 0);
60 }
61
set_or_clear_invalid_guest_state(bool set)62 static void set_or_clear_invalid_guest_state(bool set)
63 {
64 static struct kvm_sregs sregs;
65
66 if (!sregs.cr0)
67 vcpu_sregs_get(vm, VCPU_ID, &sregs);
68 sregs.tr.unusable = !!set;
69 vcpu_sregs_set(vm, VCPU_ID, &sregs);
70 }
71
set_invalid_guest_state(void)72 static void set_invalid_guest_state(void)
73 {
74 set_or_clear_invalid_guest_state(true);
75 }
76
clear_invalid_guest_state(void)77 static void clear_invalid_guest_state(void)
78 {
79 set_or_clear_invalid_guest_state(false);
80 }
81
sigalrm_handler(int sig)82 static void sigalrm_handler(int sig)
83 {
84 struct kvm_vcpu_events events;
85
86 TEST_ASSERT(sig == SIGALRM, "Unexpected signal = %d", sig);
87
88 vcpu_events_get(vm, VCPU_ID, &events);
89
90 /*
91 * If an exception is pending, attempt KVM_RUN with invalid guest,
92 * otherwise rearm the timer and keep doing so until the timer fires
93 * between KVM queueing an exception and re-entering the guest.
94 */
95 if (events.exception.pending) {
96 set_invalid_guest_state();
97 run_vcpu_with_invalid_state();
98 } else {
99 set_timer();
100 }
101 }
102
main(int argc,char * argv[])103 int main(int argc, char *argv[])
104 {
105 if (!is_intel_cpu() || vm_is_unrestricted_guest(NULL)) {
106 print_skip("Must be run with kvm_intel.unrestricted_guest=0");
107 exit(KSFT_SKIP);
108 }
109
110 vm = vm_create_default(VCPU_ID, 0, (void *)guest_code);
111
112 vm_init_descriptor_tables(vm);
113 vcpu_init_descriptor_tables(vm, VCPU_ID);
114
115 vm_install_exception_handler(vm, UD_VECTOR, guest_ud_handler);
116
117 /*
118 * Stuff invalid guest state for L2 by making TR unusuable. The next
119 * KVM_RUN should induce a TRIPLE_FAULT in L2 as KVM doesn't support
120 * emulating invalid guest state for L2.
121 */
122 set_invalid_guest_state();
123 run_vcpu_with_invalid_state();
124
125 /*
126 * Verify KVM also handles the case where userspace gains control while
127 * an exception is pending and stuffs invalid state. Run with valid
128 * guest state and a timer firing every 200us, and attempt to enter the
129 * guest with invalid state when the handler interrupts KVM with an
130 * exception pending.
131 */
132 clear_invalid_guest_state();
133 TEST_ASSERT(signal(SIGALRM, sigalrm_handler) != SIG_ERR,
134 "Failed to register SIGALRM handler, errno = %d (%s)",
135 errno, strerror(errno));
136
137 set_timer();
138 run_vcpu_with_invalid_state();
139 }
140