cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

kvm_page_table_test.c (14007B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * KVM page table test
      4 *
      5 * Copyright (C) 2021, Huawei, Inc.
      6 *
      7 * Make sure that THP has been enabled or enough HUGETLB pages with specific
      8 * page size have been pre-allocated on your system, if you are planning to
      9 * use hugepages to back the guest memory for testing.
     10 */
     11
     12#define _GNU_SOURCE /* for program_invocation_name */
     13
     14#include <stdio.h>
     15#include <stdlib.h>
     16#include <time.h>
     17#include <pthread.h>
     18#include <semaphore.h>
     19
     20#include "test_util.h"
     21#include "kvm_util.h"
     22#include "processor.h"
     23#include "guest_modes.h"
     24
     25#define TEST_MEM_SLOT_INDEX             1
     26
     27/* Default size(1GB) of the memory for testing */
     28#define DEFAULT_TEST_MEM_SIZE		(1 << 30)
     29
     30/* Default guest test virtual memory offset */
     31#define DEFAULT_GUEST_TEST_MEM		0xc0000000
     32
     33/* Different guest memory accessing stages */
     34enum test_stage {
     35	KVM_BEFORE_MAPPINGS,
     36	KVM_CREATE_MAPPINGS,
     37	KVM_UPDATE_MAPPINGS,
     38	KVM_ADJUST_MAPPINGS,
     39	NUM_TEST_STAGES,
     40};
     41
     42static const char * const test_stage_string[] = {
     43	"KVM_BEFORE_MAPPINGS",
     44	"KVM_CREATE_MAPPINGS",
     45	"KVM_UPDATE_MAPPINGS",
     46	"KVM_ADJUST_MAPPINGS",
     47};
     48
     49struct vcpu_args {
     50	int vcpu_id;
     51	bool vcpu_write;
     52};
     53
     54struct test_args {
     55	struct kvm_vm *vm;
     56	uint64_t guest_test_virt_mem;
     57	uint64_t host_page_size;
     58	uint64_t host_num_pages;
     59	uint64_t large_page_size;
     60	uint64_t large_num_pages;
     61	uint64_t host_pages_per_lpage;
     62	enum vm_mem_backing_src_type src_type;
     63	struct vcpu_args vcpu_args[KVM_MAX_VCPUS];
     64};
     65
     66/*
     67 * Guest variables. Use addr_gva2hva() if these variables need
     68 * to be changed in host.
     69 */
     70static enum test_stage guest_test_stage;
     71
     72/* Host variables */
     73static uint32_t nr_vcpus = 1;
     74static struct test_args test_args;
     75static enum test_stage *current_stage;
     76static bool host_quit;
     77
     78/* Whether the test stage is updated, or completed */
     79static sem_t test_stage_updated;
     80static sem_t test_stage_completed;
     81
     82/*
     83 * Guest physical memory offset of the testing memory slot.
     84 * This will be set to the topmost valid physical address minus
     85 * the test memory size.
     86 */
     87static uint64_t guest_test_phys_mem;
     88
     89/*
     90 * Guest virtual memory offset of the testing memory slot.
     91 * Must not conflict with identity mapped test code.
     92 */
     93static uint64_t guest_test_virt_mem = DEFAULT_GUEST_TEST_MEM;
     94
     95static void guest_code(int vcpu_id)
     96{
     97	struct test_args *p = &test_args;
     98	struct vcpu_args *vcpu_args = &p->vcpu_args[vcpu_id];
     99	enum test_stage *current_stage = &guest_test_stage;
    100	uint64_t addr;
    101	int i, j;
    102
    103	/* Make sure vCPU args data structure is not corrupt */
    104	GUEST_ASSERT(vcpu_args->vcpu_id == vcpu_id);
    105
    106	while (true) {
    107		addr = p->guest_test_virt_mem;
    108
    109		switch (READ_ONCE(*current_stage)) {
    110		/*
    111		 * All vCPU threads will be started in this stage,
    112		 * where guest code of each vCPU will do nothing.
    113		 */
    114		case KVM_BEFORE_MAPPINGS:
    115			break;
    116
    117		/*
    118		 * Before dirty logging, vCPUs concurrently access the first
    119		 * 8 bytes of each page (host page/large page) within the same
    120		 * memory region with different accessing types (read/write).
    121		 * Then KVM will create normal page mappings or huge block
    122		 * mappings for them.
    123		 */
    124		case KVM_CREATE_MAPPINGS:
    125			for (i = 0; i < p->large_num_pages; i++) {
    126				if (vcpu_args->vcpu_write)
    127					*(uint64_t *)addr = 0x0123456789ABCDEF;
    128				else
    129					READ_ONCE(*(uint64_t *)addr);
    130
    131				addr += p->large_page_size;
    132			}
    133			break;
    134
    135		/*
    136		 * During dirty logging, KVM will only update attributes of the
    137		 * normal page mappings from RO to RW if memory backing src type
    138		 * is anonymous. In other cases, KVM will split the huge block
    139		 * mappings into normal page mappings if memory backing src type
    140		 * is THP or HUGETLB.
    141		 */
    142		case KVM_UPDATE_MAPPINGS:
    143			if (p->src_type == VM_MEM_SRC_ANONYMOUS) {
    144				for (i = 0; i < p->host_num_pages; i++) {
    145					*(uint64_t *)addr = 0x0123456789ABCDEF;
    146					addr += p->host_page_size;
    147				}
    148				break;
    149			}
    150
    151			for (i = 0; i < p->large_num_pages; i++) {
    152				/*
    153				 * Write to the first host page in each large
    154				 * page region, and triger break of large pages.
    155				 */
    156				*(uint64_t *)addr = 0x0123456789ABCDEF;
    157
    158				/*
    159				 * Access the middle host pages in each large
    160				 * page region. Since dirty logging is enabled,
    161				 * this will create new mappings at the smallest
    162				 * granularity.
    163				 */
    164				addr += p->large_page_size / 2;
    165				for (j = 0; j < p->host_pages_per_lpage / 2; j++) {
    166					READ_ONCE(*(uint64_t *)addr);
    167					addr += p->host_page_size;
    168				}
    169			}
    170			break;
    171
    172		/*
    173		 * After dirty logging is stopped, vCPUs concurrently read
    174		 * from every single host page. Then KVM will coalesce the
    175		 * split page mappings back to block mappings. And a TLB
    176		 * conflict abort could occur here if TLB entries of the
    177		 * page mappings are not fully invalidated.
    178		 */
    179		case KVM_ADJUST_MAPPINGS:
    180			for (i = 0; i < p->host_num_pages; i++) {
    181				READ_ONCE(*(uint64_t *)addr);
    182				addr += p->host_page_size;
    183			}
    184			break;
    185
    186		default:
    187			GUEST_ASSERT(0);
    188		}
    189
    190		GUEST_SYNC(1);
    191	}
    192}
    193
    194static void *vcpu_worker(void *data)
    195{
    196	int ret;
    197	struct vcpu_args *vcpu_args = data;
    198	struct kvm_vm *vm = test_args.vm;
    199	int vcpu_id = vcpu_args->vcpu_id;
    200	struct kvm_run *run;
    201	struct timespec start;
    202	struct timespec ts_diff;
    203	enum test_stage stage;
    204
    205	vcpu_args_set(vm, vcpu_id, 1, vcpu_id);
    206	run = vcpu_state(vm, vcpu_id);
    207
    208	while (!READ_ONCE(host_quit)) {
    209		ret = sem_wait(&test_stage_updated);
    210		TEST_ASSERT(ret == 0, "Error in sem_wait");
    211
    212		if (READ_ONCE(host_quit))
    213			return NULL;
    214
    215		clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    216		ret = _vcpu_run(vm, vcpu_id);
    217		ts_diff = timespec_elapsed(start);
    218
    219		TEST_ASSERT(ret == 0, "vcpu_run failed: %d\n", ret);
    220		TEST_ASSERT(get_ucall(vm, vcpu_id, NULL) == UCALL_SYNC,
    221			    "Invalid guest sync status: exit_reason=%s\n",
    222			    exit_reason_str(run->exit_reason));
    223
    224		pr_debug("Got sync event from vCPU %d\n", vcpu_id);
    225		stage = READ_ONCE(*current_stage);
    226
    227		/*
    228		 * Here we can know the execution time of every
    229		 * single vcpu running in different test stages.
    230		 */
    231		pr_debug("vCPU %d has completed stage %s\n"
    232			 "execution time is: %ld.%.9lds\n\n",
    233			 vcpu_id, test_stage_string[stage],
    234			 ts_diff.tv_sec, ts_diff.tv_nsec);
    235
    236		ret = sem_post(&test_stage_completed);
    237		TEST_ASSERT(ret == 0, "Error in sem_post");
    238	}
    239
    240	return NULL;
    241}
    242
    243struct test_params {
    244	uint64_t phys_offset;
    245	uint64_t test_mem_size;
    246	enum vm_mem_backing_src_type src_type;
    247};
    248
    249static struct kvm_vm *pre_init_before_test(enum vm_guest_mode mode, void *arg)
    250{
    251	int ret;
    252	struct test_params *p = arg;
    253	struct vcpu_args *vcpu_args;
    254	enum vm_mem_backing_src_type src_type = p->src_type;
    255	uint64_t large_page_size = get_backing_src_pagesz(src_type);
    256	uint64_t guest_page_size = vm_guest_mode_params[mode].page_size;
    257	uint64_t host_page_size = getpagesize();
    258	uint64_t test_mem_size = p->test_mem_size;
    259	uint64_t guest_num_pages;
    260	uint64_t alignment;
    261	void *host_test_mem;
    262	struct kvm_vm *vm;
    263	int vcpu_id;
    264
    265	/* Align up the test memory size */
    266	alignment = max(large_page_size, guest_page_size);
    267	test_mem_size = (test_mem_size + alignment - 1) & ~(alignment - 1);
    268
    269	/* Create a VM with enough guest pages */
    270	guest_num_pages = test_mem_size / guest_page_size;
    271	vm = vm_create_with_vcpus(mode, nr_vcpus, DEFAULT_GUEST_PHY_PAGES,
    272				  guest_num_pages, 0, guest_code, NULL);
    273
    274	/* Align down GPA of the testing memslot */
    275	if (!p->phys_offset)
    276		guest_test_phys_mem = (vm_get_max_gfn(vm) - guest_num_pages) *
    277				       guest_page_size;
    278	else
    279		guest_test_phys_mem = p->phys_offset;
    280#ifdef __s390x__
    281	alignment = max(0x100000UL, alignment);
    282#endif
    283	guest_test_phys_mem = align_down(guest_test_phys_mem, alignment);
    284
    285	/* Set up the shared data structure test_args */
    286	test_args.vm = vm;
    287	test_args.guest_test_virt_mem = guest_test_virt_mem;
    288	test_args.host_page_size = host_page_size;
    289	test_args.host_num_pages = test_mem_size / host_page_size;
    290	test_args.large_page_size = large_page_size;
    291	test_args.large_num_pages = test_mem_size / large_page_size;
    292	test_args.host_pages_per_lpage = large_page_size / host_page_size;
    293	test_args.src_type = src_type;
    294
    295	for (vcpu_id = 0; vcpu_id < KVM_MAX_VCPUS; vcpu_id++) {
    296		vcpu_args = &test_args.vcpu_args[vcpu_id];
    297		vcpu_args->vcpu_id = vcpu_id;
    298		vcpu_args->vcpu_write = !(vcpu_id % 2);
    299	}
    300
    301	/* Add an extra memory slot with specified backing src type */
    302	vm_userspace_mem_region_add(vm, src_type, guest_test_phys_mem,
    303				    TEST_MEM_SLOT_INDEX, guest_num_pages, 0);
    304
    305	/* Do mapping(GVA->GPA) for the testing memory slot */
    306	virt_map(vm, guest_test_virt_mem, guest_test_phys_mem, guest_num_pages);
    307
    308	/* Cache the HVA pointer of the region */
    309	host_test_mem = addr_gpa2hva(vm, (vm_paddr_t)guest_test_phys_mem);
    310
    311	/* Export shared structure test_args to guest */
    312	ucall_init(vm, NULL);
    313	sync_global_to_guest(vm, test_args);
    314
    315	ret = sem_init(&test_stage_updated, 0, 0);
    316	TEST_ASSERT(ret == 0, "Error in sem_init");
    317
    318	ret = sem_init(&test_stage_completed, 0, 0);
    319	TEST_ASSERT(ret == 0, "Error in sem_init");
    320
    321	current_stage = addr_gva2hva(vm, (vm_vaddr_t)(&guest_test_stage));
    322	*current_stage = NUM_TEST_STAGES;
    323
    324	pr_info("Testing guest mode: %s\n", vm_guest_mode_string(mode));
    325	pr_info("Testing memory backing src type: %s\n",
    326		vm_mem_backing_src_alias(src_type)->name);
    327	pr_info("Testing memory backing src granularity: 0x%lx\n",
    328		large_page_size);
    329	pr_info("Testing memory size(aligned): 0x%lx\n", test_mem_size);
    330	pr_info("Guest physical test memory offset: 0x%lx\n",
    331		guest_test_phys_mem);
    332	pr_info("Host  virtual  test memory offset: 0x%lx\n",
    333		(uint64_t)host_test_mem);
    334	pr_info("Number of testing vCPUs: %d\n", nr_vcpus);
    335
    336	return vm;
    337}
    338
    339static void vcpus_complete_new_stage(enum test_stage stage)
    340{
    341	int ret;
    342	int vcpus;
    343
    344	/* Wake up all the vcpus to run new test stage */
    345	for (vcpus = 0; vcpus < nr_vcpus; vcpus++) {
    346		ret = sem_post(&test_stage_updated);
    347		TEST_ASSERT(ret == 0, "Error in sem_post");
    348	}
    349	pr_debug("All vcpus have been notified to continue\n");
    350
    351	/* Wait for all the vcpus to complete new test stage */
    352	for (vcpus = 0; vcpus < nr_vcpus; vcpus++) {
    353		ret = sem_wait(&test_stage_completed);
    354		TEST_ASSERT(ret == 0, "Error in sem_wait");
    355
    356		pr_debug("%d vcpus have completed stage %s\n",
    357			 vcpus + 1, test_stage_string[stage]);
    358	}
    359
    360	pr_debug("All vcpus have completed stage %s\n",
    361		 test_stage_string[stage]);
    362}
    363
    364static void run_test(enum vm_guest_mode mode, void *arg)
    365{
    366	int ret;
    367	pthread_t *vcpu_threads;
    368	struct kvm_vm *vm;
    369	int vcpu_id;
    370	struct timespec start;
    371	struct timespec ts_diff;
    372
    373	/* Create VM with vCPUs and make some pre-initialization */
    374	vm = pre_init_before_test(mode, arg);
    375
    376	vcpu_threads = malloc(nr_vcpus * sizeof(*vcpu_threads));
    377	TEST_ASSERT(vcpu_threads, "Memory allocation failed");
    378
    379	host_quit = false;
    380	*current_stage = KVM_BEFORE_MAPPINGS;
    381
    382	for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++) {
    383		pthread_create(&vcpu_threads[vcpu_id], NULL, vcpu_worker,
    384			       &test_args.vcpu_args[vcpu_id]);
    385	}
    386
    387	vcpus_complete_new_stage(*current_stage);
    388	pr_info("Started all vCPUs successfully\n");
    389
    390	/* Test the stage of KVM creating mappings */
    391	*current_stage = KVM_CREATE_MAPPINGS;
    392
    393	clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    394	vcpus_complete_new_stage(*current_stage);
    395	ts_diff = timespec_elapsed(start);
    396
    397	pr_info("KVM_CREATE_MAPPINGS: total execution time: %ld.%.9lds\n\n",
    398		ts_diff.tv_sec, ts_diff.tv_nsec);
    399
    400	/* Test the stage of KVM updating mappings */
    401	vm_mem_region_set_flags(vm, TEST_MEM_SLOT_INDEX,
    402				KVM_MEM_LOG_DIRTY_PAGES);
    403
    404	*current_stage = KVM_UPDATE_MAPPINGS;
    405
    406	clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    407	vcpus_complete_new_stage(*current_stage);
    408	ts_diff = timespec_elapsed(start);
    409
    410	pr_info("KVM_UPDATE_MAPPINGS: total execution time: %ld.%.9lds\n\n",
    411		ts_diff.tv_sec, ts_diff.tv_nsec);
    412
    413	/* Test the stage of KVM adjusting mappings */
    414	vm_mem_region_set_flags(vm, TEST_MEM_SLOT_INDEX, 0);
    415
    416	*current_stage = KVM_ADJUST_MAPPINGS;
    417
    418	clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    419	vcpus_complete_new_stage(*current_stage);
    420	ts_diff = timespec_elapsed(start);
    421
    422	pr_info("KVM_ADJUST_MAPPINGS: total execution time: %ld.%.9lds\n\n",
    423		ts_diff.tv_sec, ts_diff.tv_nsec);
    424
    425	/* Tell the vcpu thread to quit */
    426	host_quit = true;
    427	for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++) {
    428		ret = sem_post(&test_stage_updated);
    429		TEST_ASSERT(ret == 0, "Error in sem_post");
    430	}
    431
    432	for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++)
    433		pthread_join(vcpu_threads[vcpu_id], NULL);
    434
    435	ret = sem_destroy(&test_stage_updated);
    436	TEST_ASSERT(ret == 0, "Error in sem_destroy");
    437
    438	ret = sem_destroy(&test_stage_completed);
    439	TEST_ASSERT(ret == 0, "Error in sem_destroy");
    440
    441	free(vcpu_threads);
    442	ucall_uninit(vm);
    443	kvm_vm_free(vm);
    444}
    445
    446static void help(char *name)
    447{
    448	puts("");
    449	printf("usage: %s [-h] [-p offset] [-m mode] "
    450	       "[-b mem-size] [-v vcpus] [-s mem-type]\n", name);
    451	puts("");
    452	printf(" -p: specify guest physical test memory offset\n"
    453	       "     Warning: a low offset can conflict with the loaded test code.\n");
    454	guest_modes_help();
    455	printf(" -b: specify size of the memory region for testing. e.g. 10M or 3G.\n"
    456	       "     (default: 1G)\n");
    457	printf(" -v: specify the number of vCPUs to run\n"
    458	       "     (default: 1)\n");
    459	backing_src_help("-s");
    460	puts("");
    461}
    462
    463int main(int argc, char *argv[])
    464{
    465	int max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
    466	struct test_params p = {
    467		.test_mem_size = DEFAULT_TEST_MEM_SIZE,
    468		.src_type = DEFAULT_VM_MEM_SRC,
    469	};
    470	int opt;
    471
    472	guest_modes_append_default();
    473
    474	while ((opt = getopt(argc, argv, "hp:m:b:v:s:")) != -1) {
    475		switch (opt) {
    476		case 'p':
    477			p.phys_offset = strtoull(optarg, NULL, 0);
    478			break;
    479		case 'm':
    480			guest_modes_cmdline(optarg);
    481			break;
    482		case 'b':
    483			p.test_mem_size = parse_size(optarg);
    484			break;
    485		case 'v':
    486			nr_vcpus = atoi(optarg);
    487			TEST_ASSERT(nr_vcpus > 0 && nr_vcpus <= max_vcpus,
    488				    "Invalid number of vcpus, must be between 1 and %d", max_vcpus);
    489			break;
    490		case 's':
    491			p.src_type = parse_backing_src_type(optarg);
    492			break;
    493		case 'h':
    494		default:
    495			help(argv[0]);
    496			exit(0);
    497		}
    498	}
    499
    500	for_each_guest_mode(run_test, &p);
    501
    502	return 0;
    503}