2022-07-15 19:38:16

by Peter Gonda

[permalink] [raw]
Subject: [RFC V1 00/10] *** KVM: selftests: Add simple SEV test

This patch series combines the work Michael Roth has done in supporting
SEV guests in selftests and the work Sean Christopherson suggested to
allow ucalls from SEV guests. And the work Sean has sent to consolidate
the ucall boilerplate code. Along with a very simple version of the
SEV selftests Michael originally proposed.

Michael Roth (7):
KVM: selftests: move vm_phy_pages_alloc() earlier in file
KVM: selftests: sparsebit: add const where appropriate
KVM: selftests: add hooks for managing encrypted guest memory
KVM: selftests: handle encryption bits in page tables
KVM: selftests: add support for encrypted vm_vaddr_* allocations
KVM: selftests: add library for creating/interacting with SEV guests
KVM: selftests: Add simple sev vm testing

Sean Christopherson (1):
KVM: selftests: Consolidate boilerplate code in get_ucall()

Peter Gonda (2):
tools: Add atomic_test_and_set_bit()
KVM: selftests: Make ucall work with encrypted guests

tools/arch/x86/include/asm/atomic.h | 7 +
tools/testing/selftests/kvm/.gitignore | 1 +
tools/testing/selftests/kvm/Makefile | 4 +
.../selftests/kvm/include/kvm_util_base.h | 50 +++-
.../testing/selftests/kvm/include/sparsebit.h | 36 +--
.../selftests/kvm/include/ucall_common.h | 15 +-
.../selftests/kvm/include/x86_64/sev.h | 44 +++
.../testing/selftests/kvm/lib/aarch64/ucall.c | 36 +--
tools/testing/selftests/kvm/lib/kvm_util.c | 268 +++++++++++++-----
.../selftests/kvm/lib/perf_test_util.c | 2 +-
tools/testing/selftests/kvm/lib/riscv/ucall.c | 40 +--
tools/testing/selftests/kvm/lib/s390x/ucall.c | 37 +--
tools/testing/selftests/kvm/lib/sparsebit.c | 48 ++--
.../testing/selftests/kvm/lib/ucall_common.c | 140 +++++++++
.../selftests/kvm/lib/x86_64/processor.c | 15 +-
tools/testing/selftests/kvm/lib/x86_64/sev.c | 251 ++++++++++++++++
.../testing/selftests/kvm/lib/x86_64/ucall.c | 37 +--
.../selftests/kvm/x86_64/sev_all_boot_test.c | 134 +++++++++
18 files changed, 903 insertions(+), 262 deletions(-)
create mode 100644 tools/testing/selftests/kvm/include/x86_64/sev.h
create mode 100644 tools/testing/selftests/kvm/lib/ucall_common.c
create mode 100644 tools/testing/selftests/kvm/lib/x86_64/sev.c
create mode 100644 tools/testing/selftests/kvm/x86_64/sev_all_boot_test.c

--
2.37.0.170.g444d1eabd0-goog


2022-07-15 19:38:30

by Peter Gonda

[permalink] [raw]
Subject: [RFC V1 09/10] KVM: selftests: add library for creating/interacting with SEV guests

From: Michael Roth <[email protected]>

Add interfaces to allow tests to create/manage SEV guests. The
additional state associated with these guests is encapsulated in a new
struct sev_vm, which is a light wrapper around struct kvm_vm. These
VMs will use vm_set_memory_encryption() and vm_get_encrypted_phy_pages()
under the covers to configure and sync up with the core kvm_util
library on what should/shouldn't be treated as encrypted memory.

Signed-off-by: Michael Roth <[email protected]>
Signed-off-by: Peter Gonda <[email protected]>

---
tools/testing/selftests/kvm/Makefile | 1 +
.../selftests/kvm/include/x86_64/sev.h | 44 +++
tools/testing/selftests/kvm/lib/x86_64/sev.c | 251 ++++++++++++++++++
3 files changed, 296 insertions(+)
create mode 100644 tools/testing/selftests/kvm/include/x86_64/sev.h
create mode 100644 tools/testing/selftests/kvm/lib/x86_64/sev.c

diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 3d9f2a017389..f3f29a64e4b2 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -56,6 +56,7 @@ LIBKVM_x86_64 += lib/x86_64/processor.c
LIBKVM_x86_64 += lib/x86_64/svm.c
LIBKVM_x86_64 += lib/x86_64/ucall.c
LIBKVM_x86_64 += lib/x86_64/vmx.c
+LIBKVM_x86_64 += lib/x86_64/sev.c

