cachepc

Prime+Probe cache-based side-channel attack on AMD SEV-SNP protected virtual machines
git clone https://git.sinitax.com/sinitax/cachepc
Log | Files | Refs | Submodules | README | sfeed.txt

commit a554be1738d019e4b5d5b0b0ee9aac6b6ac302a6
parent 769e05dd63ed0379e7325da6e82c0c46c151ef4e
Author: Louis Burda <quent.burda@gmail.com>
Date:   Mon, 23 Jan 2023 20:38:36 +0100

Use 16-bit realmode assembly for guests (!)

Diffstat:
MMakefile | 2+-
Mcachepc/cachepc.c | 2+-
Mcachepc/cachepc.h | 5++++-
Mcachepc/event.c | 2+-
Mcachepc/kvm.c | 95++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mcachepc/uapi.h | 6++++--
Mtest/kvm-eviction.c | 15++++++++++-----
Mtest/kvm-eviction_guest.S | 13++++++++++---
Mtest/kvm-step.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mtest/kvm-step_guest.S | 27++++++++++++++++-----------
Mtest/kvm.c | 7+++++--
Mutil/disasm | 9+++++++--
12 files changed, 200 insertions(+), 47 deletions(-)

diff --git a/Makefile b/Makefile @@ -51,7 +51,7 @@ load: prep: sudo sh -c "echo 0 > /proc/sys/kernel/watchdog" - sudo cpupower frequency-set -f 3.7GHz + sudo cpupower frequency-set -d 3.7GHz -u 3.7GHz util/%: util/%.c $(CACHEPC_UAPI) diff --git a/cachepc/cachepc.c b/cachepc/cachepc.c @@ -534,7 +534,7 @@ is_in_arr(uint32_t elem, uint32_t *arr, uint32_t arr_len) } void -cachepc_apic_oneshot(uint32_t interval) +cachepc_apic_oneshot_run(uint32_t interval) { native_apic_mem_write(APIC_LVTT, LOCAL_TIMER_VECTOR | APIC_LVT_TIMER_ONESHOT); native_apic_mem_write(APIC_TDCR, APIC_TDR_DIV_1); diff --git a/cachepc/cachepc.h b/cachepc/cachepc.h @@ -97,7 +97,7 @@ void cachepc_probe(cacheline *head); uint64_t cachepc_read_pmc(uint64_t event); -void cachepc_apic_oneshot(uint32_t interval); +void cachepc_apic_oneshot_run(uint32_t interval); extern bool cachepc_debug; @@ -112,6 +112,9 @@ extern bool cachepc_pause_vm; extern bool cachepc_singlestep; extern bool cachepc_singlestep_reset; +extern bool cachepc_long_step; + +extern bool cachepc_apic_oneshot; extern uint32_t cachepc_apic_timer; extern uint32_t cachepc_track_mode; diff --git a/cachepc/event.c b/cachepc/event.c @@ -52,7 +52,7 @@ cachepc_send_event(struct cpc_event event) write_unlock(&cachepc_event_lock); /* wait for ack with timeout */ - deadline = ktime_get_ns() + 20000000000ULL; /* 20s in ns */ + deadline = ktime_get_ns() + 10000000000ULL; /* 10s in ns */ while (!cachepc_event_is_done(cachepc_event.id)) { if (ktime_get_ns() > deadline) { CPC_WARN("Timeout waiting for ack of event %llu\n", diff --git a/cachepc/kvm.c b/cachepc/kvm.c @@ -51,9 +51,14 @@ EXPORT_SYMBOL(cachepc_rip_prev_set); bool cachepc_singlestep = false; bool cachepc_singlestep_reset = false; -uint32_t cachepc_apic_timer = 0; +bool cachepc_long_step = false; EXPORT_SYMBOL(cachepc_singlestep); EXPORT_SYMBOL(cachepc_singlestep_reset); +EXPORT_SYMBOL(cachepc_long_step); + +bool cachepc_apic_oneshot = false; +uint32_t cachepc_apic_timer = 0; +EXPORT_SYMBOL(cachepc_apic_oneshot); EXPORT_SYMBOL(cachepc_apic_timer); uint32_t cachepc_track_mode = false; @@ -293,7 +298,11 @@ cachepc_kvm_reset_ioctl(void __user *arg_user) cachepc_singlestep = false; cachepc_singlestep_reset = false; + + cachepc_apic_oneshot = false; cachepc_apic_timer = 0; + + cachepc_retinst = 0; cachepc_rip_prev_set = false; return 0; @@ -315,6 +324,54 @@ cachepc_kvm_debug_ioctl(void __user *arg_user) } int +cachepc_kvm_get_regs_ioctl(void __user *arg_user) +{ + struct kvm_regs *regs; + struct kvm_vcpu *vcpu; + + if (!arg_user) return -EINVAL; + + if (!main_vm || xa_empty(&main_vm->vcpu_array)) + return -EFAULT; + + vcpu = xa_load(&main_vm->vcpu_array, 0); + + regs = kzalloc(sizeof(struct kvm_regs), GFP_KERNEL_ACCOUNT); + if (!regs) return -ENOMEM; + + regs->rax = kvm_rax_read(vcpu); + regs->rbx = kvm_rbx_read(vcpu); + regs->rcx = kvm_rcx_read(vcpu); + regs->rdx = kvm_rdx_read(vcpu); + regs->rsi = kvm_rsi_read(vcpu); + regs->rdi = kvm_rdi_read(vcpu); + regs->rsp = kvm_rsp_read(vcpu); + regs->rbp = kvm_rbp_read(vcpu); +#ifdef CONFIG_X86_64 + regs->r8 = kvm_r8_read(vcpu); + regs->r9 = kvm_r9_read(vcpu); + regs->r10 = kvm_r10_read(vcpu); + regs->r11 = kvm_r11_read(vcpu); + regs->r12 = kvm_r12_read(vcpu); + regs->r13 = kvm_r13_read(vcpu); + regs->r14 = kvm_r14_read(vcpu); + regs->r15 = kvm_r15_read(vcpu); +#endif + + regs->rip = kvm_rip_read(vcpu); + regs->rflags = kvm_get_rflags(vcpu); + + if (copy_to_user(arg_user, regs, sizeof(struct kvm_regs))) { + kfree(regs); + return -EFAULT; + } + + kfree(regs); + + return 0; +} + +int cachepc_kvm_test_eviction_ioctl(void __user *arg_user) { uint32_t u32; @@ -400,14 +457,26 @@ cachepc_kvm_apply_baseline_ioctl(void __user *arg_user) } int +cachepc_kvm_long_step_ioctl(void __user *arg_user) +{ + if (arg_user) return -EINVAL; + + cachepc_long_step = true; + + return 0; +} + +int cachepc_kvm_vmsa_read_ioctl(void __user *arg_user) { struct kvm_vcpu *vcpu; struct vcpu_svm *svm; - if (!main_vm || !arg_user) return -EINVAL; + if (!arg_user) return -EINVAL; + + if (!main_vm || xa_empty(&main_vm->vcpu_array)) + return -EFAULT; - BUG_ON(xa_empty(&main_vm->vcpu_array)); vcpu = xa_load(&main_vm->vcpu_array, 0); svm = to_svm(vcpu); @@ -439,7 +508,9 @@ cachepc_kvm_reset_tracking_ioctl(void __user *arg_user) struct kvm_vcpu *vcpu; struct cpc_fault *fault, *next; - BUG_ON(!main_vm || xa_empty(&main_vm->vcpu_array)); + if (!main_vm || xa_empty(&main_vm->vcpu_array)) + return -EFAULT; + vcpu = xa_load(&main_vm->vcpu_array, 0); cachepc_untrack_all(vcpu, KVM_PAGE_TRACK_EXEC); cachepc_untrack_all(vcpu, KVM_PAGE_TRACK_ACCESS); @@ -475,7 +546,9 @@ cachepc_kvm_track_mode_ioctl(void __user *arg_user) if (copy_from_user(&mode, arg_user, sizeof(mode))) return -EFAULT; - BUG_ON(!main_vm || xa_empty(&main_vm->vcpu_array)); + if (!main_vm || xa_empty(&main_vm->vcpu_array)) + return -EFAULT; + vcpu = xa_load(&main_vm->vcpu_array, 0); cachepc_untrack_all(vcpu, KVM_PAGE_TRACK_EXEC); @@ -483,8 +556,10 @@ cachepc_kvm_track_mode_ioctl(void __user *arg_user) cachepc_untrack_all(vcpu, KVM_PAGE_TRACK_WRITE); cachepc_apic_timer = 0; + cachepc_apic_oneshot = false; cachepc_singlestep = false; cachepc_singlestep_reset = false; + cachepc_long_step = false; switch (mode) { case CPC_TRACK_FULL: @@ -615,6 +690,8 @@ cachepc_kvm_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) return cachepc_kvm_reset_ioctl(arg_user); case KVM_CPC_DEBUG: return cachepc_kvm_debug_ioctl(arg_user); + case KVM_CPC_GET_REGS: + return cachepc_kvm_get_regs_ioctl(arg_user); case KVM_CPC_TEST_EVICTION: return cachepc_kvm_test_eviction_ioctl(arg_user); case KVM_CPC_READ_COUNTS: @@ -627,6 +704,8 @@ cachepc_kvm_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) return cachepc_kvm_calc_baseline_ioctl(arg_user); case KVM_CPC_APPLY_BASELINE: return cachepc_kvm_apply_baseline_ioctl(arg_user); + case KVM_CPC_LONG_STEP: + return cachepc_kvm_long_step_ioctl(arg_user); case KVM_CPC_VMSA_READ: return cachepc_kvm_vmsa_read_ioctl(arg_user); case KVM_CPC_SVME_READ: @@ -670,7 +749,7 @@ cachepc_kvm_setup_test(void *p) cachepc_ctx = cachepc_get_ctx(); cachepc_ds = cachepc_prepare_ds(cachepc_ctx); - cachepc_victim = cachepc_prepare_victim(cachepc_ctx, 13); + cachepc_victim = cachepc_prepare_victim(cachepc_ctx, 15); cachepc_kvm_system_setup(); @@ -696,10 +775,14 @@ cachepc_kvm_init(void) cachepc_victim = NULL; cachepc_retinst = 0; + cachepc_long_step = false; cachepc_singlestep = false; cachepc_singlestep_reset = false; cachepc_track_mode = CPC_TRACK_NONE; + cachepc_apic_oneshot = false; + cachepc_apic_timer = 0; + cachepc_inst_fault_gfn = 0; cachepc_inst_fault_err = 0; diff --git a/cachepc/uapi.h b/cachepc/uapi.h @@ -13,7 +13,9 @@ #define KVM_CPC_RESET _IOWR(KVMIO, 0x20, __u32) #define KVM_CPC_DEBUG _IOW(KVMIO, 0x21, __u32) -#define KVM_CPC_TEST_EVICTION _IOWR(KVMIO, 0x22, __u32) +#define KVM_CPC_GET_REGS _IOW(KVMIO, 0x22, __u32) + +#define KVM_CPC_TEST_EVICTION _IOWR(KVMIO, 0x23, __u32) #define KVM_CPC_READ_COUNTS _IOR(KVMIO, 0x25, __u64) @@ -22,7 +24,7 @@ #define KVM_CPC_CALC_BASELINE _IOR(KVMIO, 0x28, __u32) #define KVM_CPC_APPLY_BASELINE _IOR(KVMIO, 0x29, __u32) -#define KVM_CPC_SINGLE_STEP _IO(KVMIO, 0x2A) +#define KVM_CPC_LONG_STEP _IO(KVMIO, 0x2A) #define KVM_CPC_VMSA_READ _IOR(KVMIO, 0x2C, __u64) #define KVM_CPC_SVME_READ _IOR(KVMIO, 0x2D, __u32) diff --git a/test/kvm-eviction.c b/test/kvm-eviction.c @@ -50,7 +50,7 @@ vm_init(struct kvm *kvm, void *code_start, void *code_end) { size_t ramsize; - ramsize = L1_SIZE * 2; + ramsize = L1_SIZE; if (!strcmp(vmtype, "kvm")) { kvm_init(kvm, ramsize, code_start, code_end); } else if (!strcmp(vmtype, "sev")) { @@ -77,15 +77,17 @@ collect(struct kvm *kvm, uint8_t *counts) ret = ioctl(kvm->vcpufd, KVM_RUN, NULL); if (ret == -1) err(1, "KVM_RUN"); - // warnx("rip:%lu code:%i", vm_get_rip(kvm), kvm->run->exit_reason); - if (kvm->run->exit_reason != KVM_EXIT_HLT) { + if (kvm->run->exit_reason == KVM_EXIT_MMIO) { + errx(1, "KVM died from OOB access! rip:%lu addr:%lu", + vm_get_rip(kvm), kvm->run->mmio.phys_addr); + } else if (kvm->run->exit_reason != KVM_EXIT_HLT) { errx(1, "KVM died! rip:%lu code:%i", vm_get_rip(kvm), kvm->run->exit_reason); } ret = ioctl(kvm_dev, KVM_CPC_READ_COUNTS, counts); - if (ret == -1) err(1, "ioctl KVM_CPC_READ_COUNTS"); + if (ret == -1) err(1, "KVM_CPC_READ_COUNTS"); } int @@ -114,7 +116,10 @@ main(int argc, const char **argv) /* reset kernel module state */ ret = ioctl(kvm_dev, KVM_CPC_RESET); - if (ret == -1) err(1, "ioctl KVM_CPC_RESET"); + if (ret == -1) err(1, "KVM_CPC_RESET"); + + ret = ioctl(kvm_dev, KVM_CPC_LONG_STEP); + if (ret == -1) err(1, "KVM_CPC_LONG_STEP"); /* resolve page faults in advance (code only covers 1 page).. * we want the read counts to apply between KVM_RUN and KVM_EXIT_HLT, diff --git a/test/kvm-eviction_guest.S b/test/kvm-eviction_guest.S @@ -7,13 +7,20 @@ .global guest_without_start .global guest_without_stop +.align(16) +.code16gcc + guest_with_start: - mov (L1_LINESIZE * (TARGET_SET + L1_SETS)), %rbx + mov $(L1_LINESIZE * (L1_SETS + TARGET_SET)), %bx + movb (%bx), %bl hlt - jmp guest_with_start + + mov $0x00, %ax + jmp *%ax guest_with_stop: guest_without_start: hlt - jmp guest_without_start + mov $0x00, %ax + jmp *%ax guest_without_stop: diff --git a/test/kvm-step.c b/test/kvm-step.c @@ -35,19 +35,51 @@ extern uint8_t guest_start[]; extern uint8_t guest_stop[]; -uint8_t * -read_counts() +static const char *vmtype; + +uint64_t +vm_get_rip(struct kvm *kvm) { - uint8_t *counts; + struct kvm_regs regs; + uint64_t rip; int ret; - counts = malloc(L1_SETS * sizeof(uint8_t)); - if (!counts) err(1, "malloc"); + if (!strcmp(vmtype, "sev-snp")) { + rip = snp_dbg_decrypt_rip(kvm->vmfd); + } else if (!strcmp(vmtype, "sev-es")) { + rip = sev_dbg_decrypt_rip(kvm->vmfd); + } else { + ret = ioctl(kvm_dev, KVM_CPC_GET_REGS, &regs); + if (ret == -1) err(1, "KVM_CPC_GET_REGS"); + rip = regs.rip; + } - ret = ioctl(kvm_dev, KVM_CPC_READ_COUNTS, counts); - if (ret) err(1, "ioctl KVM_CPC_READ_COUNTS"); + return rip; +} - return counts; +void +vm_init(struct kvm *kvm, void *code_start, void *code_end) +{ + size_t ramsize; + + ramsize = L1_SIZE * 2; + 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) +{ + kvm_deinit(kvm); } uint64_t @@ -68,8 +100,8 @@ monitor(struct kvm *kvm, bool baseline) ret = ioctl(kvm_dev, KVM_CPC_READ_COUNTS, counts); if (ret) err(1, "ioctl KVM_CPC_READ_COUNTS"); - printf("Event: cnt:%llu rip:%lu inst:%llu data:%llu retired:%llu\n", - event.step.fault_count, snp_dbg_decrypt_rip(kvm->vmfd), + printf("Event: rip:%llu cnt:%llu inst:%llu data:%llu ret:%llu\n", + vm_get_rip(kvm), event.step.fault_count, event.step.fault_gfns[0], event.step.fault_gfns[1], event.step.retinst); print_counts(counts); @@ -92,13 +124,20 @@ main(int argc, const char **argv) uint32_t arg; int 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 vm mode: %s", vmtype); + setvbuf(stdout, NULL, _IONBF, 0); pin_process(0, TARGET_CORE, true); kvm_setup_init(); - sev_snp_kvm_init(&kvm, L1_SIZE * 2, guest_start, guest_stop); + vm_init(&kvm, guest_start, guest_stop); /* reset kernel module state */ ret = ioctl(kvm_dev, KVM_CPC_RESET, NULL); @@ -141,6 +180,7 @@ main(int argc, const char **argv) while (eventcnt < 50) { eventcnt += monitor(&kvm, true); } + printf("Baseline done\n"); ret = ioctl(kvm_dev, KVM_CPC_VM_REQ_PAUSE); if (ret) err(1, "ioctl KVM_CPC_VM_REQ_PAUSE"); @@ -191,7 +231,7 @@ main(int argc, const char **argv) exit(0); } - kvm_deinit(&kvm); + vm_deinit(&kvm); kvm_setup_deinit(); } diff --git a/test/kvm-step_guest.S b/test/kvm-step_guest.S @@ -5,18 +5,23 @@ .global guest_start .global guest_stop +.align(16) +.code16gcc + guest_start: - mov $(L1_LINESIZE * (L1_SETS + 9)), %rbx -# hlt -# mov $(L1_LINESIZE * (L1_SETS + 10)), %rbx - mov $(L1_LINESIZE * (L1_SETS + 11)), %rbx -# hlt -# mov $(L1_LINESIZE * (L1_SETS + 12)), %rbx - mov $(L1_LINESIZE * (L1_SETS + 13)), %rbx -# hlt -# mov $(L1_LINESIZE * (L1_SETS + 14)), %rbx - mov $(L1_LINESIZE * (L1_SETS + 15)), %rbx + mov $(L1_LINESIZE * (L1_SETS + 11)), %bx + movb (%bx), %bl + hlt + + mov $(L1_LINESIZE * (L1_SETS + 13)), %bx + movb (%bx), %bl hlt - jmp guest_start + + mov $(L1_LINESIZE * (L1_SETS + 15)), %bx + movb (%bx), %bl + hlt + + mov $0x00, %ax + jmp *%ax guest_stop: diff --git a/test/kvm.c b/test/kvm.c @@ -220,10 +220,14 @@ kvm_init_memory(struct kvm *kvm, size_t ramsize, kvm->mem = mmap(NULL, kvm->memsize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (!kvm->mem) err(1, "mmap kvm->mem"); - memset(kvm->mem, 0, kvm->memsize); + /* nop slide oob to detect errors quickly */ + memset(kvm->mem, 0x90, kvm->memsize); assert(code_stop - code_start <= kvm->memsize); memcpy(kvm->mem, code_start, code_stop - code_start); + printf("KVM Memory:\n"); + hexdump(code_start, code_stop - code_start); + memset(&region, 0, sizeof(region)); region.slot = 0; region.memory_size = kvm->memsize; @@ -272,7 +276,6 @@ kvm_init_regs(struct kvm *kvm) regs.rip = 0; regs.rsp = kvm->memsize - 8; regs.rbp = kvm->memsize - 8; - regs.rflags = 0x2; ret = ioctl(kvm->vcpufd, KVM_SET_REGS, &regs); if (ret == -1) err(1, "KVM_SET_REGS"); } diff --git a/util/disasm b/util/disasm @@ -1,8 +1,13 @@ #!/bin/sh if [ $# -lt 2 ]; then - echo "Usage: guest_asm FILE FUNC" + echo "Usage: guest_asm FILE FUNC (guest)" exit 1 fi -gdb --batch -ex "disassemble $2" $1 +ARCH="i386" +if [ "$3" = "guest" ]; then + ARCH="i8086" +fi + +gdb --batch -ex "set architecture $ARCH" -ex "disassemble $2" $1