1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2023 SUSE LLC
4 * Author: Nicolai Stange <[email protected]>
5 * LTP port: Martin Doucha <[email protected]>
6 */
7
8 /*\
9 * CVE 2021-3656
10 *
11 * Check that KVM correctly intercepts VMSAVE and VMLOAD instructions
12 * in a nested virtual machine even when the parent guest disables
13 * intercepting either instruction. If KVM does not override the disabled
14 * intercepts, it'll give the nested VM read/write access to a few bytes
15 * of an arbitrary physical memory page. Unauthorized memory access fixed in:
16 *
17 * commit c7dfa4009965a9b2d7b329ee970eb8da0d32f0bc
18 * Author: Maxim Levitsky <[email protected]>
19 * Date: Mon Jul 19 16:05:00 2021 +0300
20 *
21 * KVM: nSVM: always intercept VMLOAD/VMSAVE when nested (CVE-2021-3656)
22 */
23
24 #include "kvm_test.h"
25
26 #ifdef COMPILE_PAYLOAD
27 #if defined(__i386__) || defined(__x86_64__)
28
29 #include "kvm_x86_svm.h"
30
31 static void *vmsave_buf;
32
33 /* Load FS, GS, TR and LDTR state from vmsave_buf */
guest_vmload(void)34 static int guest_vmload(void)
35 {
36 kvm_svm_vmload(vmsave_buf);
37 return 0;
38 }
39
40 /* Save current FS, GS, TR and LDTR state to vmsave_buf */
guest_vmsave(void)41 static int guest_vmsave(void)
42 {
43 kvm_svm_vmsave(vmsave_buf);
44 return 0;
45 }
46
cmp_descriptor(const struct kvm_vmcb_descriptor * a,const struct kvm_vmcb_descriptor * b)47 static int cmp_descriptor(const struct kvm_vmcb_descriptor *a,
48 const struct kvm_vmcb_descriptor *b)
49 {
50 int ret;
51
52 ret = a->selector != b->selector;
53 ret = ret || a->attrib != b->attrib;
54 ret = ret || a->limit != b->limit;
55 ret = ret || a->base != b->base;
56 return ret;
57 }
58
59 /* Return non-zero if the VMCB fields touched by vmsave/vmload differ */
cmp_vmcb(const struct kvm_vmcb * a,const struct kvm_vmcb * b)60 static int cmp_vmcb(const struct kvm_vmcb *a, const struct kvm_vmcb *b)
61 {
62 int ret;
63
64 ret = cmp_descriptor(&a->fs, &b->fs);
65 ret = ret || cmp_descriptor(&a->gs, &b->gs);
66 ret = ret || cmp_descriptor(&a->tr, &b->tr);
67 ret = ret || cmp_descriptor(&a->ldtr, &b->ldtr);
68 ret = ret || a->kernel_gs_base != b->kernel_gs_base;
69 ret = ret || a->star != b->star;
70 ret = ret || a->lstar != b->lstar;
71 ret = ret || a->cstar != b->cstar;
72 ret = ret || a->sfmask != b->sfmask;
73 ret = ret || a->sysenter_cs != b->sysenter_cs;
74 ret = ret || a->sysenter_esp != b->sysenter_esp;
75 ret = ret || a->sysenter_eip != b->sysenter_eip;
76 return ret;
77 }
78
main(void)79 void main(void)
80 {
81 uint16_t ss;
82 uint64_t rsp;
83 struct kvm_svm_vcpu *vcpu;
84
85 kvm_init_svm();
86 vcpu = kvm_create_svm_vcpu(guest_vmload, 1);
87 kvm_vmcb_set_intercept(vcpu->vmcb, SVM_INTERCEPT_VMLOAD, 0);
88 vmsave_buf = kvm_alloc_vmcb();
89
90 /* Save allocated stack for later VM reinit */
91 ss = vcpu->vmcb->ss.selector >> 3;
92 rsp = vcpu->vmcb->rsp;
93
94 /* Load partial state from vmsave_buf and save it to vcpu->vmcb */
95 kvm_svm_vmrun(vcpu);
96
97 if (vcpu->vmcb->exitcode != SVM_EXIT_HLT)
98 tst_brk(TBROK, "Nested VM exited unexpectedly");
99
100 if (cmp_vmcb(vcpu->vmcb, vmsave_buf)) {
101 tst_res(TFAIL, "Nested VM can read host memory");
102 return;
103 }
104
105 /* Load state from vcpu->vmcb and save it to vmsave_buf */
106 memset(vmsave_buf, 0xaa, sizeof(struct kvm_vmcb));
107 kvm_init_guest_vmcb(vcpu->vmcb, 1, ss, (void *)rsp, guest_vmsave);
108 kvm_vmcb_set_intercept(vcpu->vmcb, SVM_INTERCEPT_VMSAVE, 0);
109 kvm_svm_vmrun(vcpu);
110
111 if (vcpu->vmcb->exitcode != SVM_EXIT_HLT)
112 tst_brk(TBROK, "Nested VM exited unexpectedly");
113
114 if (cmp_vmcb(vcpu->vmcb, vmsave_buf)) {
115 tst_res(TFAIL, "Nested VM can overwrite host memory");
116 return;
117 }
118
119 tst_res(TPASS, "VMLOAD and VMSAVE were intercepted by kernel");
120 }
121
122 #else /* defined(__i386__) || defined(__x86_64__) */
123 TST_TEST_TCONF("Test supported only on x86");
124 #endif /* defined(__i386__) || defined(__x86_64__) */
125
126 #else /* COMPILE_PAYLOAD */
127
128 static struct tst_test test = {
129 .test_all = tst_kvm_run,
130 .setup = tst_kvm_setup,
131 .cleanup = tst_kvm_cleanup,
132 .supported_archs = (const char *const []) {
133 "x86_64",
134 "x86",
135 NULL
136 },
137 .tags = (struct tst_tag[]){
138 {"linux-git", "c7dfa4009965"},
139 {"CVE", "2021-3656"},
140 {}
141 }
142 };
143
144 #endif /* COMPILE_PAYLOAD */
145