Re: [PATCH v8 09/40] x86/compressed: Add helper for validating pages in the decompression stage

Venu Busireddy
2021-12-17T20:48:37Z

On 2021-12-10 09:43:01 -0600, Brijesh Singh wrote:
> Many of the integrity guarantees of SEV-SNP are enforced through the
> Reverse Map Table (RMP). Each RMP entry contains the GPA at which a
> particular page of DRAM should be mapped. The VMs can request the
> hypervisor to add pages in the RMP table via the Page State Change VMGEXIT
> defined in the GHCB specification. Inside each RMP entry is a Validated
> flag; this flag is automatically cleared to 0 by the CPU hardware when a
> new RMP entry is created for a guest. Each VM page can be either
> validated or invalidated, as indicated by the Validated flag in the RMP
> entry. Memory access to a private page that is not validated generates
> a #VC. A VM must use PVALIDATE instruction to validate the private page
> before using it.
>
> To maintain the security guarantee of SEV-SNP guests, when transitioning
> pages from private to shared, the guest must invalidate the pages before
> asking the hypervisor to change the page state to shared in the RMP table.
>
> After the pages are mapped private in the page table, the guest must issue
> a page state change VMGEXIT to make the pages private in the RMP table and
> validate it.
>
> On boot, BIOS should have validated the entire system memory. During
> the kernel decompression stage, the VC handler uses the
> set_memory_decrypted() to make the GHCB page shared (i.e clear encryption
> attribute). And while exiting from the decompression, it calls the
> set_page_encrypted() to make the page private.
>
> Add sev_snp_set_page_{private,shared}() helper that is used by the

Since the functions being added are snp_set_page_{private,shared}(),

s/sev_snp_set_page_/snp_set_page_/

Also, s/helper that is/helpers that are/

> set_memory_{decrypt,encrypt}() to change the page state in the RMP table.

s/decrypt,encrypt/decrypted,encrypted/

>
> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
> ---
> arch/x86/boot/compressed/ident_map_64.c | 18 +++++++++-
> arch/x86/boot/compressed/misc.h | 4 +++
> arch/x86/boot/compressed/sev.c | 46 +++++++++++++++++++++++++
> arch/x86/include/asm/sev-common.h | 26 ++++++++++++++
> 4 files changed, 93 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/boot/compressed/ident_map_64.c b/arch/x86/boot/compressed/ident_map_64.c
> index f7213d0943b8..ef77453cc629 100644
> --- a/arch/x86/boot/compressed/ident_map_64.c
> +++ b/arch/x86/boot/compressed/ident_map_64.c
> @@ -275,15 +275,31 @@ static int set_clr_page_flags(struct x86_mapping_info *info,
> * Changing encryption attributes of a page requires to flush it from
> * the caches.
> */
> - if ((set | clr) & _PAGE_ENC)
> + if ((set | clr) & _PAGE_ENC) {
> clflush_page(address);
>
> + /*
> + * If the encryption attribute is being cleared, then change
> + * the page state to shared in the RMP table.
> + */
> + if (clr)

This function is also called by set_page_non_present() with clr set to
_PAGE_PRESENT. Do we want to change the page state to shared even when
the page is not present? If not, shouldn't the check be (clr & _PAGE_ENC)?

> + snp_set_page_shared(pte_pfn(*ptep) << PAGE_SHIFT);
> + }
> +
> /* Update PTE */
> pte = *ptep;
> pte = pte_set_flags(pte, set);
> pte = pte_clear_flags(pte, clr);
> set_pte(ptep, pte);
>
> + /*
> + * If the encryption attribute is being set, then change the page state to
> + * private in the RMP entry. The page state must be done after the PTE
> + * is updated.
> + */
> + if (set & _PAGE_ENC)
> + snp_set_page_private(__pa(address & PAGE_MASK));
> +
> /* Flush TLB after changing encryption attribute */
> write_cr3(top_level_pgt);
>
> diff --git a/arch/x86/boot/compressed/misc.h b/arch/x86/boot/compressed/misc.h
> index 23e0e395084a..01cc13c12059 100644
> --- a/arch/x86/boot/compressed/misc.h
> +++ b/arch/x86/boot/compressed/misc.h
> @@ -124,6 +124,8 @@ static inline void console_init(void)
> void sev_enable(struct boot_params *bp);
> 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);
> #else
> static inline void sev_enable(struct boot_params *bp) { }
> static inline void sev_es_shutdown_ghcb(void) { }
> @@ -131,6 +133,8 @@ static inline bool sev_es_check_ghcb_fault(unsigned long address)
> {
> return false;
> }
> +static inline void snp_set_page_private(unsigned long paddr) { }
> +static inline void snp_set_page_shared(unsigned long paddr) { }
> #endif
>
> /* acpi.c */
> diff --git a/arch/x86/boot/compressed/sev.c b/arch/x86/boot/compressed/sev.c
> index 9be369f72299..12a93acc94ba 100644
> --- a/arch/x86/boot/compressed/sev.c
> +++ b/arch/x86/boot/compressed/sev.c
> @@ -119,6 +119,52 @@ static enum es_result vc_read_mem(struct es_em_ctxt *ctxt,
> /* Include code for early handlers */
> #include "../../kernel/sev-shared.c"
>
> +static inline bool sev_snp_enabled(void)
> +{
> + return sev_status & MSR_AMD64_SEV_SNP_ENABLED;
> +}
> +
> +static void __page_state_change(unsigned long paddr, enum psc_op op)
> +{
> + u64 val;
> +
> + if (!sev_snp_enabled())
> + return;
> +
> + /*
> + * If private -> shared then invalidate the page before requesting the

This comment is confusing. We don't know what the present state is,
right? If we don't, shouldn't we just say:

If the operation is SNP_PAGE_STATE_SHARED, invalidate the page before
requesting the state change in the RMP table.

> + * state change in the RMP table.
> + */
> + if (op == SNP_PAGE_STATE_SHARED && pvalidate(paddr, RMP_PG_SIZE_4K, 0))
> + sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PVALIDATE);
> +
> + /* Issue VMGEXIT to change the page state in RMP table. */
> + sev_es_wr_ghcb_msr(GHCB_MSR_PSC_REQ_GFN(paddr >> PAGE_SHIFT, op));
> + VMGEXIT();
> +
> + /* Read the response of the VMGEXIT. */
> + val = sev_es_rd_ghcb_msr();
> + if ((GHCB_RESP_CODE(val) != GHCB_MSR_PSC_RESP) || GHCB_MSR_PSC_RESP_VAL(val))
> + sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PSC);
> +
> + /*
> + * Now that page is added in the RMP table, validate it so that it is
> + * consistent with the RMP entry.

The page is not "added", right? Shouldn't we just say:

Validate the page so that it is consistent with the RMP entry.

Venu