[PATCH Part1 RFC v4 08/36] x86/sev: check the vmpl level

From: Brijesh Singh
Date: Wed Jul 07 2021 - 14:16:05 EST


Virtual Machine Privilege Level (VMPL) is an optional feature in the
SEV-SNP architecture, which allows a guest VM to divide its address space
into four levels. The level can be used to provide the hardware isolated
abstraction layers with a VM. The VMPL0 is the highest privilege, and
VMPL3 is the least privilege. Certain operations must be done by the VMPL0
software, such as:

* Validate or invalidate memory range (PVALIDATE instruction)
* Allocate VMSA page (RMPADJUST instruction when VMSA=1)

The initial SEV-SNP support assumes that the guest kernel is running on
VMPL0. Let's add a check to make sure that kernel is running at VMPL0
before continuing the boot. There is no easy method to query the current
VMPL level, so use the RMPADJUST instruction to determine whether its
booted at the VMPL0.

Signed-off-by: Brijesh Singh <brijesh.singh@xxxxxxx>
---
arch/x86/boot/compressed/sev.c | 41 ++++++++++++++++++++++++++++---
arch/x86/include/asm/sev-common.h | 1 +
arch/x86/include/asm/sev.h | 3 +++
3 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/arch/x86/boot/compressed/sev.c b/arch/x86/boot/compressed/sev.c
index 7be325d9b09f..2f3081e9c78c 100644
--- a/arch/x86/boot/compressed/sev.c
+++ b/arch/x86/boot/compressed/sev.c
@@ -134,6 +134,36 @@ static inline bool sev_snp_enabled(void)
return msr_sev_status & MSR_AMD64_SEV_SNP_ENABLED;
}

+static bool is_vmpl0(void)
+{
+ u64 attrs, va;
+ int err;
+
+ /*
+ * There is no straightforward way to query the current VMPL level. The
+ * simplest method is to use the RMPADJUST instruction to change a page
+ * permission to a VMPL level-1, and if the guest kernel is launched at
+ * at a level <= 1, then RMPADJUST instruction will return an error.
+ */
+ attrs = 1;
+
+ /*
+ * Any page aligned virtual address is sufficent to test the VMPL level.
+ * The boot_ghcb_page is page aligned memory, so lets use for the test.
+ */
+ va = (u64)&boot_ghcb_page;
+
+ /* Instruction mnemonic supported in binutils versions v2.36 and later */
+ asm volatile (".byte 0xf3,0x0f,0x01,0xfe\n\t"
+ : "=a" (err)
+ : "a" (va), "c" (RMP_PG_SIZE_4K), "d" (attrs)
+ : "memory", "cc");
+ if (err)
+ return false;
+
+ return true;
+}
+
static bool do_early_sev_setup(void)
{
if (!sev_es_negotiate_protocol())
@@ -141,10 +171,15 @@ static bool do_early_sev_setup(void)

/*
* If SEV-SNP is enabled, then check if the hypervisor supports the SEV-SNP
- * features.
+ * features and is launched at VMPL-0 level.
*/
- if (sev_snp_enabled() && !(sev_hv_features & GHCB_HV_FT_SNP))
- sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SNP_UNSUPPORTED);
+ if (sev_snp_enabled()) {
+ if (!sev_hv_features & GHCB_HV_FT_SNP)
+ sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SNP_UNSUPPORTED);
+
+ if (!is_vmpl0())
+ sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_NOT_VMPL0);
+ }

if (set_page_decrypted((unsigned long)&boot_ghcb_page))
return false;
diff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h
index 8abc5eb7ca3d..ea508835ab33 100644
--- a/arch/x86/include/asm/sev-common.h
+++ b/arch/x86/include/asm/sev-common.h
@@ -78,5 +78,6 @@
#define GHCB_TERM_REGISTER 0 /* GHCB GPA registration failure */
#define GHCB_TERM_PSC 1 /* Page State Change failure */
#define GHCB_TERM_PVALIDATE 2 /* Pvalidate failure */
+#define GHCB_TERM_NOT_VMPL0 3 /* SNP guest is not running at VMPL-0 */

#endif
diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
index b308815a2c01..242af1154e49 100644
--- a/arch/x86/include/asm/sev.h
+++ b/arch/x86/include/asm/sev.h
@@ -62,6 +62,9 @@ extern bool handle_vc_boot_ghcb(struct pt_regs *regs);
/* Software defined (when rFlags.CF = 1) */
#define PVALIDATE_FAIL_NOUPDATE 255

+/* RMP page size */
+#define RMP_PG_SIZE_4K 0
+
#ifdef CONFIG_AMD_MEM_ENCRYPT
extern struct static_key_false sev_es_enable_key;
extern void __sev_es_ist_enter(struct pt_regs *regs);
--
2.17.1