LIBKVM_aarch64 += lib/aarch64/gic.c
LIBKVM_aarch64 += lib/aarch64/gic_v3.c
diff --git a/tools/testing/selftests/kvm/include/x86_64/sev.h b/tools/testing/selftests/kvm/include/x86_64/sev.h
new file mode 100644
index 000000000000..2f7f7c741b12
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/x86_64/sev.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Helpers used for SEV guests
+ *
+ * Copyright (C) 2021 Advanced Micro Devices
+ */
+#ifndef SELFTEST_KVM_SEV_H
+#define SELFTEST_KVM_SEV_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "kvm_util.h"
+
+/* Makefile might set this separately for user-overrides */
+#ifndef SEV_DEV_PATH
+#define SEV_DEV_PATH "/dev/sev"
+#endif
+
+#define SEV_FW_REQ_VER_MAJOR 0
+#define SEV_FW_REQ_VER_MINOR 17
+
+#define SEV_POLICY_NO_DBG (1UL << 0)
+#define SEV_POLICY_ES (1UL << 2)
+
+enum {
+ SEV_GSTATE_UNINIT = 0,
+ SEV_GSTATE_LUPDATE,
+ SEV_GSTATE_LSECRET,
+ SEV_GSTATE_RUNNING,
+};
+
+struct sev_vm;
+
+void kvm_sev_ioctl(struct sev_vm *sev, int cmd, void *data);
+struct kvm_vm *sev_get_vm(struct sev_vm *sev);
+uint8_t sev_get_enc_bit(struct sev_vm *sev);
+
+struct sev_vm *sev_vm_create(uint32_t policy, uint64_t npages);
+void sev_vm_free(struct sev_vm *sev);
+void sev_vm_launch(struct sev_vm *sev);
+void sev_vm_launch_measure(struct sev_vm *sev, uint8_t *measurement);
+void sev_vm_launch_finish(struct sev_vm *sev);
+
+#endif /* SELFTEST_KVM_SEV_H */
diff --git a/tools/testing/selftests/kvm/lib/x86_64/sev.c b/tools/testing/selftests/kvm/lib/x86_64/sev.c
new file mode 100644
index 000000000000..309467cd7501
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/x86_64/sev.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Helpers used for SEV guests
+ *
+ * Copyright (C) 2021 Advanced Micro Devices
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "kvm_util.h"
+#include "linux/psp-sev.h"
+#include "processor.h"
+#include "sev.h"
+
+#define PAGE_SHIFT 12
+
+struct sev_vm {
+ struct kvm_vm *vm;
+ int fd;
+ int enc_bit;
+ uint32_t sev_policy;
+};
+
+/* Common SEV helpers/accessors. */
+
+struct kvm_vm *sev_get_vm(struct sev_vm *sev)
+{
+ return sev->vm;
+}
+
+uint8_t sev_get_enc_bit(struct sev_vm *sev)
+{
+ return sev->enc_bit;
+}
+
+void sev_ioctl(int sev_fd, int cmd, void *data)
+{
+ int ret;
+ struct sev_issue_cmd arg;
+
+ arg.cmd = cmd;
+ arg.data = (unsigned long)data;
+ ret = ioctl(sev_fd, SEV_ISSUE_CMD, &arg);
+ TEST_ASSERT(ret == 0,
+ "SEV ioctl %d failed, error: %d, fw_error: %d",
+ cmd, ret, arg.error);
+}
+
+void kvm_sev_ioctl(struct sev_vm *sev, int cmd, void *data)
+{
+ struct kvm_sev_cmd arg = {0};
+ int ret;
+
+ arg.id = cmd;
+ arg.sev_fd = sev->fd;
+ arg.data = (__u64)data;
+
+ ret = ioctl(sev->vm->fd, KVM_MEMORY_ENCRYPT_OP, &arg);
+ TEST_ASSERT(ret == 0,
+ "SEV KVM ioctl %d failed, rc: %i errno: %i (%s), fw_error: %d",
+ cmd, ret, errno, strerror(errno), arg.error);
+}
+
+/* Local helpers. */
+
+static void
+sev_register_user_region(struct sev_vm *sev, void *hva, uint64_t size)
+{
+ struct kvm_enc_region range = {0};
+ int ret;
+
+ pr_debug("%s: hva: %p, size: %lu\n", __func__, hva, size);
+
+ range.addr = (__u64)hva;
+ range.size = size;
+
+ ret = ioctl(sev->vm->fd, KVM_MEMORY_ENCRYPT_REG_REGION, &range);
+ TEST_ASSERT(ret == 0, "failed to register user range, errno: %i\n", errno);
+}
+
+static void
+sev_encrypt_phy_range(struct sev_vm *sev, vm_paddr_t gpa, uint64_t size)
+{
+ struct kvm_sev_launch_update_data ksev_update_data = {0};
+
+ pr_debug("%s: addr: 0x%lx, size: %lu\n", __func__, gpa, size);
+
+ ksev_update_data.uaddr = (__u64)addr_gpa2hva(sev->vm, gpa);
+ ksev_update_data.len = size;
+
+ kvm_sev_ioctl(sev, KVM_SEV_LAUNCH_UPDATE_DATA, &ksev_update_data);
+}
+
+static void sev_encrypt(struct sev_vm *sev)
+{
+ const struct sparsebit *enc_phy_pages;
+ struct kvm_vm *vm = sev->vm;
+ sparsebit_idx_t pg = 0;
+ vm_paddr_t gpa_start;
+ uint64_t memory_size;
+
+ /* Only memslot 0 supported for now. */
+ enc_phy_pages = vm_get_encrypted_phy_pages(sev->vm, 0, &gpa_start, &memory_size);
+ TEST_ASSERT(enc_phy_pages, "Unable to retrieve encrypted pages bitmap");
+ while (pg < (memory_size / vm->page_size)) {
+ sparsebit_idx_t pg_cnt;
+
+ if (sparsebit_is_clear(enc_phy_pages, pg)) {
+ pg = sparsebit_next_set(enc_phy_pages, pg);
+ if (!pg)
+ break;
+ }
+
+ pg_cnt = sparsebit_next_clear(enc_phy_pages, pg) - pg;
+ if (pg_cnt <= 0)
+ pg_cnt = 1;
+
+ sev_encrypt_phy_range(sev,
+ gpa_start + pg * vm->page_size,
+ pg_cnt * vm->page_size);
+ pg += pg_cnt;
+ }
+
+ sev->vm->memcrypt.encrypted = true;
+}
+
+/* SEV VM implementation. */
+
+static struct sev_vm *sev_vm_alloc(struct kvm_vm *vm)
+{
+ struct sev_user_data_status sev_status = {0};
+ uint32_t eax, ebx, ecx, edx;
+ struct sev_vm *sev;
+ int sev_fd;
+
+ sev_fd = open(SEV_DEV_PATH, O_RDWR);
+ if (sev_fd < 0) {
+ pr_info("Failed to open SEV device, path: %s, error: %d, skipping test.\n",
+ SEV_DEV_PATH, sev_fd);
+ return NULL;
+ }
+
+ sev_ioctl(sev_fd, SEV_PLATFORM_STATUS, &sev_status);
+
+ if (!(sev_status.api_major > SEV_FW_REQ_VER_MAJOR ||
+ (sev_status.api_major == SEV_FW_REQ_VER_MAJOR &&
+ sev_status.api_minor >= SEV_FW_REQ_VER_MINOR))) {
+ pr_info("SEV FW version too old. Have API %d.%d (build: %d), need %d.%d, skipping test.\n",
+ sev_status.api_major, sev_status.api_minor, sev_status.build,
+ SEV_FW_REQ_VER_MAJOR, SEV_FW_REQ_VER_MINOR);
+ return NULL;
+ }
+
+ sev = calloc(1, sizeof(*sev));
+ sev->fd = sev_fd;
+ sev->vm = vm;
+
+ /* Get encryption bit via CPUID. */
+ eax = 0x8000001f;
+ ecx = 0;
+ cpuid(&eax, &ebx, &ecx, &edx);
+ sev->enc_bit = ebx & 0x3F;
+
+ return sev;
+}
+
+void sev_vm_free(struct sev_vm *sev)
+{
+ ucall_uninit(sev->vm);
+ kvm_vm_free(sev->vm);
+ close(sev->fd);
+ free(sev);
+}
+
+struct sev_vm *sev_vm_create(uint32_t policy, uint64_t npages)
+{
+ struct sev_vm *sev;
+ struct kvm_vm *vm;
+
+ /* Need to handle memslots after init, and after setting memcrypt. */
+ vm = vm_create_barebones();
+ sev = sev_vm_alloc(vm);
+ if (!sev)
+ return NULL;
+ sev->sev_policy = policy;
+
+ kvm_sev_ioctl(sev, KVM_SEV_INIT, NULL);
+
+ vm->vpages_mapped = sparsebit_alloc();
+ vm_set_memory_encryption(vm, true, true, sev->enc_bit);
+ pr_info("SEV cbit: %d\n", sev->enc_bit);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, npages, 0);
+ sev_register_user_region(sev, addr_gpa2hva(vm, 0),
+ npages * vm->page_size);
+
+ pr_info("SEV guest created, policy: 0x%x, size: %lu KB\n",
+ sev->sev_policy, npages * vm->page_size / 1024);
+
+ return sev;
+}
+
+void sev_vm_launch(struct sev_vm *sev)
+{
+ struct kvm_sev_launch_start ksev_launch_start = {0};
+ struct kvm_sev_guest_status ksev_status = {0};
+
+ /* Need to use ucall_shared for synchronization. */
+ //ucall_init_ops(sev_get_vm(sev), NULL, &ucall_ops_halt);
+
+ ksev_launch_start.policy = sev->sev_policy;
+ kvm_sev_ioctl(sev, KVM_SEV_LAUNCH_START, &ksev_launch_start);
+ kvm_sev_ioctl(sev, KVM_SEV_GUEST_STATUS, &ksev_status);
+ TEST_ASSERT(ksev_status.policy == sev->sev_policy, "Incorrect guest policy.");
+ TEST_ASSERT(ksev_status.state == SEV_GSTATE_LUPDATE,
+ "Unexpected guest state: %d", ksev_status.state);
+
+ ucall_init(sev->vm, NULL);
+
+ sev_encrypt(sev);
+}
+
+void sev_vm_launch_measure(struct sev_vm *sev, uint8_t *measurement)
+{
+ struct kvm_sev_launch_measure ksev_launch_measure = {0};
+ struct kvm_sev_guest_status ksev_guest_status = {0};
+
+ ksev_launch_measure.len = 256;
+ ksev_launch_measure.uaddr = (__u64)measurement;
+ kvm_sev_ioctl(sev, KVM_SEV_LAUNCH_MEASURE, &ksev_launch_measure);
+
+ /* Measurement causes a state transition, check that. */
+ kvm_sev_ioctl(sev, KVM_SEV_GUEST_STATUS, &ksev_guest_status);
+ TEST_ASSERT(ksev_guest_status.state == SEV_GSTATE_LSECRET,
+ "Unexpected guest state: %d", ksev_guest_status.state);
+}
+
+void sev_vm_launch_finish(struct sev_vm *sev)
+{
+ struct kvm_sev_guest_status ksev_status = {0};
+
+ kvm_sev_ioctl(sev, KVM_SEV_GUEST_STATUS, &ksev_status);
+ TEST_ASSERT(ksev_status.state == SEV_GSTATE_LUPDATE ||
+ ksev_status.state == SEV_GSTATE_LSECRET,
+ "Unexpected guest state: %d", ksev_status.state);
+
+ kvm_sev_ioctl(sev, KVM_SEV_LAUNCH_FINISH, NULL);
+
+ kvm_sev_ioctl(sev, KVM_SEV_GUEST_STATUS, &ksev_status);
+ TEST_ASSERT(ksev_status.state == SEV_GSTATE_RUNNING,
+ "Unexpected guest state: %d", ksev_status.state);
+}
--
2.37.0.170.g444d1eabd0-goog

