Skip to content

Commit 2428865

Browse files
committed
KVM: selftests: Add a nested (forced) emulation intercept test for x86
Add a rudimentary test for validating KVM's handling of L1 hypervisor intercepts during instruction emulation on behalf of L2. To minimize complexity and avoid overlap with other tests, only validate KVM's handling of instructions that L1 wants to intercept, i.e. that generate a nested VM-Exit. Full testing of emulation on behalf of L2 is better achieved by running existing (forced) emulation tests in a VM, (although on VMX, getting L0 to emulate on #UD requires modifying either L1 KVM to not intercept #UD, or modifying L0 KVM to prioritize L0's exception intercepts over L1's intercepts, as is done by KVM for SVM). Since emulation should never be successful, i.e. L2 always exits to L1, dynamically generate the L2 code stream instead of adding a helper for each instruction. Doing so requires hand coding instruction opcodes, but makes it significantly easier for the test to compute the expected "next RIP" and instruction length. Link: https://lore.kernel.org/r/20250201015518.689704-12-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
1 parent 79a14af commit 2428865

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

tools/testing/selftests/kvm/Makefile.kvm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ TEST_GEN_PROGS_x86 += x86/hyperv_tlb_flush
6969
TEST_GEN_PROGS_x86 += x86/kvm_clock_test
7070
TEST_GEN_PROGS_x86 += x86/kvm_pv_test
7171
TEST_GEN_PROGS_x86 += x86/monitor_mwait_test
72+
TEST_GEN_PROGS_x86 += x86/nested_emulation_test
7273
TEST_GEN_PROGS_x86 += x86/nested_exceptions_test
7374
TEST_GEN_PROGS_x86 += x86/platform_info_test
7475
TEST_GEN_PROGS_x86 += x86/pmu_counters_test
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
#include "test_util.h"
3+
#include "kvm_util.h"
4+
#include "processor.h"
5+
#include "vmx.h"
6+
#include "svm_util.h"
7+
8+
enum {
9+
SVM_F,
10+
VMX_F,
11+
NR_VIRTUALIZATION_FLAVORS,
12+
};
13+
14+
struct emulated_instruction {
15+
const char name[32];
16+
uint8_t opcode[15];
17+
uint32_t exit_reason[NR_VIRTUALIZATION_FLAVORS];
18+
};
19+
20+
static struct emulated_instruction instructions[] = {
21+
{
22+
.name = "pause",
23+
.opcode = { 0xf3, 0x90 },
24+
.exit_reason = { SVM_EXIT_PAUSE,
25+
EXIT_REASON_PAUSE_INSTRUCTION, }
26+
},
27+
{
28+
.name = "hlt",
29+
.opcode = { 0xf4 },
30+
.exit_reason = { SVM_EXIT_HLT,
31+
EXIT_REASON_HLT, }
32+
},
33+
};
34+
35+
static uint8_t kvm_fep[] = { 0x0f, 0x0b, 0x6b, 0x76, 0x6d }; /* ud2 ; .ascii "kvm" */
36+
static uint8_t l2_guest_code[sizeof(kvm_fep) + 15];
37+
static uint8_t *l2_instruction = &l2_guest_code[sizeof(kvm_fep)];
38+
39+
static uint32_t get_instruction_length(struct emulated_instruction *insn)
40+
{
41+
uint32_t i;
42+
43+
for (i = 0; i < ARRAY_SIZE(insn->opcode) && insn->opcode[i]; i++)
44+
;
45+
46+
return i;
47+
}
48+
49+
static void guest_code(void *test_data)
50+
{
51+
int f = this_cpu_has(X86_FEATURE_SVM) ? SVM_F : VMX_F;
52+
int i;
53+
54+
memcpy(l2_guest_code, kvm_fep, sizeof(kvm_fep));
55+
56+
if (f == SVM_F) {
57+
struct svm_test_data *svm = test_data;
58+
struct vmcb *vmcb = svm->vmcb;
59+
60+
generic_svm_setup(svm, NULL, NULL);
61+
vmcb->save.idtr.limit = 0;
62+
vmcb->save.rip = (u64)l2_guest_code;
63+
64+
vmcb->control.intercept |= BIT_ULL(INTERCEPT_SHUTDOWN) |
65+
BIT_ULL(INTERCEPT_PAUSE) |
66+
BIT_ULL(INTERCEPT_HLT);
67+
vmcb->control.intercept_exceptions = 0;
68+
} else {
69+
GUEST_ASSERT(prepare_for_vmx_operation(test_data));
70+
GUEST_ASSERT(load_vmcs(test_data));
71+
72+
prepare_vmcs(test_data, NULL, NULL);
73+
GUEST_ASSERT(!vmwrite(GUEST_IDTR_LIMIT, 0));
74+
GUEST_ASSERT(!vmwrite(GUEST_RIP, (u64)l2_guest_code));
75+
GUEST_ASSERT(!vmwrite(EXCEPTION_BITMAP, 0));
76+
77+
vmwrite(CPU_BASED_VM_EXEC_CONTROL, vmreadz(CPU_BASED_VM_EXEC_CONTROL) |
78+
CPU_BASED_PAUSE_EXITING |
79+
CPU_BASED_HLT_EXITING);
80+
}
81+
82+
for (i = 0; i < ARRAY_SIZE(instructions); i++) {
83+
struct emulated_instruction *insn = &instructions[i];
84+
uint32_t insn_len = get_instruction_length(insn);
85+
uint32_t exit_insn_len;
86+
u32 exit_reason;
87+
88+
/*
89+
* Copy the target instruction to the L2 code stream, and fill
90+
* the remaining bytes with INT3s so that a missed intercept
91+
* results in a consistent failure mode (SHUTDOWN).
92+
*/
93+
memcpy(l2_instruction, insn->opcode, insn_len);
94+
memset(l2_instruction + insn_len, 0xcc, sizeof(insn->opcode) - insn_len);
95+
96+
if (f == SVM_F) {
97+
struct svm_test_data *svm = test_data;
98+
struct vmcb *vmcb = svm->vmcb;
99+
100+
run_guest(vmcb, svm->vmcb_gpa);
101+
exit_reason = vmcb->control.exit_code;
102+
exit_insn_len = vmcb->control.next_rip - vmcb->save.rip;
103+
GUEST_ASSERT_EQ(vmcb->save.rip, (u64)l2_instruction);
104+
} else {
105+
GUEST_ASSERT_EQ(i ? vmresume() : vmlaunch(), 0);
106+
exit_reason = vmreadz(VM_EXIT_REASON);
107+
exit_insn_len = vmreadz(VM_EXIT_INSTRUCTION_LEN);
108+
GUEST_ASSERT_EQ(vmreadz(GUEST_RIP), (u64)l2_instruction);
109+
}
110+
111+
__GUEST_ASSERT(exit_reason == insn->exit_reason[f],
112+
"Wanted exit_reason '0x%x' for '%s', got '0x%x'",
113+
insn->exit_reason[f], insn->name, exit_reason);
114+
115+
__GUEST_ASSERT(exit_insn_len == insn_len,
116+
"Wanted insn_len '%u' for '%s', got '%u'",
117+
insn_len, insn->name, exit_insn_len);
118+
}
119+
120+
GUEST_DONE();
121+
}
122+
123+
int main(int argc, char *argv[])
124+
{
125+
vm_vaddr_t nested_test_data_gva;
126+
struct kvm_vcpu *vcpu;
127+
struct kvm_vm *vm;
128+
129+
TEST_REQUIRE(is_forced_emulation_enabled);
130+
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX));
131+
132+
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
133+
vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, -2ul);
134+
135+
if (kvm_cpu_has(X86_FEATURE_SVM))
136+
vcpu_alloc_svm(vm, &nested_test_data_gva);
137+
else
138+
vcpu_alloc_vmx(vm, &nested_test_data_gva);
139+
140+
vcpu_args_set(vcpu, 1, nested_test_data_gva);
141+
142+
vcpu_run(vcpu);
143+
TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
144+
145+
kvm_vm_free(vm);
146+
}

0 commit comments

Comments
 (0)