[PATCH v9 38/43] x86/sev: Use firmware-validated CPUID for SEV-SNP guests

Brijesh Singh
2022-01-28T17:21:25Z

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 run-time kernel either through a bootparams field that
was initialized by the boot/compressed kernel, or via a setup_data
structure as defined by the Linux Boot Protocol.

Locate the Confidential Computing blob from these sources and, if found,
use the provided CPUID page/table address to create a copy that the
run-time 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/sev.c | 37 -----------------
arch/x86/kernel/sev-shared.c | 37 +++++++++++++++++
arch/x86/kernel/sev.c | 75 ++++++++++++++++++++++++++++++++++
3 files changed, 112 insertions(+), 37 deletions(-)

diff --git a/arch/x86/boot/compressed/sev.c b/arch/x86/boot/compressed/sev.c
index cde5658e1007..21cdafac71e3 100644
--- a/arch/x86/boot/compressed/sev.c
+++ b/arch/x86/boot/compressed/sev.c
@@ -393,43 +393,6 @@ static struct cc_blob_sev_info *snp_find_cc_blob(struct boot_params *bp)
return cc_info;
}

-/*
- * 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.
- */
-static void snp_setup_cpuid_table(const struct cc_blob_sev_info *cc_info)
-{
- const struct snp_cpuid_info *cpuid_info_fw, *cpuid_info;
- int i;
-
- if (!cc_info || !cc_info->cpuid_phys || cc_info->cpuid_len < PAGE_SIZE)
- sev_es_terminate(SEV_TERM_SET_LINUX, 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(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID);
-
- cpuid_info = snp_cpuid_info_get_ptr();
- memcpy((void *)cpuid_info, cpuid_info_fw, sizeof(*cpuid_info));
-
- /* Initialize CPUID ranges for range-checking. */
- 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;
- }
-}
-
bool snp_init(struct boot_params *bp)
{
struct cc_blob_sev_info *cc_info;
diff --git a/arch/x86/kernel/sev-shared.c b/arch/x86/kernel/sev-shared.c
index 2bddbfea079b..795426ee6108 100644
--- a/arch/x86/kernel/sev-shared.c
+++ b/arch/x86/kernel/sev-shared.c
@@ -961,3 +961,40 @@ static struct cc_blob_sev_info *snp_find_cc_blob_setup_data(struct boot_params *

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.
+ */
+static void __init snp_setup_cpuid_table(const struct cc_blob_sev_info *cc_info)
+{
+ const struct snp_cpuid_info *cpuid_info_fw, *cpuid_info;
+ int i;
+
+ if (!cc_info || !cc_info->cpuid_phys || cc_info->cpuid_len < PAGE_SIZE)
+ sev_es_terminate(SEV_TERM_SET_LINUX, 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(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID);
+
+ cpuid_info = snp_cpuid_info_get_ptr();
+ memcpy((void *)cpuid_info, cpuid_info_fw, sizeof(*cpuid_info));
+
+ /* Initialize CPUID ranges for range-checking. */
+ 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;
+ }
+}
diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
index 5c72b21c7e7b..cb97200bfda7 100644
--- a/arch/x86/kernel/sev.c
+++ b/arch/x86/kernel/sev.c
@@ -2034,6 +2034,8 @@ bool __init snp_init(struct boot_params *bp)
if (!cc_info)
return false;

+ snp_setup_cpuid_table(cc_info);
+
/*
* The CC blob will be used later to access the secrets page. Cache
* it here like the boot kernel does.
@@ -2047,3 +2049,76 @@ void __init snp_abort(void)
{
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SNP_UNSUPPORTED);
}
+
+static void snp_dump_cpuid_table(void)
+{
+ const struct snp_cpuid_info *cpuid_info = snp_cpuid_info_get_ptr();
+ int i = 0;
+
+ pr_info("count=%d reserved=0x%x reserved2=0x%llx\n",
+ cpuid_info->count, cpuid_info->__reserved1, cpuid_info->__reserved2);
+
+ for (i = 0; i < SNP_CPUID_COUNT_MAX; i++) {
+ const struct snp_cpuid_fn *fn = &cpuid_info->fn[i];
+
+ pr_info("index=%03d fn=0x%08x subfn=0x%08x: eax=0x%08x ebx=0x%08x ecx=0x%08x edx=0x%08x xcr0_in=0x%016llx xss_in=0x%016llx reserved=0x%016llx\n",
+ i, fn->eax_in, fn->ecx_in, fn->eax, fn->ebx, fn->ecx,
+ fn->edx, fn->xcr0_in, fn->xss_in, fn->__reserved);
+ }
+}
+
+/*
+ * It is useful from an auditing/testing perspective to provide an easy way
+ * for the guest owner to know that the CPUID table has been initialized as
+ * expected, but that initialization happens too early in boot to print any
+ * sort of indicator, and there's not really any other good place to do it.
+ * So do it here. This is also a good place to flag unexpected usage of the
+ * CPUID table that does not violate the SNP specification, but may still
+ * be worth noting to the user.
+ */
+static int __init snp_check_cpuid_table(void)
+{
+ const struct snp_cpuid_info *cpuid_info = snp_cpuid_info_get_ptr();
+ bool dump_table = false;
+ int i;
+
+ if (!cpuid_info->count)
+ return 0;
+
+ pr_info("Using SEV-SNP CPUID table, %d entries present.\n",
+ cpuid_info->count);
+
+ if (cpuid_info->__reserved1 || cpuid_info->__reserved2) {
+ pr_warn("Unexpected use of reserved fields in CPUID table header.\n");
+ dump_table = true;
+ }
+
+ for (i = 0; i < cpuid_info->count; i++) {
+ const struct snp_cpuid_fn *fn = &cpuid_info->fn[i];
+ struct snp_cpuid_fn zero_fn = {0};
+
+ /*
+ * Though allowed, an all-zero entry is not a valid leaf for linux
+ * guests, and likely the result of an incorrect entry count.
+ */
+ if (!memcmp(fn, &zero_fn, sizeof(struct snp_cpuid_fn))) {
+ pr_warn("CPUID table contains all-zero entry at index=%d\n", i);
+ dump_table = true;
+ }
+
+ if (fn->__reserved) {
+ pr_warn("Unexpected use of reserved fields in CPUID table entry at index=%d\n",
+ i);
+ dump_table = true;
+ }
+ }
+
+ if (dump_table) {
+ pr_warn("Dumping CPUID table:\n");
+ snp_dump_cpuid_table();
+ }
+
+ return 0;
+}
+
+arch_initcall(snp_check_cpuid_table);
--
2.25.1