2022-07-15 19:47:02

by Peter Gonda

[permalink] [raw]
Subject: [RFC V1 10/10] KVM: selftests: Add simple sev vm testing

From: Michael Roth <[email protected]>

A very simple of booting SEV guests that checks related CPUID bits. This
is a stripped down version of "[PATCH v2 08/13] KVM: selftests: add SEV
boot tests" from Michael but much simpler.

Signed-off-by: Michael Roth <[email protected]>
Signed-off-by: Peter Gonda <[email protected]>

---
tools/testing/selftests/kvm/.gitignore | 1 +
tools/testing/selftests/kvm/Makefile | 1 +
.../selftests/kvm/x86_64/sev_all_boot_test.c | 134 ++++++++++++++++++
3 files changed, 136 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86_64/sev_all_boot_test.c

diff --git a/tools/testing/selftests/kvm/.gitignore b/tools/testing/selftests/kvm/.gitignore
index f44ebf401310..ec084a61a819 100644
--- a/tools/testing/selftests/kvm/.gitignore
+++ b/tools/testing/selftests/kvm/.gitignore
@@ -34,6 +34,7 @@
/x86_64/pmu_event_filter_test
/x86_64/set_boot_cpu_id
/x86_64/set_sregs_test
+/x86_64/sev_all_boot_test
/x86_64/sev_migrate_tests
/x86_64/smm_test
/x86_64/state_test
diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index f3f29a64e4b2..2b89e6bcb5b0 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -123,6 +123,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/tsc_msrs_test
TEST_GEN_PROGS_x86_64 += x86_64/vmx_pmu_caps_test
TEST_GEN_PROGS_x86_64 += x86_64/xen_shinfo_test
TEST_GEN_PROGS_x86_64 += x86_64/xen_vmcall_test
+TEST_GEN_PROGS_x86_64 += x86_64/sev_all_boot_test
TEST_GEN_PROGS_x86_64 += x86_64/sev_migrate_tests
TEST_GEN_PROGS_x86_64 += x86_64/amx_test
TEST_GEN_PROGS_x86_64 += x86_64/max_vcpuid_cap_test
diff --git a/tools/testing/selftests/kvm/x86_64/sev_all_boot_test.c b/tools/testing/selftests/kvm/x86_64/sev_all_boot_test.c
new file mode 100644
index 000000000000..222ce41d6f68
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86_64/sev_all_boot_test.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Basic SEV boot tests.
+ *
+ * Copyright (C) 2021 Advanced Micro Devices
+ */
+#define _GNU_SOURCE /* for program_invocation_short_name */
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "test_util.h"
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+#include "linux/psp-sev.h"
+#include "sev.h"
+
+#define VCPU_ID 2
+#define PAGE_STRIDE 32
+
+#define SHARED_PAGES 8192
+#define SHARED_VADDR_MIN 0x1000000
+
+#define PRIVATE_PAGES 2048
+#define PRIVATE_VADDR_MIN (SHARED_VADDR_MIN + SHARED_PAGES * PAGE_SIZE)
+
+#define TOTAL_PAGES (512 + SHARED_PAGES + PRIVATE_PAGES)
+
+#define NR_SYNCS 1
+
+static void guest_run_loop(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ int i;
+
+ for (i = 0; i <= NR_SYNCS; ++i) {
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ continue;
+ case UCALL_DONE:
+ return;
+ case UCALL_ABORT:
+ TEST_ASSERT(false, "%s at %s:%ld\n\tvalues: %#lx, %#lx",
+ (const char *)uc.args[0], __FILE__,
+ uc.args[1], uc.args[2], uc.args[3]);
+ default:
+ TEST_ASSERT(
+ false, "Unexpected exit: %s",
+ exit_reason_str(vcpu->run->exit_reason));
+ }
+ }
+}
+
+static void __attribute__((__flatten__))
+guest_sev_code(void)
+{
+ uint32_t eax, ebx, ecx, edx;
+ uint64_t sev_status;
+
+ GUEST_SYNC(1);
+
+ eax = 0x8000001f;
+ ecx = 0;
+ cpuid(&eax, &ebx, &ecx, &edx);
+ GUEST_ASSERT(eax & (1 << 1));
+
+ sev_status = rdmsr(MSR_AMD64_SEV);
+ GUEST_ASSERT((sev_status & 0x1) == 1);
+
+ GUEST_DONE();
+}
+
+static struct sev_vm *
+setup_test_common(void *guest_code, uint64_t policy, struct kvm_vcpu **vcpu)
+{
+ uint8_t measurement[512];
+ struct sev_vm *sev;
+ struct kvm_vm *vm;
+ int i;
+
+ sev = sev_vm_create(policy, TOTAL_PAGES);
+ if (!sev)
+ return NULL;
+ vm = sev_get_vm(sev);
+
+ /* Set up VCPU and initial guest kernel. */
+ *vcpu = vm_vcpu_add(vm, VCPU_ID, guest_code);
+ kvm_vm_elf_load(vm, program_invocation_name);
+
+ /* Allocations/setup done. Encrypt initial guest payload. */
+ sev_vm_launch(sev);
+
+ /* Dump the initial measurement. A test to actually verify it would be nice. */
+ sev_vm_launch_measure(sev, measurement);
+ pr_info("guest measurement: ");
+ for (i = 0; i < 32; ++i)
+ pr_info("%02x", measurement[i]);
+ pr_info("\n");
+
+ sev_vm_launch_finish(sev);
+
+ return sev;
+}
+
+static void test_sev(void *guest_code, uint64_t policy)
+{
+ struct sev_vm *sev;
+ struct kvm_vcpu *vcpu;
+
+ sev = setup_test_common(guest_code, policy, &vcpu);
+ if (!sev)
+ return;
+
+ /* Guest is ready to run. Do the tests. */
+ guest_run_loop(vcpu);
+
+ pr_info("guest ran successfully\n");
+
+ sev_vm_free(sev);
+}
+
+int main(int argc, char *argv[])
+{
+ /* SEV tests */
+ test_sev(guest_sev_code, SEV_POLICY_NO_DBG);
+ test_sev(guest_sev_code, 0);
+
+ return 0;
+}
--
2.37.0.170.g444d1eabd0-goog