[kvm-unit-tests RFC PATCH 08/13] x86 AMD SEV-SNP: Test Private->Shared Page state changes using GHCB MSR

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



As mentioned in the GHCB spec (Section 2.3.1 GHCB MSR protocol), the
SEV-SNP guest VM issues 4K page state change requests to the hypervisor
to convert KUT's newly allocated private pages to shared pages using
GHCB MSR protcol.

The purpose of this test is to determine whether the hypervisor changes
the page state to shared. Before the conversion test, ensure the state
of the pages are in an expected state (i.e., private) by issuing a
re-validation on one of the newly allocated page to determine the
expected state of the page matches with the page's current state.
Report failure if the expected page state is not private.

Import GHCB MSR PSC related definitions from upstream linux
(arch/x86/include/asm/sev-common.h and arch/x86/include/asm/sev.h)

Signed-off-by: Pavan Kumar Paluri <papaluri@xxxxxxx>
---
 lib/x86/amd_sev.h |  51 ++++++++++
 x86/amd_sev.c     | 241 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 292 insertions(+)

diff --git a/lib/x86/amd_sev.h b/lib/x86/amd_sev.h
index 70f3763fe231..b648fb0e7873 100644
--- a/lib/x86/amd_sev.h
+++ b/lib/x86/amd_sev.h
@@ -84,6 +84,15 @@ struct ghcb {
 
 #define	VMGEXIT()			{ asm volatile("rep; vmmcall\n\r"); }
 
+/* PVALIDATE return codes */
+#define PVALIDATE_FAIL_SIZEMISMATCH	6
+
+/* Software defined (when rFlags.CF = 1) */
+#define PVALIDATE_FAIL_NOUPDATE		255
+
+/* RMP page size */
+#define RMP_PG_SIZE_4K			0
+
 enum es_result {
 	ES_OK,			/* All good */
 	ES_UNSUPPORTED,		/* Requested operation not supported */
@@ -157,6 +166,36 @@ efi_status_t setup_amd_sev(void);
  */
 #define SEV_ES_GHCB_MSR_INDEX 0xc0010130
 
+#define GHCB_DATA_LOW		12
+#define GHCB_MSR_INFO_MASK	(BIT_ULL(GHCB_DATA_LOW) - 1)
+#define GHCB_RESP_CODE(v)	((v) & GHCB_MSR_INFO_MASK)
+
+/*
+ * SNP Page State Change Operation
+ *
+ * GHCBData[55:52] - Page operation:
+ *	0x0001	Page assignment, Private
+ *	0x0002	Page assignment, Shared
+ */
+enum psc_op {
+	SNP_PAGE_STATE_PRIVATE = 1,
+	SNP_PAGE_STATE_SHARED,
+};
+
+#define GHCB_MSR_PSC_REQ	0x14
+#define GHCB_MSR_PSC_REQ_GFN(gfn, op)				\
+	/* GHCBData[55:52] */					\
+	(((u64)((op) & 0xf) << 52) |				\
+	/* GHCBData[51:12] */					\
+	((u64)((gfn) & GENMASK_ULL(39, 0)) << 12) |		\
+	/* GHCBData[11:0] */					\
+	GHCB_MSR_PSC_REQ)
+
+#define GHCB_MSR_PSC_RESP	0x15
+#define GHCB_MSR_PSC_RESP_VAL(val)				\
+	/* GHCBData[63:32] */					\
+	(((u64)(val) & GENMASK_ULL(63, 32)) >> 32)
+
 bool amd_sev_es_enabled(void);
 efi_status_t setup_vc_handler(void);
 bool amd_sev_snp_enabled(void);
@@ -166,6 +205,18 @@ void handle_sev_es_vc(struct ex_regs *regs);
 unsigned long long get_amd_sev_c_bit_mask(void);
 unsigned long long get_amd_sev_addr_upperbound(void);
 
+/*
+ * Macros to generate condition code outputs from inline assembly,
+ * The output operand must be type "bool".
+ */
+#ifdef __GCC_ASM_FLAG_OUTPUTS__
+# define CC_SET(c) "\n\t/* output condition code " #c "*/\n"
+# define CC_OUT(c) "=@cc" #c
+#else
+# define CC_SET(c) "\n\tset" #c " %[_cc_" #c "]\n"
+# define CC_OUT(c)[_cc_ ## c] "=qm"
+#endif
+
 /* GHCB Accessor functions from Linux's include/asm/svm.h */
 #define GHCB_BITMAP_IDX(field)							\
 	(offsetof(struct ghcb_save_area, field) / sizeof(u64))
diff --git a/x86/amd_sev.c b/x86/amd_sev.c
index 23f6e3490546..71d1ee1cef91 100644
--- a/x86/amd_sev.c
+++ b/x86/amd_sev.c
@@ -14,11 +14,14 @@
 #include "x86/processor.h"
 #include "x86/amd_sev.h"
 #include "msr.h"
+#include "x86/vm.h"
+#include "alloc_page.h"
 
 #define EXIT_SUCCESS 0
 #define EXIT_FAILURE 1
 
 #define TESTDEV_IO_PORT 0xe0
+#define SNP_PSC_ALLOC_ORDER 10
 
 static char st1[] = "abcdefghijklmnop";
 
@@ -94,6 +97,140 @@ static efi_status_t find_cc_blob_efi(void)
 	return EFI_SUCCESS;
 }
 
+static inline int pvalidate(u64 vaddr, bool rmp_size,
+			    bool validate)
+{
+	bool rmp_unchanged;
+	int result;
+
+	asm volatile(".byte 0xF2, 0x0F, 0x01, 0xFF\n\t"
+		     CC_SET(c)
+		     : CC_OUT(c) (rmp_unchanged), "=a" (result)
+		     : "a" (vaddr), "c" (rmp_size), "d" (validate)
+		     : "memory", "cc");
+
+	if (rmp_unchanged)
+		return PVALIDATE_FAIL_NOUPDATE;
+
+	return result;
+}
+
+static efi_status_t __sev_set_pages_state_msr_proto(unsigned long vaddr, int npages,
+						    int operation)
+{
+	unsigned long vaddr_end = vaddr + (npages * PAGE_SIZE);
+	unsigned long paddr;
+	int ret;
+	u64 val;
+
+	/*
+	 * We are re-using GHCB MSR value setup by OVMF, so save and
+	 * restore it after PSCs.
+	 */
+	phys_addr_t ghcb_old_msr = rdmsr(SEV_ES_GHCB_MSR_INDEX);
+
+	while (vaddr < vaddr_end) {
+		/*
+		 * Although identity mapped, compute GPA to use guest
+		 * physical frame number (GFN) while requesting an
+		 * explicit page state change.
+		 */
+		paddr = __pa(vaddr);
+
+		if (operation == SNP_PAGE_STATE_SHARED) {
+			/* Page invalidation happens before changing to shared */
+			ret = pvalidate(vaddr, RMP_PG_SIZE_4K, false);
+			if (ret) {
+				printf("Failed to invalidate vaddr: 0x%lx, ret: %d\n",
+				       vaddr, ret);
+				return ES_UNSUPPORTED;
+			}
+		}
+
+		wrmsr(SEV_ES_GHCB_MSR_INDEX,
+		      GHCB_MSR_PSC_REQ_GFN(paddr >> PAGE_SHIFT, operation));
+
+		VMGEXIT();
+
+		val = rdmsr(SEV_ES_GHCB_MSR_INDEX);
+
+		if (GHCB_RESP_CODE(val) != GHCB_MSR_PSC_RESP) {
+			printf("Wrong PSC response code: 0x%x\n",
+			       (unsigned int)GHCB_RESP_CODE(val));
+			return ES_VMM_ERROR;
+		}
+
+		if (GHCB_MSR_PSC_RESP_VAL(val)) {
+			printf("Failed to change page state to %s paddr: 0x%lx error: 0x%llx\n",
+			       operation == SNP_PAGE_STATE_PRIVATE ? "private"
+								   : "shared",
+			       paddr, GHCB_MSR_PSC_RESP_VAL(val));
+			return ES_VMM_ERROR;
+		}
+
+		if (operation == SNP_PAGE_STATE_PRIVATE) {
+			ret = pvalidate(vaddr, RMP_PG_SIZE_4K, true);
+			if (ret) {
+				printf("Failed to validate vaddr: 0x%lx, ret: %d\n",
+				       vaddr, ret);
+				return ES_UNSUPPORTED;
+			}
+		}
+
+		vaddr += PAGE_SIZE;
+	}
+
+	/* Restore old GHCB MSR - setup by OVMF */
+	wrmsr(SEV_ES_GHCB_MSR_INDEX, ghcb_old_msr);
+
+	return ES_OK;
+}
+
+static void set_pte_decrypted(unsigned long vaddr, int npages)
+{
+	pteval_t *pte;
+	unsigned long vaddr_end = vaddr + (npages * PAGE_SIZE);
+
+	while (vaddr < vaddr_end) {
+		pte = get_pte((pgd_t *)read_cr3(), (void *)vaddr);
+
+		if (!pte)
+			assert_msg(pte, "No pte found for vaddr 0x%lx", vaddr);
+
+		/* unset c-bit */
+		*pte &= ~(get_amd_sev_c_bit_mask());
+
+		vaddr += PAGE_SIZE;
+	}
+
+	flush_tlb();
+}
+
+static efi_status_t sev_set_pages_state_msr_proto(unsigned long vaddr, int npages,
+						  int operation)
+{
+	efi_status_t status;
+
+	vaddr = vaddr & PAGE_MASK;
+
+	/*
+	 * If the encryption bit is to be cleared, change the page state
+	 * in the RMP table.
+	 */
+	if (operation == SNP_PAGE_STATE_SHARED) {
+		status = __sev_set_pages_state_msr_proto(vaddr, npages,
+							 operation);
+		if (status != ES_OK) {
+			printf("Page state change (Private->Shared) failure.\n");
+			return status;
+		}
+
+		set_pte_decrypted(vaddr, npages);
+	}
+
+	return ES_OK;
+}
+
 static void test_sev_snp_activation(void)
 {
 	efi_status_t status;
@@ -109,6 +246,51 @@ static void test_sev_snp_activation(void)
 	report(status == EFI_SUCCESS, "SEV-SNP CC-blob presence");
 }
 
+/*
+ * Perform page revalidation to ensure page is in the expected private
+ * state. We can confirm this test to succeed when the pvalidate fails
+ * with a return code of PVALIDATE_FAIL_NOUPDATE.
+ */
+static bool is_validated_private_page(unsigned long vaddr, bool rmp_size,
+				      bool state)
+{
+	int ret;
+
+	/* Attempt a pvalidate here for the provided page size */
+	ret = pvalidate(vaddr, rmp_size, state);
+	if (ret == PVALIDATE_FAIL_NOUPDATE)
+		return true;
+
+	/*
+	 * If PVALIDATE_FAIL_SIZEMISMATCH, Entry in the RMP is a 4K
+	 * entry, and what guest is providing is a 2M entry. Therefore,
+	 * fallback to pvalidating 4K entries within 2M range.
+	 */
+	if (rmp_size && ret == PVALIDATE_FAIL_SIZEMISMATCH) {
+		unsigned long vaddr_end = vaddr + LARGE_PAGE_SIZE;
+
+		for (; vaddr < vaddr_end; vaddr += PAGE_SIZE) {
+			ret = pvalidate(vaddr, RMP_PG_SIZE_4K, state);
+			if (ret != PVALIDATE_FAIL_NOUPDATE)
+				return false;
+		}
+	}
+
+	return ret == PVALIDATE_FAIL_NOUPDATE ? true : false;
+}
+
+static int test_write(unsigned long vaddr, int npages)
+{
+	unsigned long vaddr_end = vaddr + (npages << PAGE_SHIFT);
+
+	while (vaddr < vaddr_end) {
+		memcpy((void *)vaddr, st1, strnlen(st1, PAGE_SIZE));
+		vaddr += PAGE_SIZE;
+	}
+
+	return 0;
+}
+
 static void test_stringio(void)
 {
 	int st1_len = sizeof(st1) - 1;
@@ -126,6 +308,60 @@ static void test_stringio(void)
 	report((got & 0xff00) >> 8 == st1[sizeof(st1) - 2], "outsb up");
 }
 
+static void test_sev_psc_ghcb_msr(void)
+{
+	pteval_t *pte;
+	unsigned long *vaddr;
+	efi_status_t status;
+
+	vaddr = alloc_pages(SNP_PSC_ALLOC_ORDER);
+	if (!vaddr)
+		assert_msg(vaddr, "Page allocation failure at addr: %p", vaddr);
+
+	/*
+	 * Page state changes using GHCB MSR protocol can only happen on
+	 * 4K pages.
+	 */
+	force_4k_page(vaddr);
+
+	/* Use this pte to check the C-bit */
+	pte = get_pte_level((pgd_t *)read_cr3(), (void *)vaddr, 1);
+	if (!pte) {
+		assert_msg(pte, "No pte found for vaddr %p", vaddr);
+		return;
+	}
+
+	if (*pte & get_amd_sev_c_bit_mask()) {
+		/*
+		 * Before performing private->shared test, ensure the
+		 * page is in private and in a validated state.
+		 */
+		report(is_validated_private_page((unsigned long)vaddr,
+						 RMP_PG_SIZE_4K, true),
+		       "Expected page state: Private");
+
+		report_info("Private->Shared conversion test using GHCB MSR");
+
+		/* Perform Private->Shared page state change */
+		status = sev_set_pages_state_msr_proto((unsigned long)vaddr,
+						       1 << SNP_PSC_ALLOC_ORDER,
+						       SNP_PAGE_STATE_SHARED);
+
+		report(status == ES_OK, "Private->Shared Page State Change");
+
+		/*
+		 * Access the now-shared page(s) with C-bit cleared and
+		 * ensure read/writes return expected data.
+		 */
+		report(!test_write((unsigned long)vaddr, 1 << SNP_PSC_ALLOC_ORDER),
+		       "Write to %d unencrypted pages after private->shared conversion",
+		       1 << SNP_PSC_ALLOC_ORDER);
+	}
+
+	/* Cleanup */
+	free_pages_by_order(vaddr, SNP_PSC_ALLOC_ORDER);
+}
+
 int main(void)
 {
 	int rtn;
@@ -134,5 +370,10 @@ int main(void)
 	test_sev_es_activation();
 	test_sev_snp_activation();
 	test_stringio();
+	setup_vm();
+
+	if (amd_sev_snp_enabled())
+		test_sev_psc_ghcb_msr();
+
 	return report_summary();
 }
-- 
2.34.1





[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux