aboutsummaryrefslogtreecommitdiffstats
path: root/tools/testing/selftests/kvm/x86_64/vmx_exception_with_invalid_guest_state.c
blob: 2641b286b4ed2707e72e2d0f7868dd9e8d9a1552 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SPDX-License-Identifier: GPL-2.0-only
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"

#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#include "kselftest.h"

static void guest_ud_handler(struct ex_regs *regs)
{
	/* Loop on the ud2 until guest state is made invalid. */
}

static void guest_code(void)
{
	asm volatile("ud2");
}

static void __run_vcpu_with_invalid_state(struct kvm_vcpu *vcpu)
{
	struct kvm_run *run = vcpu->run;

	vcpu_run(vcpu);

	TEST_ASSERT(run->exit_reason == KVM_EXIT_INTERNAL_ERROR,
		    "Expected KVM_EXIT_INTERNAL_ERROR, got %d (%s)\n",
		    run->exit_reason, exit_reason_str(run->exit_reason));
	TEST_ASSERT(run->emulation_failure.suberror == KVM_INTERNAL_ERROR_EMULATION,
		    "Expected emulation failure, got %d\n",
		    run->emulation_failure.suberror);
}

static void run_vcpu_with_invalid_state(struct kvm_vcpu *vcpu)
{
	/*
	 * Always run twice to verify KVM handles the case where _KVM_ queues
	 * an exception with invalid state and then exits to userspace, i.e.
	 * that KVM doesn't explode if userspace ignores the initial error.
	 */
	__run_vcpu_with_invalid_state(vcpu);
	__run_vcpu_with_invalid_state(vcpu);
}

static void set_timer(void)
{
	struct itimerval timer;

	timer.it_value.tv_sec  = 0;
	timer.it_value.tv_usec = 200;
	timer.it_interval = timer.it_value;
	ASSERT_EQ(setitimer(ITIMER_REAL, &timer, NULL), 0);
}

static void set_or_clear_invalid_guest_state(struct kvm_vcpu *vcpu, bool set)
{
	static struct kvm_sregs sregs;

	if (!sregs.cr0)
		vcpu_sregs_get(vcpu, &sregs);
	sregs.tr.unusable = !!set;
	vcpu_sregs_set(vcpu, &sregs);
}

static void set_invalid_guest_state(struct kvm_vcpu *vcpu)
{
	set_or_clear_invalid_guest_state(vcpu, true);
}

static void clear_invalid_guest_state(struct kvm_vcpu *vcpu)
{
	set_or_clear_invalid_guest_state(vcpu, false);
}

static struct kvm_vcpu *get_set_sigalrm_vcpu(struct kvm_vcpu *__vcpu)
{
	static struct kvm_vcpu *vcpu = NULL;

	if (__vcpu)
		vcpu = __vcpu;
	return vcpu;
}

static void sigalrm_handler(int sig)
{
	struct kvm_vcpu *vcpu = get_set_sigalrm_vcpu(NULL);
	struct kvm_vcpu_events events;

	TEST_ASSERT(sig == SIGALRM, "Unexpected signal = %d", sig);

	vcpu_events_get(vcpu, &events);

	/*
	 * If an exception is pending, attempt KVM_RUN with invalid guest,
	 * otherwise rearm the timer and keep doing so until the timer fires
	 * between KVM queueing an exception and re-entering the guest.
	 */
	if (events.exception.pending) {
		set_invalid_guest_state(vcpu);
		run_vcpu_with_invalid_state(vcpu);
	} else {
		set_timer();
	}
}

int main(int argc, char *argv[])
{
	struct kvm_vcpu *vcpu;
	struct kvm_vm *vm;

	TEST_REQUIRE(is_intel_cpu());
	TEST_REQUIRE(!vm_is_unrestricted_guest(NULL));

	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
	get_set_sigalrm_vcpu(vcpu);

	vm_init_descriptor_tables(vm);
	vcpu_init_descriptor_tables(vcpu);

	vm_install_exception_handler(vm, UD_VECTOR, guest_ud_handler);

	/*
	 * Stuff invalid guest state for L2 by making TR unusuable.  The next
	 * KVM_RUN should induce a TRIPLE_FAULT in L2 as KVM doesn't support
	 * emulating invalid guest state for L2.
	 */
	set_invalid_guest_state(vcpu);
	run_vcpu_with_invalid_state(vcpu);

	/*
	 * Verify KVM also handles the case where userspace gains control while
	 * an exception is pending and stuffs invalid state.  Run with valid
	 * guest state and a timer firing every 200us, and attempt to enter the
	 * guest with invalid state when the handler interrupts KVM with an
	 * exception pending.
	 */
	clear_invalid_guest_state(vcpu);
	TEST_ASSERT(signal(SIGALRM, sigalrm_handler) != SIG_ERR,
		    "Failed to register SIGALRM handler, errno = %d (%s)",
		    errno, strerror(errno));

	set_timer();
	run_vcpu_with_invalid_state(vcpu);
}