x86/sev: Check for the presence of an SVSM in the SNP secrets page

During early boot phases, check for the presence of an SVSM when running
as an SEV-SNP guest.

An SVSM is present if not running at VMPL0 and the 64-bit value at offset
0x148 into the secrets page is non-zero. If an SVSM is present, save the
SVSM Calling Area address (CAA), located at offset 0x150 into the secrets
page, and set the VMPL level of the guest, which should be non-zero, to
indicate the presence of an SVSM.

  [ bp: Touchups. ]

Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Link: https://lore.kernel.org/r/9d3fe161be93d4ea60f43c2a3f2c311fe708b63b.1717600736.git.thomas.lendacky@amd.com
This commit is contained in:
Tom Lendacky 2024-06-05 10:18:45 -05:00 committed by Borislav Petkov (AMD)
parent b547fc2c99
commit 878e70dbd2
6 changed files with 160 additions and 10 deletions

View File

@ -130,4 +130,31 @@ SNP feature support.
More details in AMD64 APM[1] Vol 2: 15.34.10 SEV_STATUS MSR
[1] https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf
Secure VM Service Module (SVSM)
===============================
SNP provides a feature called Virtual Machine Privilege Levels (VMPL) which
defines four privilege levels at which guest software can run. The most
privileged level is 0 and numerically higher numbers have lesser privileges.
More details in the AMD64 APM Vol 2, section "15.35.7 Virtual Machine
Privilege Levels", docID: 24593.
When using that feature, different services can run at different protection
levels, apart from the guest OS but still within the secure SNP environment.
They can provide services to the guest, like a vTPM, for example.
When a guest is not running at VMPL0, it needs to communicate with the software
running at VMPL0 to perform privileged operations or to interact with secure
services. An example fur such a privileged operation is PVALIDATE which is
*required* to be executed at VMPL0.
In this scenario, the software running at VMPL0 is usually called a Secure VM
Service Module (SVSM). Discovery of an SVSM and the API used to communicate
with it is documented in "Secure VM Service Module for SEV-SNP Guests", docID:
58019.
(Latest versions of the above-mentioned documents can be found by using
a search engine like duckduckgo.com and typing in:
site:amd.com "Secure VM Service Module for SEV-SNP Guests", docID: 58019
for example.)

View File

@ -462,6 +462,13 @@ static bool early_snp_init(struct boot_params *bp)
*/
setup_cpuid_table(cc_info);
/*
* Record the SVSM Calling Area (CA) address if the guest is not
* running at VMPL0. The CA will be used to communicate with the
* SVSM and request its services.
*/
svsm_setup_ca(cc_info);
/*
* Pass run-time kernel a pointer to CC info via boot_params so EFI
* config table doesn't need to be searched again during early startup
@ -571,14 +578,12 @@ void sev_enable(struct boot_params *bp)
/*
* Enforce running at VMPL0.
*
* RMPADJUST modifies RMP permissions of a lesser-privileged (numerically
* higher) privilege level. Here, clear the VMPL1 permission mask of the
* GHCB page. If the guest is not running at VMPL0, this will fail.
*
* If the guest is running at VMPL0, it will succeed. Even if that operation
* modifies permission bits, it is still ok to do so currently because Linux
* SNP guests running at VMPL0 only run at VMPL0, so VMPL1 or higher
* permission mask changes are a don't-care.
* Use RMPADJUST (see the rmpadjust() function for a description of
* what the instruction does) to update the VMPL1 permissions of a
* page. If the guest is running at VMPL0, this will succeed. If the
* guest is running at any other VMPL, this will fail. Linux SNP guests
* only ever run at a single VMPL level so permission mask changes of a
* lesser-privileged VMPL are a don't-care.
*/
if (rmpadjust((unsigned long)&boot_ghcb_page, RMP_PG_SIZE_4K, 1))
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_NOT_VMPL0);

View File

