[PATCH v6 33/42] boot/compressed/64: use firmware-validated CPUID for SEV-SNP guests

Brijesh Singh
2021-10-08T18:06:20Z

From: Michael Roth <michael.roth@amd.com>

SEV-SNP guests will be provided the location of special 'secrets' and
'CPUID' pages via the Confidential Computing blob. This blob is
provided to the boot kernel either through an EFI config table entry,
or via a setup_data structure as defined by the Linux Boot Protocol.

Locate the Confidential Computing from these sources and, if found,
use the provided CPUID page/table address to create a copy that the
boot kernel will use when servicing cpuid instructions via a #VC
handler.

Signed-off-by: Michael Roth <michael.roth@amd.com>
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
arch/x86/boot/compressed/head_64.S | 1 +
arch/x86/boot/compressed/idt_64.c | 5 +-
arch/x86/boot/compressed/misc.h | 2 +
arch/x86/boot/compressed/sev.c | 79 ++++++++++++++++++++++++++++++
arch/x86/include/asm/sev.h | 14 ++++++
arch/x86/kernel/sev-shared.c | 78 +++++++++++++++++++++++++++++
6 files changed, 178 insertions(+), 1 deletion(-)

diff --git a/arch/x86/boot/compressed/head_64.S b/arch/x86/boot/compressed/head_64.S
index 572c535cf45b..c9252f0b0e81 100644
--- a/arch/x86/boot/compressed/head_64.S
+++ b/arch/x86/boot/compressed/head_64.S
@@ -444,6 +444,7 @@ SYM_CODE_START(startup_64)
.Lon_kernel_cs:

pushq %rsi
+ movq %rsi, %rdi /* real mode address */
call load_stage1_idt
popq %rsi

diff --git a/arch/x86/boot/compressed/idt_64.c b/arch/x86/boot/compressed/idt_64.c
index 9b93567d663a..3c0f7c8d9152 100644
--- a/arch/x86/boot/compressed/idt_64.c
+++ b/arch/x86/boot/compressed/idt_64.c
@@ -28,7 +28,7 @@ static void load_boot_idt(const struct desc_ptr *dtr)
}

/* Setup IDT before kernel jumping to .Lrelocated */
-void load_stage1_idt(void)
+void load_stage1_idt(void *rmode)
{
boot_idt_desc.address = (unsigned long)boot_idt;

@@ -37,6 +37,9 @@ void load_stage1_idt(void)
set_idt_entry(X86_TRAP_VC, boot_stage1_vc);

load_boot_idt(&boot_idt_desc);
+
+ if (IS_ENABLED(CONFIG_AMD_MEM_ENCRYPT))
+ snp_cpuid_init_boot(rmode);
}

/* Setup IDT after kernel jumping to .Lrelocated */
diff --git a/arch/x86/boot/compressed/misc.h b/arch/x86/boot/compressed/misc.h
index d4a26f3d3580..9b66a8bf336e 100644
--- a/arch/x86/boot/compressed/misc.h
+++ b/arch/x86/boot/compressed/misc.h
@@ -124,6 +124,7 @@ void sev_es_shutdown_ghcb(void);
extern bool sev_es_check_ghcb_fault(unsigned long address);
void snp_set_page_private(unsigned long paddr);
void snp_set_page_shared(unsigned long paddr);
+void snp_cpuid_init_boot(struct boot_params *bp);

#else
static inline void sev_es_shutdown_ghcb(void) { }
@@ -133,6 +134,7 @@ static inline bool sev_es_check_ghcb_fault(unsigned long address)
}
static inline void snp_set_page_private(unsigned long paddr) { }
static inline void snp_set_page_shared(unsigned long paddr) { }
+static inline void snp_cpuid_init_boot(struct boot_params *bp) { }

#endif

