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