@ -163,6 +163,10 @@ struct snp_psc_desc {
#define GHCB_TERM_NOT_VMPL0 3 /* SNP guest is not running at VMPL-0 */
#define GHCB_TERM_CPUID 4 /* CPUID-validation failure */
#define GHCB_TERM_CPUID_HV 5 /* CPUID failure during hypervisor fallback */
#define GHCB_TERM_SECRETS_PAGE 6 /* Secrets page failure */
#define GHCB_TERM_NO_SVSM 7 /* SVSM is not advertised in the secrets page */
#define GHCB_TERM_SVSM_VMPL0 8 /* SVSM is present but has set VMPL to 0 */
#define GHCB_TERM_SVSM_CAA 9 /* SVSM is present but CAA is not page aligned */
#define GHCB_RESP_CODE(v) ((v) & GHCB_MSR_INFO_MASK)

View File

@ -152,9 +152,32 @@ struct snp_secrets_page {
u8 vmpck2[VMPCK_KEY_LEN];
u8 vmpck3[VMPCK_KEY_LEN];
struct secrets_os_area os_area;
u8 rsvd3[3840];
u8 vmsa_tweak_bitmap[64];
/* SVSM fields */
u64 svsm_base;
u64 svsm_size;
u64 svsm_caa;
u32 svsm_max_version;
u8 svsm_guest_vmpl;
u8 rsvd3[3];
/* Remainder of page */
u8 rsvd4[3744];
} __packed;
/*
* The SVSM Calling Area (CA) related structures.
*/
struct svsm_ca {
u8 call_pending;
u8 mem_available;
u8 rsvd1[6];
u8 svsm_buffer[PAGE_SIZE - 8];
};
#ifdef CONFIG_AMD_MEM_ENCRYPT
extern void __sev_es_ist_enter(struct pt_regs *regs);
extern void __sev_es_ist_exit(void);
@ -181,6 +204,14 @@ static __always_inline void sev_es_nmi_complete(void)
extern int __init sev_es_efi_map_ghcbs(pgd_t *pgd);
extern void sev_enable(struct boot_params *bp);
/*
* RMPADJUST modifies the RMP permissions of a page of a lesser-
* privileged (numerically higher) VMPL.
*
* If the guest is running at a higher-privilege than the privilege
* level the instruction is targeting, the instruction will succeed,
* otherwise, it will fail.
*/
static inline int rmpadjust(unsigned long vaddr, bool rmp_psize, unsigned long attrs)
{
int rc;

View File

@ -23,6 +23,21 @@
#define sev_printk_rtl(fmt, ...)
#endif
/*
* SVSM related information:
* When running under an SVSM, the VMPL that Linux is executing at must be
* non-zero. The VMPL is therefore used to indicate the presence of an SVSM.
*
* During boot, the page tables are set up as identity mapped and later
* changed to use kernel virtual addresses. Maintain separate virtual and
* physical addresses for the CAA to allow SVSM functions to be used during
* early boot, both with identity mapped virtual addresses and proper kernel
* virtual addresses.
*/
static u8 snp_vmpl __ro_after_init;
static struct svsm_ca *boot_svsm_caa __ro_after_init;
static u64 boot_svsm_caa_pa __ro_after_init;
/* I/O parameters for CPUID-related helpers */
struct cpuid_leaf {
u32 fn;
@ -1269,3 +1284,64 @@ static enum es_result vc_check_opcode_bytes(struct es_em_ctxt *ctxt,
return ES_UNSUPPORTED;
}
/*
* Maintain the GPA of the SVSM Calling Area (CA) in order to utilize the SVSM
* services needed when not running in VMPL0.
*/
static void __head svsm_setup_ca(const struct cc_blob_sev_info *cc_info)
{
struct snp_secrets_page *secrets_page;
u64 caa;
BUILD_BUG_ON(sizeof(*secrets_page) != PAGE_SIZE);
/*
* Check if running at VMPL0.
*
* Use RMPADJUST (see the rmpadjust() function for a description of what
* the instruction does) to update the VMPL1 permissions of a page. If
* the guest is running at VMPL0, this will succeed and implies there is
* no SVSM. If the guest is running at any other VMPL, this will fail.
* Linux SNP guests only ever run at a single VMPL level so permission mask
* changes of a lesser-privileged VMPL are a don't-care.
*
* Use a rip-relative reference to obtain the proper address, since this
* routine is running identity mapped when called, both by the decompressor
* code and the early kernel code.
*/
if (!rmpadjust((unsigned long)&RIP_REL_REF(boot_ghcb_page), RMP_PG_SIZE_4K, 1))
return;
/*
* Not running at VMPL0, ensure everything has been properly supplied
* for running under an SVSM.
*/
if (!cc_info || !cc_info->secrets_phys || cc_info->secrets_len != PAGE_SIZE)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SECRETS_PAGE);
secrets_page = (struct snp_secrets_page *)cc_info->secrets_phys;
if (!secrets_page->svsm_size)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_NO_SVSM);
if (!secrets_page->svsm_guest_vmpl)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SVSM_VMPL0);
RIP_REL_REF(snp_vmpl) = secrets_page->svsm_guest_vmpl;
caa = secrets_page->svsm_caa;
/*
* An open-coded PAGE_ALIGNED() in order to avoid including
* kernel-proper headers into the decompressor.
*/
if (caa & (PAGE_SIZE - 1))
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SVSM_CAA);
/*
* The CA is identity mapped when this routine is called, both by the
* decompressor code and the early kernel code.
*/
RIP_REL_REF(boot_svsm_caa) = (struct svsm_ca *)caa;
RIP_REL_REF(boot_svsm_caa_pa) = caa;
}

View File

@ -2108,6 +2108,13 @@ bool __head snp_init(struct boot_params *bp)
setup_cpuid_table(cc_info);
/*
* Record the SVSM Calling Area address (CAA) if the guest is not
* running at VMPL0. The CA will be used to communicate with the
* SVSM to perform the SVSM services.
*/
svsm_setup_ca(cc_info);
/*
* The CC blob will be used later to access the secrets page. Cache
* it here like the boot kernel does.