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 da9326a66bd534de38ab07dac96e8af051e94923
parent df90539b9e7ee75c206d5b77d4f1b94882d090c7
Author: Louis Burda <quent.burda@gmail.com>
Date:   Thu,  2 Feb 2023 09:42:58 -0600

Add event batching and handling of instructions loads on page boundaries for CPC_TRACK_PAGES

Diffstat:
MMakefile | 2+-
Mcachepc/cachepc.c | 2+-
Mcachepc/cachepc.h | 6+++++-
Mcachepc/const.h | 4+++-
Mcachepc/event.c | 33++++++++++++++++++++++++---------
Mcachepc/event.h | 9++++-----
Mcachepc/kvm.c | 12++++++++----
Mcachepc/uapi.h | 6+++---
Mqemu/install.sh | 2+-
Mqemu/launch-victim.sh | 6+++---
Mqemu/launch.sh | 2+-
Aqemu/ssh.sh | 3+++
Mtest/kvm-pagestep.c | 9+++++++++
Mtest/kvm-pagestep_guest.S | 12++++++++++++
Mtest/qemu-pagestep.c | 45+++++++++++++++++++++++++++++++++++++++------
15 files changed, 117 insertions(+), 36 deletions(-)

diff --git a/Makefile b/Makefile @@ -60,7 +60,7 @@ load: prep: sudo sh -c "echo 0 > /proc/sys/kernel/watchdog" - sudo cpupower frequency-set -d 3.7GHz -u 3.7GHz + sudo cpupower frequency-set -g powersave sudo bash -c "for f in /proc/irq/*/smp_affinity; do echo 1 > \$$f 2>/dev/null; done" util/%: util/%.c $(UTIL_SRCS) diff --git a/cachepc/cachepc.c b/cachepc/cachepc.c @@ -217,6 +217,6 @@ cpc_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); - native_apic_mem_write(APIC_TMICT, interval); + native_apic_mem_write(APIC_TMICT, interval / CPC_APIC_TIMER_SOFTDIV); } EXPORT_SYMBOL(cpc_apic_oneshot_run); diff --git a/cachepc/cachepc.h b/cachepc/cachepc.h @@ -37,8 +37,12 @@ struct cpc_fault { }; struct cpc_track_pages { + bool prev_avail; bool cur_avail; + bool next_avail; + uint64_t prev_gfn; uint64_t cur_gfn; + uint64_t next_gfn; uint64_t retinst; bool in_step; }; @@ -95,7 +99,7 @@ extern bool cpc_singlestep; extern bool cpc_singlestep_reset; extern bool cpc_long_step; -extern bool cpc_apic_oneshot; +extern volatile bool cpc_apic_oneshot; extern uint32_t cpc_apic_timer; extern uint32_t cpc_track_mode; diff --git a/cachepc/const.h b/cachepc/const.h @@ -21,4 +21,6 @@ #define CPC_CL_COUNT_OFFSET 16 #define CPC_APIC_TIMER_SOFTDIV 3 -#define CPC_APIC_TIMER_MIN (100 * CPC_APIC_TIMER_SOFTDIV) +#define CPC_APIC_TIMER_MIN (50 * CPC_APIC_TIMER_SOFTDIV) + +#define CPC_EVENT_BATCH_MAX 1000 diff --git a/cachepc/event.c b/cachepc/event.c @@ -33,7 +33,9 @@ EXPORT_SYMBOL(cpc_send_track_page_event); void cpc_events_init(void) { - cpc_eventbuf = NULL; + cpc_eventbuf = kzalloc(sizeof(struct cpc_event) + * CPC_EVENT_BATCH_MAX, GFP_KERNEL); + BUG_ON(!cpc_eventbuf); cpc_eventbuf_len = 0; cpc_event_batching = false; rwlock_init(&cpc_event_lock); @@ -70,7 +72,7 @@ cpc_send_event(struct cpc_event event) } if (cpc_event_batching) { - if (cpc_eventbuf_len < CPC_EVENTBUF_CAP) { + if (cpc_eventbuf_len < CPC_EVENT_BATCH_MAX) { event.id = 0; memcpy(&cpc_eventbuf[cpc_eventbuf_len], &event, sizeof(struct cpc_event)); @@ -238,10 +240,22 @@ cpc_ack_event_ioctl(void __user *arg_user) } int -cpc_read_events_ioctl(void __user *arg_user) +cpc_batch_events_ioctl(void __user *arg_user) +{ + uint32_t enable; + + if (copy_from_user(&enable, arg_user, sizeof(enable))) + return -EFAULT; + + cpc_event_batching = enable; + + return 0; +} + +int +cpc_read_batch_ioctl(void __user *arg_user) { - struct cpc_batch_event batch; - size_t n; + struct cpc_event_batch batch; if (!arg_user) return -EINVAL; @@ -256,11 +270,12 @@ cpc_read_events_ioctl(void __user *arg_user) return -EAGAIN; } - n = cpc_eventbuf_len; - if (batch.maxcnt < n) - n = batch.maxcnt; + batch.cnt = cpc_eventbuf_len; + if (batch.maxcnt < batch.cnt) + batch.cnt = batch.maxcnt; - if (copy_to_user(batch.buf, cpc_eventbuf, sizeof(struct cpc_event) * n)) { + if (copy_to_user(batch.buf, cpc_eventbuf, + sizeof(struct cpc_event) * batch.cnt)) { write_unlock(&cpc_event_lock); return -EFAULT; } diff --git a/cachepc/event.h b/cachepc/event.h @@ -7,14 +7,12 @@ #include <linux/kvm_host.h> #include <linux/types.h> -#define CPC_EVENTBUF_CAP 5000 - extern struct cpc_event *cpc_eventbuf; extern size_t cpc_eventbuf_len; extern bool cpc_event_batching; -void cpc_event_init(void); -void cpc_event_deinit(void); +void cpc_events_init(void); +void cpc_events_deinit(void); void cpc_events_reset(void); int cpc_send_guest_event(uint64_t type, uint64_t val); @@ -27,4 +25,5 @@ bool cpc_event_is_done(void); int cpc_poll_event_ioctl(void __user *arg_user); int cpc_ack_event_ioctl(void __user *arg_user); -int cpc_read_events_ioctl(void __user *arg_user); +int cpc_batch_events_ioctl(void __user *arg_user); +int cpc_read_batch_ioctl(void __user *arg_user); diff --git a/cachepc/kvm.c b/cachepc/kvm.c @@ -59,7 +59,7 @@ EXPORT_SYMBOL(cpc_singlestep); EXPORT_SYMBOL(cpc_singlestep_reset); EXPORT_SYMBOL(cpc_long_step); -bool cpc_apic_oneshot = false; +volatile bool cpc_apic_oneshot = false; uint32_t cpc_apic_timer = 0; EXPORT_SYMBOL(cpc_apic_oneshot); EXPORT_SYMBOL(cpc_apic_timer); @@ -624,8 +624,10 @@ cpc_kvm_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) return cpc_poll_event_ioctl(arg_user); case KVM_CPC_ACK_EVENT: return cpc_ack_event_ioctl(arg_user); - case KVM_CPC_READ_EVENTS: - return cpc_read_events_ioctl(arg_user); + case KVM_CPC_BATCH_EVENTS: + return cpc_batch_events_ioctl(arg_user); + case KVM_CPC_READ_BATCH: + return cpc_read_batch_ioctl(arg_user); // case KVM_CPC_TRACK_PAGE: // return cpc_track_page_ioctl(arg_user); // case KVM_CPC_TRACK_RANGE_START: @@ -700,7 +702,7 @@ cpc_kvm_init(void) cpc_baseline = kzalloc(L1_SETS, GFP_KERNEL); BUG_ON(!cpc_baseline); - cpc_events_reset(); + cpc_events_init(); ret = smp_call_function_single(CPC_ISOLCPU, cpc_setup_test, NULL, true); @@ -710,6 +712,8 @@ cpc_kvm_init(void) void cpc_kvm_exit(void) { + cpc_events_deinit(); + kfree(cpc_msrmts); kfree(cpc_baseline); diff --git a/cachepc/uapi.h b/cachepc/uapi.h @@ -30,7 +30,8 @@ #define KVM_CPC_POLL_EVENT _IOWR(KVMIO, 0x40, struct cpc_event) #define KVM_CPC_ACK_EVENT _IOWR(KVMIO, 0x41, __u64) -#define KVM_CPC_READ_EVENTS _IOWR(KVMIO, 0x42, struct cpc_batch_event) +#define KVM_CPC_BATCH_EVENTS _IOW(KVMIO, 0x42, __u32) +#define KVM_CPC_READ_BATCH _IOWR(KVMIO, 0x43, struct cpc_event_batch) #define KVM_CPC_VM_REQ_PAUSE _IO(KVMIO, 0x50) @@ -93,7 +94,7 @@ struct cpc_guest_event { __u64 val; }; -struct cpc_batch_event { +struct cpc_event_batch { __u64 cnt; __u64 maxcnt; struct cpc_event *buf; @@ -106,7 +107,6 @@ struct cpc_event { struct cpc_track_step_event step; struct cpc_track_page_event page; struct cpc_guest_event guest; - struct cpc_batch_event batch; }; }; diff --git a/qemu/install.sh b/qemu/install.sh @@ -5,7 +5,7 @@ set -ex gitroot=$(git rev-parse --show-toplevel) cd "$gitroot/qemu" -DISK="debian11.qcow2" +DISK="guest.qcow2" DEBIANISO="debian-11.4.0-amd64-DVD-1.iso" if [ ! -e "$DISK" ]; then diff --git a/qemu/launch-victim.sh b/qemu/launch-victim.sh @@ -10,15 +10,15 @@ if [ ! -e cmdline ]; then exit 1 fi -if [ ! -e debian11_encrypted.qcow2 ]; then +if [ ! -e guest_encrypted.qcow2 ]; then echo "Copying disk.." - rsync -a --info=progress2 debian11.qcow2 debian11_encrypted.qcow2 + rsync -a --info=progress2 guest.qcow2 guest_encrypted.qcow2 fi sudo LIBVIRT_DEBUG=1 virsh net-start default 2>&1 | grep -i warning || true sudo PREFIX=$gitroot/AMDSEV $gitroot/AMDSEV/launch-qemu.sh \ - -hda debian11_encrypted.qcow2 \ + -hda guest_encrypted.qcow2 \ -console serial \ -vnc 1 \ -mem 2024 \ diff --git a/qemu/launch.sh b/qemu/launch.sh @@ -8,7 +8,7 @@ cd "$gitroot/qemu" sudo LIBVIRT_DEBUG=1 virsh net-start default 2>&1 | grep -i warning || true sudo PREFIX=$gitroot/AMDSEV $gitroot/AMDSEV/launch-qemu.sh \ - -hda debian11.qcow2 \ + -hda guest.qcow2 \ -console serial \ -vnc 1 \ -mem 2024 \ diff --git a/qemu/ssh.sh b/qemu/ssh.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +ssh -p 8000 test@localhost diff --git a/test/kvm-pagestep.c b/test/kvm-pagestep.c @@ -41,6 +41,13 @@ monitor(struct kvm *kvm, bool baseline) return 1; } +void +kill_child(void) +{ + printf("Killing vm..\n"); + kill(child, SIGKILL); +} + int main(int argc, const char **argv) { @@ -96,6 +103,8 @@ main(int argc, const char **argv) } else { pin_process(0, SECONDARY_CORE, true); + atexit(kill_child); + ipc_wait_child(ipc); printf("Monitor start\n"); diff --git a/test/kvm-pagestep_guest.S b/test/kvm-pagestep_guest.S @@ -11,6 +11,17 @@ guest: nop .endr +# instruction on page boundary test + nop +.rept 2 +.rept L1_LINESIZE * L1_SETS - 2 + nop +.endr + cpuid # two byte instruction +.endr +# --------------------------------- + +# speculation on conditional jump test mov $0x01, %bx cmp $0x00, %bx @@ -19,6 +30,7 @@ guest: nop .endr skip: +# ----------------------------------- jmp guest diff --git a/test/qemu-pagestep.c b/test/qemu-pagestep.c @@ -15,23 +15,48 @@ #include <stdio.h> #include <stdlib.h> +struct cpc_event *batch_buffer; + +void +report(struct cpc_event *event) +{ + if (event->type == CPC_EVENT_TRACK_PAGE) { + printf("Page event: rip:%016llx prev:%08llx next:%08llx ret:%llu\n", + vm_get_rip(), event->page.inst_gfn_prev, + event->page.inst_gfn, event->page.retinst); + } else if (event->type == CPC_EVENT_GUEST) { + printf("Guest event: type:%u arg:%u\n", + event->guest.type, event->guest.val); + } else { + printf("Unexpected event type %i\n", event->type); + } + printf("\n"); +} + void monitor(void) { struct cpc_event event; + struct cpc_event_batch batch; + size_t i; int ret; ret = ioctl(kvm_dev, KVM_CPC_POLL_EVENT, &event); if (ret && errno == EAGAIN) return; if (ret) err(1, "KVM_CPC_POLL_EVENT"); - if (event.type == CPC_EVENT_TRACK_PAGE) { - printf("Event: rip:%016llx prev:%08llx next:%08llx ret:%llu\n", - vm_get_rip(), event.page.inst_gfn_prev, - event.page.inst_gfn, event.page.retinst); - printf("\n"); + if (event.type == CPC_EVENT_BATCH) { + batch.cnt = 0; + batch.maxcnt = CPC_EVENT_BATCH_MAX; + batch.buf = batch_buffer; + + ret = ioctl(kvm_dev, KVM_CPC_READ_BATCH, &batch); + if (ret) err(1, "KVM_CPC_READ_BATCH"); + + for (i = 0; i < batch.cnt; i++) + report(&batch.buf[i]); } else { - printf("Unexpected event type %i\n", event.type); + report(&event); } ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id); @@ -42,8 +67,12 @@ int main(int argc, const char **argv) { struct cpc_track_cfg cfg; + uint32_t arg; int ret; + batch_buffer = malloc(sizeof(struct cpc_event) * CPC_EVENT_BATCH_MAX); + if (!batch_buffer) err(1, "malloc"); + setvbuf(stdout, NULL, _IONBF, 0); kvm_setup_init(); @@ -53,6 +82,10 @@ main(int argc, const char **argv) ret = ioctl(kvm_dev, KVM_CPC_RESET); if (ret) err(1, "KVM_CPC_RESET"); + arg = true; + ret = ioctl(kvm_dev, KVM_CPC_BATCH_EVENTS, &arg); + if (ret) err(1, "KVM_CPC_BATCH_EVENTS"); + memset(&cfg, 0, sizeof(cfg)); cfg.mode = CPC_TRACK_PAGES; ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);