diff --git a/arch/x86/boot/compressed/sev.c b/arch/x86/boot/compressed/sev.c
index 11c459809d4c..60885d80bf5f 100644
--- a/arch/x86/boot/compressed/sev.c
+++ b/arch/x86/boot/compressed/sev.c
@@ -297,3 +297,82 @@ void do_boot_stage2_vc(struct pt_regs *regs, unsigned long exit_code)
else if (result != ES_RETRY)
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
}
+
+/* Search for Confidential Computing blob in the EFI config table. */
+static struct cc_blob_sev_info *snp_find_cc_blob_efi(struct boot_params *bp)
+{
+ struct cc_blob_sev_info *cc_info;
+ unsigned long conf_table_pa;
+ unsigned int conf_table_len;
+ bool efi_64;
+ int ret;
+
+ ret = efi_get_conf_table(bp, &conf_table_pa, &conf_table_len, &efi_64);
+ if (ret)
+ return NULL;
+
+ ret = efi_find_vendor_table(conf_table_pa, conf_table_len,
+ EFI_CC_BLOB_GUID, efi_64,
+ (unsigned long *)&cc_info);
+ if (ret)
+ return NULL;
+
+ return cc_info;
+}
+
+/*
+ * Initial set up of SEV-SNP CPUID table relies on information provided
+ * by the Confidential Computing blob, which can be passed to the boot kernel
+ * by firmware/bootloader in the following ways:
+ *
+ * - via an entry in the EFI config table
+ * - via a setup_data structure, as defined by the Linux Boot Protocol
+ *
+ * Scan for the blob in that order.
+ */
+struct cc_blob_sev_info *snp_find_cc_blob(struct boot_params *bp)
+{
+ struct cc_blob_sev_info *cc_info;
+
+ cc_info = snp_find_cc_blob_efi(bp);
+ if (cc_info)
+ goto found_cc_info;
+
+ cc_info = snp_find_cc_blob_setup_data(bp);
+ if (!cc_info)
+ return NULL;
+
+found_cc_info:
+ if (cc_info->magic != CC_BLOB_SEV_HDR_MAGIC)
+ sev_es_terminate(0, GHCB_SNP_UNSUPPORTED);
+
+ return cc_info;
+}
+
+void snp_cpuid_init_boot(struct boot_params *bp)
+{
+ struct cc_blob_sev_info *cc_info;
+ u32 eax;
+
+ if (!bp)
+ return;
+
+ cc_info = snp_find_cc_blob(bp);
+ if (!cc_info)
+ return;
+
+ snp_cpuid_info_create(cc_info);
+
+ /* SEV-SNP CPUID table is set up now. Do some sanity checks. */
+ if (!snp_cpuid_active())
+ sev_es_terminate(1, GHCB_TERM_CPUID);
+
+ /* CPUID bits for SEV (bit 1) and SEV-SNP (bit 4) should be enabled. */
+ eax = native_cpuid_eax(0x8000001f);
+ if (!(eax & (BIT(4) | BIT(1))))
+ sev_es_terminate(1, GHCB_TERM_CPUID);
+
+ /* It should be safe to read SEV MSR and check features now. */
+ if (!sev_snp_enabled())
+ sev_es_terminate(1, GHCB_TERM_CPUID);
+}
diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
index 534fa1c4c881..7c88762cdb23 100644
--- a/arch/x86/include/asm/sev.h
+++ b/arch/x86/include/asm/sev.h
@@ -11,6 +11,7 @@
#include <linux/types.h>
#include <asm/insn.h>
#include <asm/sev-common.h>
+#include <asm/bootparam.h>

