Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: kvm: test enforcement of paravirtual cpuid features

Add a set of tests that ensure the guest cannot access paravirtual msrs
and hypercalls that have been disabled in the KVM_CPUID_FEATURES leaf.
Expect a #GP in the case of msr accesses and -KVM_ENOSYS from
hypercalls.

Cc: Jim Mattson <jmattson@google.com>
Signed-off-by: Oliver Upton <oupton@google.com>
Reviewed-by: Peter Shier <pshier@google.com>
Reviewed-by: Aaron Lewis <aaronlewis@google.com>
Message-Id: <20201027231044.655110-7-oupton@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

authored by

Oliver Upton and committed by
Paolo Bonzini
ac4a4d6d 29faeb96

+308
+1
tools/testing/selftests/kvm/.gitignore
··· 5 5 /x86_64/cr4_cpuid_sync_test 6 6 /x86_64/debug_regs 7 7 /x86_64/evmcs_test 8 + /x86_64/kvm_pv_test 8 9 /x86_64/hyperv_cpuid 9 10 /x86_64/mmio_warning_test 10 11 /x86_64/platform_info_test
+1
tools/testing/selftests/kvm/Makefile
··· 41 41 TEST_GEN_PROGS_x86_64 = x86_64/cr4_cpuid_sync_test 42 42 TEST_GEN_PROGS_x86_64 += x86_64/evmcs_test 43 43 TEST_GEN_PROGS_x86_64 += x86_64/hyperv_cpuid 44 + TEST_GEN_PROGS_x86_64 += x86_64/kvm_pv_test 44 45 TEST_GEN_PROGS_x86_64 += x86_64/mmio_warning_test 45 46 TEST_GEN_PROGS_x86_64 += x86_64/platform_info_test 46 47 TEST_GEN_PROGS_x86_64 += x86_64/set_sregs_test
+3
tools/testing/selftests/kvm/include/kvm_util.h
··· 63 63 64 64 int kvm_check_cap(long cap); 65 65 int vm_enable_cap(struct kvm_vm *vm, struct kvm_enable_cap *cap); 66 + int vcpu_enable_cap(struct kvm_vm *vm, uint32_t vcpu_id, 67 + struct kvm_enable_cap *cap); 68 + void vm_enable_dirty_ring(struct kvm_vm *vm, uint32_t ring_size); 66 69 67 70 struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm); 68 71 struct kvm_vm *_vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm);
+12
tools/testing/selftests/kvm/include/x86_64/processor.h
··· 363 363 void (*handler)(struct ex_regs *)); 364 364 365 365 /* 366 + * set_cpuid() - overwrites a matching cpuid entry with the provided value. 367 + * matches based on ent->function && ent->index. returns true 368 + * if a match was found and successfully overwritten. 369 + * @cpuid: the kvm cpuid list to modify. 370 + * @ent: cpuid entry to insert 371 + */ 372 + bool set_cpuid(struct kvm_cpuid2 *cpuid, struct kvm_cpuid_entry2 *ent); 373 + 374 + uint64_t kvm_hypercall(uint64_t nr, uint64_t a0, uint64_t a1, uint64_t a2, 375 + uint64_t a3); 376 + 377 + /* 366 378 * Basic CPU control in CR0 367 379 */ 368 380 #define X86_CR0_PE (1UL<<0) /* Protection Enable */
+28
tools/testing/selftests/kvm/lib/kvm_util.c
··· 86 86 return ret; 87 87 } 88 88 89 + /* VCPU Enable Capability 90 + * 91 + * Input Args: 92 + * vm - Virtual Machine 93 + * vcpu_id - VCPU 94 + * cap - Capability 95 + * 96 + * Output Args: None 97 + * 98 + * Return: On success, 0. On failure a TEST_ASSERT failure is produced. 99 + * 100 + * Enables a capability (KVM_CAP_*) on the VCPU. 101 + */ 102 + int vcpu_enable_cap(struct kvm_vm *vm, uint32_t vcpu_id, 103 + struct kvm_enable_cap *cap) 104 + { 105 + struct vcpu *vcpu = vcpu_find(vm, vcpu_id); 106 + int r; 107 + 108 + TEST_ASSERT(vcpu, "cannot find vcpu %d", vcpu_id); 109 + 110 + r = ioctl(vcpu->fd, KVM_ENABLE_CAP, cap); 111 + TEST_ASSERT(!r, "KVM_ENABLE_CAP vCPU ioctl failed,\n" 112 + " rc: %i, errno: %i", r, errno); 113 + 114 + return r; 115 + } 116 + 89 117 static void vm_open(struct kvm_vm *vm, int perm) 90 118 { 91 119 vm->kvm_fd = open(KVM_DEV_PATH, perm);
+29
tools/testing/selftests/kvm/lib/x86_64/processor.c
··· 1227 1227 *data); 1228 1228 } 1229 1229 } 1230 + 1231 + bool set_cpuid(struct kvm_cpuid2 *cpuid, 1232 + struct kvm_cpuid_entry2 *ent) 1233 + { 1234 + int i; 1235 + 1236 + for (i = 0; i < cpuid->nent; i++) { 1237 + struct kvm_cpuid_entry2 *cur = &cpuid->entries[i]; 1238 + 1239 + if (cur->function != ent->function || cur->index != ent->index) 1240 + continue; 1241 + 1242 + memcpy(cur, ent, sizeof(struct kvm_cpuid_entry2)); 1243 + return true; 1244 + } 1245 + 1246 + return false; 1247 + } 1248 + 1249 + uint64_t kvm_hypercall(uint64_t nr, uint64_t a0, uint64_t a1, uint64_t a2, 1250 + uint64_t a3) 1251 + { 1252 + uint64_t r; 1253 + 1254 + asm volatile("vmcall" 1255 + : "=a"(r) 1256 + : "b"(a0), "c"(a1), "d"(a2), "S"(a3)); 1257 + return r; 1258 + }
+234
tools/testing/selftests/kvm/x86_64/kvm_pv_test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (C) 2020, Google LLC. 4 + * 5 + * Tests for KVM paravirtual feature disablement 6 + */ 7 + #include <asm/kvm_para.h> 8 + #include <linux/kvm_para.h> 9 + #include <stdint.h> 10 + 11 + #include "test_util.h" 12 + #include "kvm_util.h" 13 + #include "processor.h" 14 + 15 + extern unsigned char rdmsr_start; 16 + extern unsigned char rdmsr_end; 17 + 18 + static u64 do_rdmsr(u32 idx) 19 + { 20 + u32 lo, hi; 21 + 22 + asm volatile("rdmsr_start: rdmsr;" 23 + "rdmsr_end:" 24 + : "=a"(lo), "=c"(hi) 25 + : "c"(idx)); 26 + 27 + return (((u64) hi) << 32) | lo; 28 + } 29 + 30 + extern unsigned char wrmsr_start; 31 + extern unsigned char wrmsr_end; 32 + 33 + static void do_wrmsr(u32 idx, u64 val) 34 + { 35 + u32 lo, hi; 36 + 37 + lo = val; 38 + hi = val >> 32; 39 + 40 + asm volatile("wrmsr_start: wrmsr;" 41 + "wrmsr_end:" 42 + : : "a"(lo), "c"(idx), "d"(hi)); 43 + } 44 + 45 + static int nr_gp; 46 + 47 + static void guest_gp_handler(struct ex_regs *regs) 48 + { 49 + unsigned char *rip = (unsigned char *)regs->rip; 50 + bool r, w; 51 + 52 + r = rip == &rdmsr_start; 53 + w = rip == &wrmsr_start; 54 + GUEST_ASSERT(r || w); 55 + 56 + nr_gp++; 57 + 58 + if (r) 59 + regs->rip = (uint64_t)&rdmsr_end; 60 + else 61 + regs->rip = (uint64_t)&wrmsr_end; 62 + } 63 + 64 + struct msr_data { 65 + uint32_t idx; 66 + const char *name; 67 + }; 68 + 69 + #define TEST_MSR(msr) { .idx = msr, .name = #msr } 70 + #define UCALL_PR_MSR 0xdeadbeef 71 + #define PR_MSR(msr) ucall(UCALL_PR_MSR, 1, msr) 72 + 73 + /* 74 + * KVM paravirtual msrs to test. Expect a #GP if any of these msrs are read or 75 + * written, as the KVM_CPUID_FEATURES leaf is cleared. 76 + */ 77 + static struct msr_data msrs_to_test[] = { 78 + TEST_MSR(MSR_KVM_SYSTEM_TIME), 79 + TEST_MSR(MSR_KVM_SYSTEM_TIME_NEW), 80 + TEST_MSR(MSR_KVM_WALL_CLOCK), 81 + TEST_MSR(MSR_KVM_WALL_CLOCK_NEW), 82 + TEST_MSR(MSR_KVM_ASYNC_PF_EN), 83 + TEST_MSR(MSR_KVM_STEAL_TIME), 84 + TEST_MSR(MSR_KVM_PV_EOI_EN), 85 + TEST_MSR(MSR_KVM_POLL_CONTROL), 86 + TEST_MSR(MSR_KVM_ASYNC_PF_INT), 87 + TEST_MSR(MSR_KVM_ASYNC_PF_ACK), 88 + }; 89 + 90 + static void test_msr(struct msr_data *msr) 91 + { 92 + PR_MSR(msr); 93 + do_rdmsr(msr->idx); 94 + GUEST_ASSERT(READ_ONCE(nr_gp) == 1); 95 + 96 + nr_gp = 0; 97 + do_wrmsr(msr->idx, 0); 98 + GUEST_ASSERT(READ_ONCE(nr_gp) == 1); 99 + nr_gp = 0; 100 + } 101 + 102 + struct hcall_data { 103 + uint64_t nr; 104 + const char *name; 105 + }; 106 + 107 + #define TEST_HCALL(hc) { .nr = hc, .name = #hc } 108 + #define UCALL_PR_HCALL 0xdeadc0de 109 + #define PR_HCALL(hc) ucall(UCALL_PR_HCALL, 1, hc) 110 + 111 + /* 112 + * KVM hypercalls to test. Expect -KVM_ENOSYS when called, as the corresponding 113 + * features have been cleared in KVM_CPUID_FEATURES. 114 + */ 115 + static struct hcall_data hcalls_to_test[] = { 116 + TEST_HCALL(KVM_HC_KICK_CPU), 117 + TEST_HCALL(KVM_HC_SEND_IPI), 118 + TEST_HCALL(KVM_HC_SCHED_YIELD), 119 + }; 120 + 121 + static void test_hcall(struct hcall_data *hc) 122 + { 123 + uint64_t r; 124 + 125 + PR_HCALL(hc); 126 + r = kvm_hypercall(hc->nr, 0, 0, 0, 0); 127 + GUEST_ASSERT(r == -KVM_ENOSYS); 128 + } 129 + 130 + static void guest_main(void) 131 + { 132 + int i; 133 + 134 + for (i = 0; i < ARRAY_SIZE(msrs_to_test); i++) { 135 + test_msr(&msrs_to_test[i]); 136 + } 137 + 138 + for (i = 0; i < ARRAY_SIZE(hcalls_to_test); i++) { 139 + test_hcall(&hcalls_to_test[i]); 140 + } 141 + 142 + GUEST_DONE(); 143 + } 144 + 145 + static void clear_kvm_cpuid_features(struct kvm_cpuid2 *cpuid) 146 + { 147 + struct kvm_cpuid_entry2 ent = {0}; 148 + 149 + ent.function = KVM_CPUID_FEATURES; 150 + TEST_ASSERT(set_cpuid(cpuid, &ent), 151 + "failed to clear KVM_CPUID_FEATURES leaf"); 152 + } 153 + 154 + static void pr_msr(struct ucall *uc) 155 + { 156 + struct msr_data *msr = (struct msr_data *)uc->args[0]; 157 + 158 + pr_info("testing msr: %s (%#x)\n", msr->name, msr->idx); 159 + } 160 + 161 + static void pr_hcall(struct ucall *uc) 162 + { 163 + struct hcall_data *hc = (struct hcall_data *)uc->args[0]; 164 + 165 + pr_info("testing hcall: %s (%lu)\n", hc->name, hc->nr); 166 + } 167 + 168 + static void handle_abort(struct ucall *uc) 169 + { 170 + TEST_FAIL("%s at %s:%ld", (const char *)uc->args[0], 171 + __FILE__, uc->args[1]); 172 + } 173 + 174 + #define VCPU_ID 0 175 + 176 + static void enter_guest(struct kvm_vm *vm) 177 + { 178 + struct kvm_run *run; 179 + struct ucall uc; 180 + int r; 181 + 182 + run = vcpu_state(vm, VCPU_ID); 183 + 184 + while (true) { 185 + r = _vcpu_run(vm, VCPU_ID); 186 + TEST_ASSERT(!r, "vcpu_run failed: %d\n", r); 187 + TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, 188 + "unexpected exit reason: %u (%s)", 189 + run->exit_reason, exit_reason_str(run->exit_reason)); 190 + 191 + switch (get_ucall(vm, VCPU_ID, &uc)) { 192 + case UCALL_PR_MSR: 193 + pr_msr(&uc); 194 + break; 195 + case UCALL_PR_HCALL: 196 + pr_hcall(&uc); 197 + break; 198 + case UCALL_ABORT: 199 + handle_abort(&uc); 200 + return; 201 + case UCALL_DONE: 202 + return; 203 + } 204 + } 205 + } 206 + 207 + int main(void) 208 + { 209 + struct kvm_enable_cap cap = {0}; 210 + struct kvm_cpuid2 *best; 211 + struct kvm_vm *vm; 212 + 213 + if (!kvm_check_cap(KVM_CAP_ENFORCE_PV_FEATURE_CPUID)) { 214 + pr_info("will skip kvm paravirt restriction tests.\n"); 215 + return 0; 216 + } 217 + 218 + vm = vm_create_default(VCPU_ID, 0, guest_main); 219 + 220 + cap.cap = KVM_CAP_ENFORCE_PV_FEATURE_CPUID; 221 + cap.args[0] = 1; 222 + vcpu_enable_cap(vm, VCPU_ID, &cap); 223 + 224 + best = kvm_get_supported_cpuid(); 225 + clear_kvm_cpuid_features(best); 226 + vcpu_set_cpuid(vm, VCPU_ID, best); 227 + 228 + vm_init_descriptor_tables(vm); 229 + vcpu_init_descriptor_tables(vm, VCPU_ID); 230 + vm_handle_exception(vm, GP_VECTOR, guest_gp_handler); 231 + 232 + enter_guest(vm); 233 + kvm_vm_free(vm); 234 + }