Skip to content

Commit d79b483

Browse files
committed
Merge branch 'kvm-svm-harden' into HEAD
This fixes three issues in nested SVM: 1) in the shutdown_interception() vmexit handler we call kvm_vcpu_reset(). However, if running nested and L1 doesn't intercept shutdown, the function resets vcpu->arch.hflags without properly leaving the nested state. This leaves the vCPU in inconsistent state and later triggers a kernel panic in SVM code. The same bug can likely be triggered by sending INIT via local apic to a vCPU which runs a nested guest. On VMX we are lucky that the issue can't happen because VMX always intercepts triple faults, thus triple fault in L2 will always be redirected to L1. Plus, handle_triple_fault() doesn't reset the vCPU. INIT IPI can't happen on VMX either because INIT events are masked while in VMX mode. Secondarily, KVM doesn't honour SHUTDOWN intercept bit of L1 on SVM. A normal hypervisor should always intercept SHUTDOWN, a unit test on the other hand might want to not do so. Finally, the guest can trigger a kernel non rate limited printk on SVM from the guest, which is fixed as well. Signed-off-by: Paolo Bonzini <[email protected]>
2 parents 9eb8ca0 + 05311ce commit d79b483

File tree

10 files changed

+172
-57
lines changed

10 files changed

+172
-57
lines changed

arch/x86/kvm/svm/nested.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
10911091

10921092
static void nested_svm_triple_fault(struct kvm_vcpu *vcpu)
10931093
{
1094+
struct vcpu_svm *svm = to_svm(vcpu);
1095+
1096+
if (!vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_SHUTDOWN))
1097+
return;
1098+
1099+
kvm_clear_request(KVM_REQ_TRIPLE_FAULT, vcpu);
10941100
nested_svm_simple_vmexit(to_svm(vcpu), SVM_EXIT_SHUTDOWN);
10951101
}
10961102

@@ -1125,6 +1131,9 @@ void svm_free_nested(struct vcpu_svm *svm)
11251131
if (!svm->nested.initialized)
11261132
return;
11271133

1134+
if (WARN_ON_ONCE(svm->vmcb != svm->vmcb01.ptr))
1135+
svm_switch_vmcb(svm, &svm->vmcb01);
1136+
11281137
svm_vcpu_free_msrpm(svm->nested.msrpm);
11291138
svm->nested.msrpm = NULL;
11301139

@@ -1143,9 +1152,6 @@ void svm_free_nested(struct vcpu_svm *svm)
11431152
svm->nested.initialized = false;
11441153
}
11451154

1146-
/*
1147-
* Forcibly leave nested mode in order to be able to reset the VCPU later on.
1148-
*/
11491155
void svm_leave_nested(struct kvm_vcpu *vcpu)
11501156
{
11511157
struct vcpu_svm *svm = to_svm(vcpu);

arch/x86/kvm/svm/svm.c

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -346,12 +346,6 @@ int svm_set_efer(struct kvm_vcpu *vcpu, u64 efer)
346346
return 0;
347347
}
348348

349-
static int is_external_interrupt(u32 info)
350-
{
351-
info &= SVM_EVTINJ_TYPE_MASK | SVM_EVTINJ_VALID;
352-
return info == (SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_INTR);
353-
}
354-
355349
static u32 svm_get_interrupt_shadow(struct kvm_vcpu *vcpu)
356350
{
357351
struct vcpu_svm *svm = to_svm(vcpu);
@@ -1438,6 +1432,7 @@ static void svm_vcpu_free(struct kvm_vcpu *vcpu)
14381432
*/
14391433
svm_clear_current_vmcb(svm->vmcb);
14401434

1435+
svm_leave_nested(vcpu);
14411436
svm_free_nested(svm);
14421437

14431438
sev_free_vcpu(vcpu);
@@ -3425,15 +3420,6 @@ static int svm_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
34253420
return 0;
34263421
}
34273422

3428-
if (is_external_interrupt(svm->vmcb->control.exit_int_info) &&
3429-
exit_code != SVM_EXIT_EXCP_BASE + PF_VECTOR &&
3430-
exit_code != SVM_EXIT_NPF && exit_code != SVM_EXIT_TASK_SWITCH &&
3431-
exit_code != SVM_EXIT_INTR && exit_code != SVM_EXIT_NMI)
3432-
printk(KERN_ERR "%s: unexpected exit_int_info 0x%x "
3433-
"exit_code 0x%x\n",
3434-
__func__, svm->vmcb->control.exit_int_info,
3435-
exit_code);
3436-
34373423
if (exit_fastpath != EXIT_FASTPATH_NONE)
34383424
return 1;
34393425

