1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2022, Google LLC. 4 */ 5 #include <fcntl.h> 6 #include <limits.h> 7 #include <pthread.h> 8 #include <sched.h> 9 #include <signal.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <sys/ioctl.h> 14 15 #include <linux/compiler.h> 16 #include <linux/kernel.h> 17 #include <linux/kvm_para.h> 18 #include <linux/memfd.h> 19 #include <linux/sizes.h> 20 21 #include <test_util.h> 22 #include <kvm_util.h> 23 #include <processor.h> 24 25 #define BASE_DATA_SLOT 10 26 #define BASE_DATA_GPA ((u64)(1ull << 32)) 27 #define PER_CPU_DATA_SIZE ((u64)(SZ_2M + PAGE_SIZE)) 28 29 /* Horrific macro so that the line info is captured accurately :-( */ 30 #define memcmp_g(gpa, pattern, size) \ 31 do { \ 32 u8 *mem = (u8 *)gpa; \ 33 size_t i; \ 34 \ 35 for (i = 0; i < size; i++) \ 36 __GUEST_ASSERT(mem[i] == pattern, \ 37 "Guest expected 0x%x at offset %lu (gpa 0x%lx), got 0x%x", \ 38 pattern, i, gpa + i, mem[i]); \ 39 } while (0) 40 41 static void memcmp_h(u8 *mem, gpa_t gpa, u8 pattern, size_t size) 42 { 43 size_t i; 44 45 for (i = 0; i < size; i++) 46 TEST_ASSERT(mem[i] == pattern, 47 "Host expected 0x%x at gpa 0x%lx, got 0x%x", 48 pattern, gpa + i, mem[i]); 49 } 50 51 /* 52 * Run memory conversion tests with explicit conversion: 53 * Execute KVM hypercall to map/unmap gpa range which will cause userspace exit 54 * to back/unback private memory. Subsequent accesses by guest to the gpa range 55 * will not cause exit to userspace. 56 * 57 * Test memory conversion scenarios with following steps: 58 * 1) Access private memory using private access and verify that memory contents 59 * are not visible to userspace. 60 * 2) Convert memory to shared using explicit conversions and ensure that 61 * userspace is able to access the shared regions. 62 * 3) Convert memory back to private using explicit conversions and ensure that 63 * userspace is again not able to access converted private regions. 64 */ 65 66 #define GUEST_STAGE(o, s) { .offset = o, .size = s } 67 68 enum ucall_syncs { 69 SYNC_SHARED, 70 SYNC_PRIVATE, 71 }; 72 73 static void guest_sync_shared(gpa_t gpa, u64 size, 74 u8 current_pattern, u8 new_pattern) 75 { 76 GUEST_SYNC5(SYNC_SHARED, gpa, size, current_pattern, new_pattern); 77 } 78 79 static void guest_sync_private(gpa_t gpa, u64 size, u8 pattern) 80 { 81 GUEST_SYNC4(SYNC_PRIVATE, gpa, size, pattern); 82 } 83 84 /* Arbitrary values, KVM doesn't care about the attribute flags. */ 85 #define MAP_GPA_SET_ATTRIBUTES BIT(0) 86 #define MAP_GPA_SHARED BIT(1) 87 #define MAP_GPA_DO_FALLOCATE BIT(2) 88 89 static void guest_map_mem(gpa_t gpa, u64 size, bool map_shared, 90 bool do_fallocate) 91 { 92 u64 flags = MAP_GPA_SET_ATTRIBUTES; 93 94 if (map_shared) 95 flags |= MAP_GPA_SHARED; 96 if (do_fallocate) 97 flags |= MAP_GPA_DO_FALLOCATE; 98 kvm_hypercall_map_gpa_range(gpa, size, flags); 99 } 100 101 static void guest_map_shared(gpa_t gpa, u64 size, bool do_fallocate) 102 { 103 guest_map_mem(gpa, size, true, do_fallocate); 104 } 105 106 static void guest_map_private(gpa_t gpa, u64 size, bool do_fallocate) 107 { 108 guest_map_mem(gpa, size, false, do_fallocate); 109 } 110 111 struct { 112 u64 offset; 113 u64 size; 114 } static const test_ranges[] = { 115 GUEST_STAGE(0, PAGE_SIZE), 116 GUEST_STAGE(0, SZ_2M), 117 GUEST_STAGE(PAGE_SIZE, PAGE_SIZE), 118 GUEST_STAGE(PAGE_SIZE, SZ_2M), 119 GUEST_STAGE(SZ_2M, PAGE_SIZE), 120 }; 121 122 static void guest_test_explicit_conversion(u64 base_gpa, bool do_fallocate) 123 { 124 const u8 def_p = 0xaa; 125 const u8 init_p = 0xcc; 126 u64 j; 127 int i; 128 129 /* Memory should be shared by default. */ 130 memset((void *)base_gpa, def_p, PER_CPU_DATA_SIZE); 131 memcmp_g(base_gpa, def_p, PER_CPU_DATA_SIZE); 132 guest_sync_shared(base_gpa, PER_CPU_DATA_SIZE, def_p, init_p); 133 134 memcmp_g(base_gpa, init_p, PER_CPU_DATA_SIZE); 135 136 for (i = 0; i < ARRAY_SIZE(test_ranges); i++) { 137 gpa_t gpa = base_gpa + test_ranges[i].offset; 138 u64 size = test_ranges[i].size; 139 u8 p1 = 0x11; 140 u8 p2 = 0x22; 141 u8 p3 = 0x33; 142 u8 p4 = 0x44; 143 144 /* 145 * Set the test region to pattern one to differentiate it from 146 * the data range as a whole (contains the initial pattern). 147 */ 148 memset((void *)gpa, p1, size); 149 150 /* 151 * Convert to private, set and verify the private data, and 152 * then verify that the rest of the data (map shared) still 153 * holds the initial pattern, and that the host always sees the 154 * shared memory (initial pattern). Unlike shared memory, 155 * punching a hole in private memory is destructive, i.e. 156 * previous values aren't guaranteed to be preserved. 157 */ 158 guest_map_private(gpa, size, do_fallocate); 159 160 if (size > PAGE_SIZE) { 161 memset((void *)gpa, p2, PAGE_SIZE); 162 goto skip; 163 } 164 165 memset((void *)gpa, p2, size); 166 guest_sync_private(gpa, size, p1); 167 168 /* 169 * Verify that the private memory was set to pattern two, and 170 * that shared memory still holds the initial pattern. 171 */ 172 memcmp_g(gpa, p2, size); 173 if (gpa > base_gpa) 174 memcmp_g(base_gpa, init_p, gpa - base_gpa); 175 if (gpa + size < base_gpa + PER_CPU_DATA_SIZE) 176 memcmp_g(gpa + size, init_p, 177 (base_gpa + PER_CPU_DATA_SIZE) - (gpa + size)); 178 179 /* 180 * Convert odd-number page frames back to shared to verify KVM 181 * also correctly handles holes in private ranges. 182 */ 183 for (j = 0; j < size; j += PAGE_SIZE) { 184 if ((j >> PAGE_SHIFT) & 1) { 185 guest_map_shared(gpa + j, PAGE_SIZE, do_fallocate); 186 guest_sync_shared(gpa + j, PAGE_SIZE, p1, p3); 187 188 memcmp_g(gpa + j, p3, PAGE_SIZE); 189 } else { 190 guest_sync_private(gpa + j, PAGE_SIZE, p1); 191 } 192 } 193 194 skip: 195 /* 196 * Convert the entire region back to shared, explicitly write 197 * pattern three to fill in the even-number frames before 198 * asking the host to verify (and write pattern four). 199 */ 200 guest_map_shared(gpa, size, do_fallocate); 201 memset((void *)gpa, p3, size); 202 guest_sync_shared(gpa, size, p3, p4); 203 memcmp_g(gpa, p4, size); 204 205 /* 206 * Free (via PUNCH_HOLE) *all* private memory so that the next 207 * iteration starts from a clean slate, e.g. with respect to 208 * whether or not there are pages/folios in guest_mem. 209 */ 210 guest_map_shared(base_gpa, PER_CPU_DATA_SIZE, true); 211 212 /* 213 * Hole-punching above zeroed private memory. Reset shared 214 * memory in preparation for the next GUEST_STAGE. 215 */ 216 memset((void *)base_gpa, init_p, PER_CPU_DATA_SIZE); 217 } 218 } 219 220 static void guest_punch_hole(gpa_t gpa, u64 size) 221 { 222 /* "Mapping" memory shared via fallocate() is done via PUNCH_HOLE. */ 223 u64 flags = MAP_GPA_SHARED | MAP_GPA_DO_FALLOCATE; 224 225 kvm_hypercall_map_gpa_range(gpa, size, flags); 226 } 227 228 /* 229 * Test that PUNCH_HOLE actually frees memory by punching holes without doing a 230 * proper conversion. Freeing (PUNCH_HOLE) should zap SPTEs, and reallocating 231 * (subsequent fault) should zero memory. 232 */ 233 static void guest_test_punch_hole(u64 base_gpa, bool precise) 234 { 235 const u8 init_p = 0xcc; 236 int i; 237 238 /* 239 * Convert the entire range to private, this testcase is all about 240 * punching holes in guest_memfd, i.e. shared mappings aren't needed. 241 */ 242 guest_map_private(base_gpa, PER_CPU_DATA_SIZE, false); 243 244 for (i = 0; i < ARRAY_SIZE(test_ranges); i++) { 245 gpa_t gpa = base_gpa + test_ranges[i].offset; 246 u64 size = test_ranges[i].size; 247 248 /* 249 * Free all memory before each iteration, even for the !precise 250 * case where the memory will be faulted back in. Freeing and 251 * reallocating should obviously work, and freeing all memory 252 * minimizes the probability of cross-testcase influence. 253 */ 254 guest_punch_hole(base_gpa, PER_CPU_DATA_SIZE); 255 256 /* Fault-in and initialize memory, and verify the pattern. */ 257 if (precise) { 258 memset((void *)gpa, init_p, size); 259 memcmp_g(gpa, init_p, size); 260 } else { 261 memset((void *)base_gpa, init_p, PER_CPU_DATA_SIZE); 262 memcmp_g(base_gpa, init_p, PER_CPU_DATA_SIZE); 263 } 264 265 /* 266 * Punch a hole at the target range and verify that reads from 267 * the guest succeed and return zeroes. 268 */ 269 guest_punch_hole(gpa, size); 270 memcmp_g(gpa, 0, size); 271 } 272 } 273 274 static void guest_code(u64 base_gpa) 275 { 276 /* 277 * Run the conversion test twice, with and without doing fallocate() on 278 * the guest_memfd backing when converting between shared and private. 279 */ 280 guest_test_explicit_conversion(base_gpa, false); 281 guest_test_explicit_conversion(base_gpa, true); 282 283 /* 284 * Run the PUNCH_HOLE test twice too, once with the entire guest_memfd 285 * faulted in, once with only the target range faulted in. 286 */ 287 guest_test_punch_hole(base_gpa, false); 288 guest_test_punch_hole(base_gpa, true); 289 GUEST_DONE(); 290 } 291 292 static void handle_exit_hypercall(struct kvm_vcpu *vcpu) 293 { 294 struct kvm_run *run = vcpu->run; 295 gpa_t gpa = run->hypercall.args[0]; 296 u64 size = run->hypercall.args[1] * PAGE_SIZE; 297 bool set_attributes = run->hypercall.args[2] & MAP_GPA_SET_ATTRIBUTES; 298 bool map_shared = run->hypercall.args[2] & MAP_GPA_SHARED; 299 bool do_fallocate = run->hypercall.args[2] & MAP_GPA_DO_FALLOCATE; 300 struct kvm_vm *vm = vcpu->vm; 301 302 TEST_ASSERT(run->hypercall.nr == KVM_HC_MAP_GPA_RANGE, 303 "Wanted MAP_GPA_RANGE (%u), got '%llu'", 304 KVM_HC_MAP_GPA_RANGE, run->hypercall.nr); 305 306 if (do_fallocate) 307 vm_guest_mem_fallocate(vm, gpa, size, map_shared); 308 309 if (set_attributes) { 310 u64 attrs = map_shared ? 0 : KVM_MEMORY_ATTRIBUTE_PRIVATE; 311 312 vm_mem_set_memory_attributes(vm, gpa, size, attrs); 313 } 314 315 run->hypercall.ret = 0; 316 } 317 318 static bool run_vcpus; 319 320 static void *__test_mem_conversions(void *__vcpu) 321 { 322 struct kvm_vcpu *vcpu = __vcpu; 323 struct kvm_run *run = vcpu->run; 324 struct kvm_vm *vm = vcpu->vm; 325 struct ucall uc; 326 327 while (!READ_ONCE(run_vcpus)) 328 ; 329 330 for ( ;; ) { 331 vcpu_run(vcpu); 332 333 if (run->exit_reason == KVM_EXIT_HYPERCALL) { 334 handle_exit_hypercall(vcpu); 335 continue; 336 } 337 338 TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, 339 "Wanted KVM_EXIT_IO, got exit reason: %u (%s)", 340 run->exit_reason, exit_reason_str(run->exit_reason)); 341 342 switch (get_ucall(vcpu, &uc)) { 343 case UCALL_ABORT: 344 REPORT_GUEST_ASSERT(uc); 345 case UCALL_SYNC: { 346 gpa_t gpa = uc.args[1]; 347 size_t size = uc.args[2]; 348 size_t i; 349 350 TEST_ASSERT(uc.args[0] == SYNC_SHARED || 351 uc.args[0] == SYNC_PRIVATE, 352 "Unknown sync command '%ld'", uc.args[0]); 353 354 for (i = 0; i < size; i += vm->page_size) { 355 size_t nr_bytes = min_t(size_t, vm->page_size, size - i); 356 u8 *hva = addr_gpa2hva(vm, gpa + i); 357 358 if (kvm_has_gmem_attributes && 359 uc.args[0] == SYNC_PRIVATE) { 360 TEST_EXPECT_SIGBUS(READ_ONCE(*hva)); 361 } else { 362 /* 363 * If not testing in-place conversion 364 * (dual backing), the host should 365 * always observe shared data, since the 366 * shared memory is a separate page. 367 * 368 * For SYNC_SHARED, test that the host 369 * can see shared memory. 370 */ 371 memcmp_h(hva, gpa + i, uc.args[3], nr_bytes); 372 } 373 374 /* For shared, write the new pattern to guest memory. */ 375 if (uc.args[0] == SYNC_SHARED) 376 memset(hva, uc.args[4], nr_bytes); 377 } 378 break; 379 } 380 case UCALL_DONE: 381 return NULL; 382 default: 383 TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd); 384 } 385 } 386 } 387 388 /* Align each vCPU's chunk of memory naturally to the size of the backing store. */ 389 static size_t compute_per_cpu_size(enum vm_mem_backing_src_type src_type) 390 { 391 size_t alignment; 392 393 if (kvm_has_gmem_attributes) 394 alignment = getpagesize(); 395 else 396 alignment = get_backing_src_pagesz(src_type); 397 398 return align_up(PER_CPU_DATA_SIZE, max_t(size_t, SZ_2M, alignment)); 399 } 400 401 static void test_mem_conversions(enum vm_mem_backing_src_type src_type, u32 nr_vcpus, 402 u32 nr_memslots) 403 { 404 const size_t per_cpu_size = compute_per_cpu_size(src_type); 405 const size_t memfd_size = per_cpu_size * nr_vcpus; 406 const size_t slot_size = memfd_size / nr_memslots; 407 struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; 408 pthread_t threads[KVM_MAX_VCPUS]; 409 struct kvm_vm *vm; 410 u64 gmem_flags; 411 int memfd, i; 412 413 const struct vm_shape shape = { 414 .mode = VM_MODE_DEFAULT, 415 .type = KVM_X86_SW_PROTECTED_VM, 416 }; 417 418 TEST_ASSERT(slot_size * nr_memslots == memfd_size, 419 "The memfd size (0x%lx) needs to be cleanly divisible by the number of memslots (%u)", 420 memfd_size, nr_memslots); 421 vm = __vm_create_with_vcpus(shape, nr_vcpus, 0, guest_code, vcpus); 422 423 vm_enable_cap(vm, KVM_CAP_EXIT_HYPERCALL, (1 << KVM_HC_MAP_GPA_RANGE)); 424 425 gmem_flags = 0; 426 if (kvm_has_gmem_attributes) 427 gmem_flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_INIT_SHARED; 428 429 memfd = vm_create_guest_memfd(vm, memfd_size, gmem_flags); 430 431 for (i = 0; i < nr_memslots; i++) 432 vm_mem_add(vm, src_type, BASE_DATA_GPA + slot_size * i, 433 BASE_DATA_SLOT + i, slot_size / vm->page_size, 434 KVM_MEM_GUEST_MEMFD, memfd, slot_size * i, gmem_flags); 435 436 for (i = 0; i < nr_vcpus; i++) { 437 gpa_t gpa = BASE_DATA_GPA + i * per_cpu_size; 438 439 vcpu_args_set(vcpus[i], 1, gpa); 440 441 /* 442 * Map only what is needed so that an out-of-bounds access 443 * results #PF => SHUTDOWN instead of data corruption. 444 */ 445 virt_map(vm, gpa, gpa, PER_CPU_DATA_SIZE / vm->page_size); 446 447 kvm_pthread_create(&threads[i], NULL, __test_mem_conversions, vcpus[i]); 448 } 449 450 WRITE_ONCE(run_vcpus, true); 451 452 for (i = 0; i < nr_vcpus; i++) 453 kvm_pthread_join(threads[i], NULL); 454 455 kvm_vm_free(vm); 456 457 /* 458 * Allocate and free memory from the guest_memfd after closing the VM 459 * fd. The guest_memfd is gifted a reference to its owning VM, i.e. 460 * should prevent the VM from being fully destroyed until the last 461 * reference to the guest_memfd is also put. 462 */ 463 kvm_fallocate(memfd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, memfd_size); 464 kvm_fallocate(memfd, FALLOC_FL_KEEP_SIZE, 0, memfd_size); 465 466 close(memfd); 467 } 468 469 static void usage(const char *cmd) 470 { 471 puts(""); 472 printf("usage: %s [-h] [-m nr_memslots] [-s mem_type] [-n nr_vcpus]\n", cmd); 473 puts(""); 474 backing_src_help("-s"); 475 puts(""); 476 puts(" -n: specify the number of vcpus (default: 1)"); 477 puts(""); 478 puts(" -m: specify the number of memslots (default: 1)"); 479 puts(""); 480 } 481 482 int main(int argc, char *argv[]) 483 { 484 enum vm_mem_backing_src_type src_type = DEFAULT_VM_MEM_SRC; 485 u32 nr_memslots = 1; 486 u32 nr_vcpus = 1; 487 int opt; 488 489 TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM)); 490 491 while ((opt = getopt(argc, argv, "hm:s:n:")) != -1) { 492 switch (opt) { 493 case 's': 494 TEST_ASSERT(!kvm_has_gmem_attributes, 495 "src_type is only configurable when testing without in-place conversion"); 496 src_type = parse_backing_src_type(optarg); 497 break; 498 case 'n': 499 nr_vcpus = atoi_positive("nr_vcpus", optarg); 500 break; 501 case 'm': 502 nr_memslots = atoi_positive("nr_memslots", optarg); 503 break; 504 case 'h': 505 default: 506 usage(argv[0]); 507 exit(0); 508 } 509 } 510 511 test_mem_conversions(src_type, nr_vcpus, nr_memslots); 512 513 return 0; 514 } 515