#define GHCB_PROTOCOL_MIN 1ULL
#define GHCB_PROTOCOL_MAX 2ULL
@@ -126,6 +127,17 @@ void __init snp_prep_memory(unsigned long paddr, unsigned int sz, enum psc_op op
void snp_set_memory_shared(unsigned long vaddr, unsigned int npages);
void snp_set_memory_private(unsigned long vaddr, unsigned int npages);
void snp_set_wakeup_secondary_cpu(void);
+/*
+ * TODO: These are exported only temporarily while boot/compressed/sev.c is
+ * the only user. This is to avoid unused function warnings for kernel/sev.c
+ * during the build of kernel proper.
+ *
+ * Once the code is added to consume these in kernel proper these functions
+ * can be moved back to being statically-scoped to units that pull in
+ * sev-shared.c via #include and these declarations can be dropped.
+ */
+void __init snp_cpuid_info_create(const struct cc_blob_sev_info *cc_info);
+struct cc_blob_sev_info *snp_find_cc_blob_setup_data(struct boot_params *bp);
#else
static inline void sev_es_ist_enter(struct pt_regs *regs) { }
static inline void sev_es_ist_exit(void) { }
@@ -141,6 +153,8 @@ static inline void __init snp_prep_memory(unsigned long paddr, unsigned int sz,
static inline void snp_set_memory_shared(unsigned long vaddr, unsigned int npages) { }
static inline void snp_set_memory_private(unsigned long vaddr, unsigned int npages) { }
static inline void snp_set_wakeup_secondary_cpu(void) { }
+void snp_cpuid_info_create(const struct cc_blob_sev_info *cc_info) { }
+struct cc_blob_sev_info *snp_find_cc_blob_setup_data(struct boot_params *bp) { }
#endif

#endif
diff --git a/arch/x86/kernel/sev-shared.c b/arch/x86/kernel/sev-shared.c
index 193ca49a1689..b321c1b7d07c 100644
--- a/arch/x86/kernel/sev-shared.c
+++ b/arch/x86/kernel/sev-shared.c
@@ -66,6 +66,9 @@ static u64 __ro_after_init sev_hv_features;
* and regenerate the CPUID table/pointer when .bss is cleared.
*/

+/* Copy of the SNP firmware's CPUID page. */
+static struct snp_cpuid_info cpuid_info_copy __ro_after_init;
+
/*
* The CPUID info can't always be referenced directly due to the need for
* pointer fixups during initial startup phase of kernel proper, so access must
@@ -390,6 +393,22 @@ snp_cpuid_find_validated_func(u32 func, u32 subfunc, u32 *eax, u32 *ebx,
return false;
}

+static void __init snp_cpuid_set_ranges(void)
+{
+ int i;
+
+ for (i = 0; i < cpuid_info->count; i++) {
+ const struct snp_cpuid_fn *fn = &cpuid_info->fn[i];
+
+ if (fn->eax_in == 0x0)
+ cpuid_std_range_max = fn->eax;
+ else if (fn->eax_in == 0x40000000)
+ cpuid_hyp_range_max = fn->eax;
+ else if (fn->eax_in == 0x80000000)
+ cpuid_ext_range_max = fn->eax;
+ }
+}
+
static bool snp_cpuid_check_range(u32 func)
{
if (func <= cpuid_std_range_max ||
@@ -934,3 +953,62 @@ static enum es_result vc_handle_rdtsc(struct ghcb *ghcb,

return ES_OK;
}
+
+struct cc_setup_data {
+ struct setup_data header;
+ u32 cc_blob_address;
+};
+
+static struct cc_setup_data *get_cc_setup_data(struct boot_params *bp)
+{
+ struct setup_data *hdr = (struct setup_data *)bp->hdr.setup_data;
+
+ while (hdr) {
+ if (hdr->type == SETUP_CC_BLOB)
+ return (struct cc_setup_data *)hdr;
+ hdr = (struct setup_data *)hdr->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Search for a Confidential Computing blob passed in as a setup_data entry
+ * via the Linux Boot Protocol.
+ */
+struct cc_blob_sev_info *
+snp_find_cc_blob_setup_data(struct boot_params *bp)
+{
+ struct cc_setup_data *sd;
+
+ sd = get_cc_setup_data(bp);
+ if (!sd)
+ return NULL;
+
+ return (struct cc_blob_sev_info *)(unsigned long)sd->cc_blob_address;
+}
+
+/*
+ * Initialize the kernel's copy of the SEV-SNP CPUID table, and set up the
+ * pointer that will be used to access it.
+ *
+ * Maintaining a direct mapping of the SEV-SNP CPUID table used by firmware
+ * would be possible as an alternative, but the approach is brittle since the
+ * mapping needs to be updated in sync with all the changes to virtual memory
+ * layout and related mapping facilities throughout the boot process.
+ */
+void __init snp_cpuid_info_create(const struct cc_blob_sev_info *cc_info)
+{
+ const struct snp_cpuid_info *cpuid_info_fw;
+
+ if (!cc_info || !cc_info->cpuid_phys || cc_info->cpuid_len < PAGE_SIZE)
+ sev_es_terminate(1, GHCB_TERM_CPUID);
+
+ cpuid_info_fw = (const struct snp_cpuid_info *)cc_info->cpuid_phys;
+ if (!cpuid_info_fw->count || cpuid_info_fw->count > SNP_CPUID_COUNT_MAX)
+ sev_es_terminate(1, GHCB_TERM_CPUID);
+
+ cpuid_info = &cpuid_info_copy;
+ memcpy((void *)cpuid_info, cpuid_info_fw, sizeof(*cpuid_info));
+ snp_cpuid_set_ranges();
+}
--
2.25.1