arch/x86/kvm/vmx/nested.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4854,6 +4854,7 @@ void nested_vmx_vmexit(struct kvm_vcpu *vcpu, u32 vm_exit_reason,
48544854

48554855
static void nested_vmx_triple_fault(struct kvm_vcpu *vcpu)
48564856
{
4857+
kvm_clear_request(KVM_REQ_TRIPLE_FAULT, vcpu);
48574858
nested_vmx_vmexit(vcpu, EXIT_REASON_TRIPLE_FAULT, 0, 0);
48584859
}
48594860

@@ -6440,9 +6441,6 @@ static int vmx_get_nested_state(struct kvm_vcpu *vcpu,
64406441
return kvm_state.size;
64416442
}
64426443

6443-
/*
6444-
* Forcibly leave nested mode in order to be able to reset the VCPU later on.
6445-
*/
64466444
void vmx_leave_nested(struct kvm_vcpu *vcpu)
64476445
{
64486446
if (is_guest_mode(vcpu)) {

arch/x86/kvm/x86.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,12 @@ static void kvm_queue_exception_vmexit(struct kvm_vcpu *vcpu, unsigned int vecto
628628
ex->payload = payload;
629629
}
630630

631+
/* Forcibly leave the nested mode in cases like a vCPU reset */
632+
static void kvm_leave_nested(struct kvm_vcpu *vcpu)
633+
{
634+
kvm_x86_ops.nested_ops->leave_nested(vcpu);
635+
}
636+
631637
static void kvm_multiple_exception(struct kvm_vcpu *vcpu,
632638
unsigned nr, bool has_error, u32 error_code,
633639
bool has_payload, unsigned long payload, bool reinject)
@@ -5195,7 +5201,7 @@ static int kvm_vcpu_ioctl_x86_set_vcpu_events(struct kvm_vcpu *vcpu,
51955201

51965202
if (events->flags & KVM_VCPUEVENT_VALID_SMM) {
51975203
if (!!(vcpu->arch.hflags & HF_SMM_MASK) != events->smi.smm) {
5198-
kvm_x86_ops.nested_ops->leave_nested(vcpu);
5204+
kvm_leave_nested(vcpu);
51995205
kvm_smm_changed(vcpu, events->smi.smm);
52005206
}
52015207

@@ -9805,7 +9811,7 @@ static void update_cr8_intercept(struct kvm_vcpu *vcpu)
98059811

98069812
int kvm_check_nested_events(struct kvm_vcpu *vcpu)
98079813
{
9808-
if (kvm_check_request(KVM_REQ_TRIPLE_FAULT, vcpu)) {
9814+
if (kvm_test_request(KVM_REQ_TRIPLE_FAULT, vcpu)) {
98099815
kvm_x86_ops.nested_ops->triple_fault(vcpu);
98109816
return 1;
98119817
}
@@ -10560,15 +10566,16 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
1056010566
r = 0;
1056110567
goto out;
1056210568
}
10563-
if (kvm_check_request(KVM_REQ_TRIPLE_FAULT, vcpu)) {
10564-
if (is_guest_mode(vcpu)) {
10569+
if (kvm_test_request(KVM_REQ_TRIPLE_FAULT, vcpu)) {
10570+
if (is_guest_mode(vcpu))
1056510571
kvm_x86_ops.nested_ops->triple_fault(vcpu);
10566-
} else {
10572+
10573+
if (kvm_check_request(KVM_REQ_TRIPLE_FAULT, vcpu)) {
1056710574
vcpu->run->exit_reason = KVM_EXIT_SHUTDOWN;
1056810575
vcpu->mmio_needed = 0;
1056910576
r = 0;
10570-
goto out;
1057110577
}
10578+
goto out;
1057210579
}
1057310580
if (kvm_check_request(KVM_REQ_APF_HALT, vcpu)) {
1057410581
/* Page is swapped out. Do synthetic halt */
@@ -11997,8 +12004,18 @@ void kvm_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
1199712004
WARN_ON_ONCE(!init_event &&
1199812005
(old_cr0 || kvm_read_cr3(vcpu) || kvm_read_cr4(vcpu)));
1199912006

12007+
/*
12008+
* SVM doesn't unconditionally VM-Exit on INIT and SHUTDOWN, thus it's
12009+
* possible to INIT the vCPU while L2 is active. Force the vCPU back
12010+
* into L1 as EFER.SVME is cleared on INIT (along with all other EFER
12011+
* bits), i.e. virtualization is disabled.
12012+
*/
12013+
if (is_guest_mode(vcpu))
12014+
kvm_leave_nested(vcpu);
12015+
1200012016
kvm_lapic_reset(vcpu, init_event);
1200112017

12018+
WARN_ON_ONCE(is_guest_mode(vcpu) || is_smm(vcpu));
1200212019
vcpu->arch.hflags = 0;
1200312020

1200412021
vcpu->arch.smi_pending = 0;

tools/testing/selftests/kvm/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
/x86_64/svm_vmcall_test
4242
/x86_64/svm_int_ctl_test
4343
/x86_64/svm_nested_soft_inject_test
44+
/x86_64/svm_nested_shutdown_test
4445
/x86_64/sync_regs_test
4546
/x86_64/tsc_msrs_test
4647
/x86_64/tsc_scaling_sync

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/state_test
101101
TEST_GEN_PROGS_x86_64 += x86_64/vmx_preemption_timer_test
102102
TEST_GEN_PROGS_x86_64 += x86_64/svm_vmcall_test
103103
TEST_GEN_PROGS_x86_64 += x86_64/svm_int_ctl_test
104+
TEST_GEN_PROGS_x86_64 += x86_64/svm_nested_shutdown_test
104105
TEST_GEN_PROGS_x86_64 += x86_64/svm_nested_soft_inject_test
105106
TEST_GEN_PROGS_x86_64 += x86_64/tsc_scaling_sync
106107
TEST_GEN_PROGS_x86_64 += x86_64/sync_regs_test

tools/testing/selftests/kvm/include/x86_64/processor.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,19 @@ struct ex_regs {
748748
uint64_t rflags;
749749
};
750750

751+
struct idt_entry {
752+
uint16_t offset0;
753+
uint16_t selector;
754+
uint16_t ist : 3;
755+
uint16_t : 5;
756+
uint16_t type : 4;
757+
uint16_t : 1;
758+
uint16_t dpl : 2;
759+
uint16_t p : 1;
760+
uint16_t offset1;
761+
uint32_t offset2; uint32_t reserved;
762+
};
763+
751764
void vm_init_descriptor_tables(struct kvm_vm *vm);
752765
void vcpu_init_descriptor_tables(struct kvm_vcpu *vcpu);
753766
void vm_install_exception_handler(struct kvm_vm *vm, int vector,

tools/testing/selftests/kvm/lib/x86_64/processor.c

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,19 +1074,6 @@ void kvm_get_cpu_address_width(unsigned int *pa_bits, unsigned int *va_bits)
10741074
}
10751075
}
10761076

1077-
struct idt_entry {
1078-
uint16_t offset0;
1079-
uint16_t selector;
1080-
uint16_t ist : 3;
1081-
uint16_t : 5;
1082-
uint16_t type : 4;
1083-
uint16_t : 1;
1084-
uint16_t dpl : 2;
1085-
uint16_t p : 1;
1086-
uint16_t offset1;
1087-
uint32_t offset2; uint32_t reserved;
1088-
};
1089-
10901077
static void set_idt_entry(struct kvm_vm *vm, int vector, unsigned long addr,
10911078
int dpl, unsigned short selector)
10921079
{
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* svm_nested_shutdown_test
4+
*
5+
* Copyright (C) 2022, Red Hat, Inc.
6+
*
7+
* Nested SVM testing: test that unintercepted shutdown in L2 doesn't crash the host
8+
*/
9+
10+
#include "test_util.h"
11+
#include "kvm_util.h"
12+
#include "processor.h"
13+
#include "svm_util.h"
14+
15+
static void l2_guest_code(struct svm_test_data *svm)
16+
{
17+
__asm__ __volatile__("ud2");
18+
}
19+
20+
static void l1_guest_code(struct svm_test_data *svm, struct idt_entry *idt)
21+
{
22+
#define L2_GUEST_STACK_SIZE 64
23+
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
24+
struct vmcb *vmcb = svm->vmcb;
25+
26+
generic_svm_setup(svm, l2_guest_code,
27+
&l2_guest_stack[L2_GUEST_STACK_SIZE]);
28+
29+
vmcb->control.intercept &= ~(BIT(INTERCEPT_SHUTDOWN));
30+
31+
idt[6].p = 0; // #UD is intercepted but its injection will cause #NP
32+
idt[11].p = 0; // #NP is not intercepted and will cause another
33+
// #NP that will be converted to #DF
34+
idt[8].p = 0; // #DF will cause #NP which will cause SHUTDOWN
35+
36+
run_guest(vmcb, svm->vmcb_gpa);
37+
38+
/* should not reach here */
39+
GUEST_ASSERT(0);
40+
}
41+
42+
int main(int argc, char *argv[])
43+
{
44+
struct kvm_vcpu *vcpu;
45+
struct kvm_run *run;
46+
vm_vaddr_t svm_gva;
47+
struct kvm_vm *vm;
48+
49+
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
50+
51+
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
52+
vm_init_descriptor_tables(vm);
53+
vcpu_init_descriptor_tables(vcpu);
54+
55+
vcpu_alloc_svm(vm, &svm_gva);
56+
57+
vcpu_args_set(vcpu, 2, svm_gva, vm->idt);
58+
run = vcpu->run;
59+
60+
vcpu_run(vcpu);
61+
TEST_ASSERT(run->exit_reason == KVM_EXIT_SHUTDOWN,
62+
"Got exit_reason other than KVM_EXIT_SHUTDOWN: %u (%s)\n",
63+
run->exit_reason,
64+
exit_reason_str(run->exit_reason));
65+
66+
kvm_vm_free(vm);
67+
}

tools/testing/selftests/kvm/x86_64/triple_fault_event_test.c

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "kvm_util.h"
44
#include "processor.h"
55
#include "vmx.h"
6+
#include "svm_util.h"
67

78
#include <string.h>
89
#include <sys/ioctl.h>
@@ -20,10 +21,11 @@ static void l2_guest_code(void)
2021
: : [port] "d" (ARBITRARY_IO_PORT) : "rax");
2122
}
2223

23-
void l1_guest_code(struct vmx_pages *vmx)
24-
{
2524
#define L2_GUEST_STACK_SIZE 64
26-
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
25+
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
26+
27+
void l1_guest_code_vmx(struct vmx_pages *vmx)
28+
{
2729

2830
GUEST_ASSERT(vmx->vmcs_gpa);
2931
GUEST_ASSERT(prepare_for_vmx_operation(vmx));
@@ -38,24 +40,53 @@ void l1_guest_code(struct vmx_pages *vmx)
3840
GUEST_DONE();
3941
}
4042

43+
void l1_guest_code_svm(struct svm_test_data *svm)
44+
{
45+
struct vmcb *vmcb = svm->vmcb;
46+
47+
generic_svm_setup(svm, l2_guest_code,
48+
&l2_guest_stack[L2_GUEST_STACK_SIZE]);
49+
50+
/* don't intercept shutdown to test the case of SVM allowing to do so */
51+
vmcb->control.intercept &= ~(BIT(INTERCEPT_SHUTDOWN));
52+
53+
run_guest(vmcb, svm->vmcb_gpa);
54+
55+
/* should not reach here, L1 should crash */
56+
GUEST_ASSERT(0);
57+
}
58+
4159
int main(void)
4260
{
4361
struct kvm_vcpu *vcpu;
4462
struct kvm_run *run;
4563
struct kvm_vcpu_events events;
46-
vm_vaddr_t vmx_pages_gva;
4764
struct ucall uc;
4865

49-
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
66+
bool has_vmx = kvm_cpu_has(X86_FEATURE_VMX);
67+
bool has_svm = kvm_cpu_has(X86_FEATURE_SVM);
68+
69+
TEST_REQUIRE(has_vmx || has_svm);
5070

5171
TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_TRIPLE_FAULT_EVENT));
5272

53-
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
54-
vm_enable_cap(vm, KVM_CAP_X86_TRIPLE_FAULT_EVENT, 1);
5573

74+
if (has_vmx) {
75+
vm_vaddr_t vmx_pages_gva;
76+
77+
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code_vmx);
78+
vcpu_alloc_vmx(vm, &vmx_pages_gva);
79+
vcpu_args_set(vcpu, 1, vmx_pages_gva);
80+
} else {
81+
vm_vaddr_t svm_gva;
82+
83+
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code_svm);
84+
vcpu_alloc_svm(vm, &svm_gva);
85+
vcpu_args_set(vcpu, 1, svm_gva);
86+
}
87+
88+
vm_enable_cap(vm, KVM_CAP_X86_TRIPLE_FAULT_EVENT, 1);
5689
run = vcpu->run;
57-
vcpu_alloc_vmx(vm, &vmx_pages_gva);
58-
vcpu_args_set(vcpu, 1, vmx_pages_gva);
5990
vcpu_run(vcpu);
6091

6192
TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
@@ -78,13 +109,21 @@ int main(void)
78109
"No triple fault pending");
79110
vcpu_run(vcpu);
80111

81-
switch (get_ucall(vcpu, &uc)) {
82-
case UCALL_DONE:
83-
break;
84-
case UCALL_ABORT:
85-
REPORT_GUEST_ASSERT(uc);
86-
default:
87-
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
88-
}
89112

113+
if (has_svm) {
114+
TEST_ASSERT(run->exit_reason == KVM_EXIT_SHUTDOWN,
115+
"Got exit_reason other than KVM_EXIT_SHUTDOWN: %u (%s)\n",
116+
run->exit_reason,
117+
exit_reason_str(run->exit_reason));
118+
} else {
119+
switch (get_ucall(vcpu, &uc)) {
120+
case UCALL_DONE:
121+
break;
122+
case UCALL_ABORT:
123+
REPORT_GUEST_ASSERT(uc);
124+
default:
125+
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
126+
}
127+
}
128+
return 0;
90129
}

0 commit comments

Comments
 (0)