#define _GNU_SOURCE #include "cachepc/uapi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) #define MIN(a,b) ((a) > (b) ? (b) : (a)) #define SAMPLE_COUNT 64 #define TARGET_CORE 2 #define SECONDARY_CORE 3 #define TARGET_SET 15 enum { WITH, WITHOUT }; struct kvm { int fd, vmfd, vcpufd; void *mem; size_t memsize; struct kvm_run *run; }; /* start and end for guest assembly */ extern uint8_t __start_guest_with[]; extern uint8_t __stop_guest_with[]; extern uint8_t __start_guest_without[]; extern uint8_t __stop_guest_without[]; static const char *vmtype; static int kvm_dev, sev_dev; enum { GSTATE_UNINIT, GSTATE_LUPDATE, GSTATE_LSECRET, GSTATE_RUNNING, GSTATE_SUPDATE, GSTATE_RUPDATE, GSTATE_SENT }; const char *sev_fwerr_strs[] = { "Success", "Platform state is invalid", "Guest state is invalid", "Platform configuration is invalid", "Buffer too small", "Platform is already owned", "Certificate is invalid", "Policy is not allowed", "Guest is not active", "Invalid address", "Bad signature", "Bad measurement", "Asid is already owned", "Invalid ASID", "WBINVD is required", "DF_FLUSH is required", "Guest handle is invalid", "Invalid command", "Guest is active", "Hardware error", "Hardware unsafe", "Feature not supported", "Invalid parameter", "Out of resources", "Integrity checks failed" }; const char *sev_gstate_strs[] = { "UNINIT", "LUPDATE", "LSECRET", "RUNNING", "SUPDATE", "RUPDATE", "SEND" }; void hexdump(void *data, int len) { int i; for (i = 0; i < len; i++) { if (i % 16 == 0 && i) printf("\n"); printf("%02X ", *(uint8_t *)(data + i)); } printf("\n"); } __attribute__((section("guest_with"))) void vm_guest_with(void) { while (1) { asm volatile("mov (%[v]), %%bl" : : [v] "r" (L1_LINESIZE * TARGET_SET)); CPC_DO_VMMCALL(EXIT, 0, 0); } } __attribute__((section("guest_without"))) void vm_guest_without(void) { while (1) { CPC_DO_VMMCALL(EXIT, 0, 0); } } bool pin_process(pid_t pid, int cpu, bool assert) { cpu_set_t cpuset; int ret; CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); ret = sched_setaffinity(pid, sizeof(cpu_set_t), &cpuset); if (ret < 0) { if (assert) err(1, "sched_setaffinity"); return false; } return true; } int read_stat_core(pid_t pid) { char path[256]; char line[2048]; FILE *file; char *p; int i, cpu; snprintf(path, sizeof(path), "/proc/%u/stat", pid); file = fopen(path, "r"); if (!file) return -1; if (!fgets(line, sizeof(line), file)) err(1, "read stat"); p = line; for (i = 0; i < 38 && (p = strchr(p, ' ')); i++) p += 1; if (!p) errx(1, "stat format"); cpu = atoi(p); fclose(file); return cpu; } const char * sev_fwerr_str(int code) { if (code < 0 || code >= ARRLEN(sev_fwerr_strs)) return "Unknown error"; return sev_fwerr_strs[code]; } const char * sev_gstate_str(int code) { if (code < 0 || code >= ARRLEN(sev_gstate_strs)) return "Unknown gstate"; return sev_gstate_strs[code]; } int sev_ioctl(int vmfd, int cmd, void *data, int *error) { struct kvm_sev_cmd input; int ret; memset(&input, 0, sizeof(input)); input.id = cmd; input.sev_fd = sev_dev; input.data = (uintptr_t) data; ret = ioctl(vmfd, KVM_MEMORY_ENCRYPT_OP, &input); if (error) *error = input.error; return ret; } void sev_get_measure(int vmfd) { struct kvm_sev_launch_measure msrmt; int ret, fwerr; uint8_t *data; memset(&msrmt, 0, sizeof(msrmt)); ret = sev_ioctl(vmfd, KVM_SEV_LAUNCH_MEASURE, &msrmt, &fwerr); if (ret < 0 && fwerr != SEV_RET_INVALID_LEN) errx(1, "KVM_SEV_LAUNCH_MEASURE: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); data = malloc(msrmt.len); msrmt.uaddr = (uintptr_t) data; ret = sev_ioctl(vmfd, KVM_SEV_LAUNCH_MEASURE, &msrmt, &fwerr); if (ret < 0) errx(1, "KVM_SEV_LAUNCH_MEASURE: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); free(data); } uint8_t sev_guest_state(int vmfd, uint32_t handle) { struct kvm_sev_guest_status status; int ret, fwerr; status.handle = handle; ret = sev_ioctl(vmfd, KVM_SEV_GUEST_STATUS, &status, &fwerr); if (ret < 0) { errx(1, "KVM_SEV_GUEST_STATUS: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); } return status.state; } void sev_debug_encrypt(int vmfd, void *src, void *dst, size_t size) { struct kvm_sev_dbg enc; int ret, fwerr; enc.src_uaddr = (uintptr_t) src; enc.dst_uaddr = (uintptr_t) dst; enc.len = size; ret = sev_ioctl(vmfd, KVM_SEV_DBG_ENCRYPT, &enc, &fwerr); if (ret < 0) errx(1, "KVM_SEV_DBG_ENCRYPT: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); } void sev_debug_decrypt(int vmfd, void *src, void *dst, size_t size) { struct kvm_sev_dbg enc; int ret, fwerr; enc.src_uaddr = (uintptr_t) src; enc.dst_uaddr = (uintptr_t) dst; enc.len = size; ret = sev_ioctl(vmfd, KVM_SEV_DBG_DECRYPT, &enc, &fwerr); if (ret < 0) errx(1, "KVM_SEV_DBG_DECRYPT: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); } void kvm_init(struct kvm *kvm, size_t ramsize, void *code_start, void *code_stop) { struct kvm_userspace_memory_region region; struct kvm_regs regs; struct kvm_sregs sregs; int ret; /* Create a kvm instance */ kvm->vmfd = ioctl(kvm_dev, KVM_CREATE_VM, 0); if (kvm->vmfd < 0) err(1, "KVM_CREATE_VM"); /* Allocate guest memory */ kvm->memsize = ramsize; kvm->mem = mmap(NULL, kvm->memsize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (!kvm->mem) err(1, "Allocating guest memory"); assert(code_stop - code_start <= kvm->memsize); memcpy(kvm->mem, code_start, code_stop - code_start); /* Map it into the vm */ memset(®ion, 0, sizeof(region)); region.slot = 0; region.memory_size = kvm->memsize; region.guest_phys_addr = 0x0000; region.userspace_addr = (uintptr_t) kvm->mem; ret = ioctl(kvm->vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); if (ret < 0) err(1, "KVM_SET_USER_MEMORY_REGION"); /* Create virtual cpu core */ kvm->vcpufd = ioctl(kvm->vmfd, KVM_CREATE_VCPU, 0); if (kvm->vcpufd < 0) err(1, "KVM_CREATE_VCPU"); /* Initialize segment regs */ memset(&sregs, 0, sizeof(sregs)); ret = ioctl(kvm->vcpufd, KVM_GET_SREGS, &sregs); if (ret < 0) err(1, "KVM_GET_SREGS"); sregs.cs.base = 0; sregs.cs.selector = 0; ret = ioctl(kvm->vcpufd, KVM_SET_SREGS, &sregs); if (ret < 0) err(1, "KVM_SET_SREGS"); /* Initialize rest of registers */ memset(®s, 0, sizeof(regs)); regs.rip = 0x0; regs.rsp = kvm->memsize - 8; regs.rbp = kvm->memsize - 8; regs.rax = 0; regs.rdx = 0; regs.rflags = 0x2; ret = ioctl(kvm->vcpufd, KVM_SET_REGS, ®s); if (ret < 0) err(1, "KVM_SET_REGS"); } void sev_kvm_init(struct kvm *kvm, size_t ramsize, void *code_start, void *code_stop) { struct kvm_userspace_memory_region region; struct kvm_sev_launch_update_data update; struct kvm_sev_launch_start start; struct kvm_regs regs; struct kvm_sregs sregs; int ret, fwerr; /* Create a kvm instance */ kvm->vmfd = ioctl(kvm_dev, KVM_CREATE_VM, 0); if (kvm->vmfd < 0) err(1, "KVM_CREATE_VM"); /* Allocate guest memory */ kvm->memsize = ramsize; kvm->mem = mmap(NULL, kvm->memsize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (!kvm->mem) err(1, "Allocating guest memory"); assert(code_stop - code_start <= kvm->memsize); memcpy(kvm->mem, code_start, code_stop - code_start); /* Map it into the vm */ memset(®ion, 0, sizeof(region)); region.slot = 0; region.memory_size = kvm->memsize; region.guest_phys_addr = 0x0000; region.userspace_addr = (uintptr_t) kvm->mem; ret = ioctl(kvm->vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); if (ret < 0) err(1, "KVM_SET_USER_MEMORY_REGION"); /* Enable SEV for vm */ ret = sev_ioctl(kvm->vmfd, KVM_SEV_INIT, NULL, &fwerr); if (ret < 0) errx(1, "KVM_SEV_INIT: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); /* Generate encryption keys and set policy */ memset(&start, 0, sizeof(start)); start.handle = 0; start.policy = 0; ret = sev_ioctl(kvm->vmfd, KVM_SEV_LAUNCH_START, &start, &fwerr); if (ret < 0) errx(1, "KVM_SEV_LAUNCH_START: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); /* Prepare the vm memory (by encrypting it) */ memset(&update, 0, sizeof(update)); update.uaddr = (uintptr_t) kvm->mem; update.len = ramsize; ret = sev_ioctl(kvm->vmfd, KVM_SEV_LAUNCH_UPDATE_DATA, &update, &fwerr); if (ret < 0) errx(1, "KVM_SEV_LAUNCH_UPDATE_DATA: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); /* Collect a measurement (necessary) */ sev_get_measure(kvm->vmfd); /* Finalize launch process */ ret = sev_ioctl(kvm->vmfd, KVM_SEV_LAUNCH_FINISH, 0, &fwerr); if (ret < 0) errx(1, "KVM_SEV_LAUNCH_FINISH: (%s) %s", strerror(errno), sev_fwerr_str(fwerr)); ret = sev_guest_state(kvm->vmfd, start.handle); if (ret != GSTATE_RUNNING) errx(1, "Bad guest state: %s", sev_gstate_str(fwerr)); /* Create virtual cpu core */ kvm->vcpufd = ioctl(kvm->vmfd, KVM_CREATE_VCPU, 0); if (kvm->vcpufd < 0) err(1, "KVM_CREATE_VCPU"); /* Map the shared kvm_run structure and following data */ ret = ioctl(kvm_dev, KVM_GET_VCPU_MMAP_SIZE, NULL); if (ret < 0) err(1, "KVM_GET_VCPU_MMAP_SIZE"); if (ret < sizeof(struct kvm_run)) errx(1, "KVM_GET_VCPU_MMAP_SIZE too small"); kvm->run = mmap(NULL, ret, PROT_READ | PROT_WRITE, MAP_SHARED, kvm->vcpufd, 0); if (!kvm->run) err(1, "mmap vcpu"); /* Initialize segment regs */ memset(&sregs, 0, sizeof(sregs)); ret = ioctl(kvm->vcpufd, KVM_GET_SREGS, &sregs); if (ret < 0) err(1, "KVM_GET_SREGS"); sregs.cs.base = 0; sregs.cs.selector = 0; ret = ioctl(kvm->vcpufd, KVM_SET_SREGS, &sregs); if (ret < 0) err(1, "KVM_SET_SREGS"); /* Initialize rest of registers */ memset(®s, 0, sizeof(regs)); regs.rip = 0x0; regs.rsp = kvm->memsize - 8; regs.rbp = kvm->memsize - 8; regs.rax = 0; regs.rdx = 0; regs.rflags = 0x2; ret = ioctl(kvm->vcpufd, KVM_SET_REGS, ®s); if (ret < 0) err(1, "KVM_SET_REGS"); } void sev_es_kvm_init(struct kvm *kvm, size_t ramsize, void *code_start, void *code_stop) { errx(1, "Not implemented"); } void sev_snp_kvm_init(struct kvm *kvm, size_t ramsize, void *code_start, void *code_stop) { errx(1, "Not implemented"); } void vm_init(struct kvm *kvm, size_t ramsize, void *code_start, void *code_end) { if (!strcmp(vmtype, "kvm")) { kvm_init(kvm, ramsize, code_start, code_end); } else if (!strcmp(vmtype, "sev")) { sev_kvm_init(kvm, ramsize, code_start, code_end); } else if (!strcmp(vmtype, "sev-es")) { sev_es_kvm_init(kvm, ramsize, code_start, code_end); } else if (!strcmp(vmtype, "sev-snp")) { sev_snp_kvm_init(kvm, ramsize, code_start, code_end); } else { errx(1, "invalid version"); } } void vm_deinit(struct kvm *kvm) { close(kvm->vmfd); close(kvm->vcpufd); munmap(kvm->mem, kvm->memsize); } void print_counts(uint8_t *counts) { int i; for (i = 0; i < 64; i++) { if (i % 16 == 0 && i) printf("\n"); if (counts[i] == 1) printf("\x1b[38;5;88m"); else if (counts[i] > 1) printf("\x1b[38;5;196m"); printf("%2i ", i); if (counts[i] > 0) printf("\x1b[0m"); } printf("\nTarget Set %i Count: %u\n\n", TARGET_SET, counts[TARGET_SET]); } void collect(struct kvm *kvm, uint8_t *counts) { struct kvm_regs regs; int ret; /* run vm twice, use count without initial stack setup */ ret = ioctl(kvm->vcpufd, KVM_RUN, NULL); if (ret < 0) err(1, "KVM_RUN"); if (kvm->run->exit_reason == KVM_EXIT_MMIO) { memset(®s, 0, sizeof(regs)); ret = ioctl(kvm->vcpufd, KVM_GET_REGS, ®s); if (ret < 0) err(1, "KVM_GET_REGS"); errx(1, "Victim access OOB: %llu %08llx => %02X\n", kvm->run->mmio.phys_addr, regs.rip, ((uint8_t *)kvm->mem)[regs.rip]); } else if (kvm->run->exit_reason != KVM_EXIT_IO) { errx(1, "KVM died: %i\n", kvm->run->exit_reason); } ret = ioctl(kvm_dev, KVM_CPC_READ_COUNTS, counts); if (ret == -1) err(1, "ioctl KVM_CPC_READ_COUNTS"); } int main(int argc, const char **argv) { struct kvm vms[2]; uint8_t counts[2][SAMPLE_COUNT][64]; uint8_t baseline[64]; uint32_t calc; int i, k, ret; vmtype = "kvm"; if (argc > 1) vmtype = argv[1]; if (strcmp(vmtype, "kvm") && strcmp(vmtype, "sev") && strcmp(vmtype, "sev-es") && strcmp(vmtype, "sev-snp")) errx(1, "invalid version: %s", vmtype); setvbuf(stdout, NULL, _IONBF, 0); pin_process(0, TARGET_CORE, true); kvm_dev = open("/dev/kvm", O_RDWR | O_CLOEXEC); if (kvm_dev < 0) err(1, "open /dev/kvm"); sev_dev = open("/dev/sev", O_RDWR | O_CLOEXEC); if (sev_dev < 0) err(1, "open /dev/sev"); /* Make sure we have the stable version of the API */ ret = ioctl(kvm_dev, KVM_GET_API_VERSION, NULL); if (ret < 0) err(1, "KVM_GET_API_VERSION"); if (ret != 12) errx(1, "KVM_GET_API_VERSION %d, expected 12", ret); /* Reset kernel module state */ ret = ioctl(kvm_dev, KVM_CPC_RESET); if (ret < 0) err(1, "ioctl KVM_CPC_RESET"); vm_init(&vms[WITH], 64 * 64 * 8 * 2, __start_guest_with, __stop_guest_with); vm_init(&vms[WITHOUT], 64 * 64 * 8 * 2, __start_guest_without, __stop_guest_without); calc = true; ret = ioctl(kvm_dev, KVM_CPC_CALC_BASELINE, &calc); if (ret == -1) err(1, "ioctl KVM_CPC_CALC_BASELINE"); for (i = 0; i < SAMPLE_COUNT; i++) { collect(&vms[WITH], counts[WITH][i]); collect(&vms[WITHOUT], counts[WITHOUT][i]); } calc = false; ret = ioctl(kvm_dev, KVM_CPC_CALC_BASELINE, &calc); if (ret == -1) err(1, "ioctl KVM_CPC_CALC_BASELINE"); ret = ioctl(kvm_dev, KVM_CPC_READ_BASELINE, baseline); if (ret == -1) err(1, "ioctl KVM_CPC_READ_BASELINE"); for (i = 0; i < SAMPLE_COUNT; i++) { printf("Evictions with access:\n"); print_counts(counts[WITH][i]); printf("Evictions without access:\n"); print_counts(counts[WITHOUT][i]); for (k = 0; k < 64; k++) { if (counts[WITH][i][k] >= 8) warnx("with: Count OOB (%i, %i)", i, k); if (baseline[k] > counts[WITH][i][k]) warnx("with: Baseline oob (%i, %i)", i, k); counts[WITH][i][k] -= baseline[k]; if (counts[WITHOUT][i][k] >= 8) warnx("without: Count OOB (%i, %i)", i, k); if (baseline[k] > counts[WITHOUT][i][k]) warnx("without: Baseline OOB (%i, %i)", i, k); counts[WITHOUT][i][k] -= baseline[k]; } if (!counts[WITH][i][TARGET_SET]) warnx("with: Missing eviction in target set"); } vm_deinit(&vms[WITH]); vm_deinit(&vms[WITHOUT]); close(kvm_dev); close(sev_dev); }