- Do the proper memory conversion of guest memory in order to be able to kexec

kernels in SNP guests along with other adjustments and cleanups to that
   effect
 
 - Start converting and moving functionality from the sev-guest driver into
   core code with the purpose of supporting the secure TSC SNP feature where
   the hypervisor cannot influence the TSC exposed to the guest anymore
 
 - Add a "nosnp" cmdline option in order to be able to disable SNP support in
   the hypervisor and thus free-up resources which are not going to be used
 
 - Cleanups
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEzv7L6UO9uDPlPSfHEsHwGGHeVUoFAmc7ZToACgkQEsHwGGHe
 VUp61hAArA8taJaGUSdoe3sN60yRWCTe30QiDLvUrDGqmPHbBnDpdYsoaZujkQMI
 334piSWWu/pB6meO93uwv8X/ZO0ryOw46RK3szTz/RhBB5pTO3NbAj1zMF5q2KUy
 a+SYbZffV+qBUEpGujGrqrwT7X3U70yCKJFaZQOGvyYFzo+kyx6euqlYP+StOD+D
 ph7SDrXv0N0uU/2OiwCzF0cKvAuNHG2Cfn3kqSKvcZ+NWF3BKmw1IkgFA9f05P+j
 mOkc+1jCbi26b94MSJHSL33iRtbD0NgUzT9F2tw9Qszw1BQ5Er30Y45ywoudAhsn
 VrpMhBwWRCUdakQ2PsI7O8WB4gnBdWpEuzS2Ssqa1akB+pggH2xQzVb5EznmbzlS
 gz/SqUP75ijTT/oGh+C/hKAES3pmO4pH48J7llOKzb8YpoxxzjSEVb2pVbLzNdIV
 +it12Cap0lW+CTNGF4p2TbuKXKkE1LiGya1JMymQiZL8quCBYJIQUttiBvBg8Ac1
 oCw2DXQZsjDw55Hwwhr95J4FuY4+iQd+o1GgRDQ4MEqaYFEfdcFRA1YCbMHgiAzu
 NOGwjrQ2PB5xGST34qobGtk7Xt2nIilDvl5K5Co2E4s14NLrlBHo2uq33d0unlIZ
 BJMrHG/IWNjuHbKl/vM05fuiKEIvpL5qTKz7oVL6tX8Zphf6ljU=
 =C431
 -----END PGP SIGNATURE-----

Merge tag 'x86_sev_for_v6.13' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 SEV updates from Borislav Petkov:

 - Do the proper memory conversion of guest memory in order to be able
   to kexec kernels in SNP guests along with other adjustments and
   cleanups to that effect

 - Start converting and moving functionality from the sev-guest driver
   into core code with the purpose of supporting the secure TSC SNP
   feature where the hypervisor cannot influence the TSC exposed to the
   guest anymore

 - Add a "nosnp" cmdline option in order to be able to disable SNP
   support in the hypervisor and thus free-up resources which are not
   going to be used

 - Cleanups

[ Reminding myself about the endless TLA's again: SEV is the AMD Secure
  Encrypted Virtualization    - Linus ]

* tag 'x86_sev_for_v6.13' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/sev: Cleanup vc_handle_msr()
  x86/sev: Convert shared memory back to private on kexec
  x86/mm: Refactor __set_clr_pte_enc()
  x86/boot: Skip video memory access in the decompressor for SEV-ES/SNP
  virt: sev-guest: Carve out SNP message context structure
  virt: sev-guest: Reduce the scope of SNP command mutex
  virt: sev-guest: Consolidate SNP guest messaging parameters to a struct
  x86/sev: Cache the secrets page address
  x86/sev: Handle failures from snp_init()
  virt: sev-guest: Use AES GCM crypto library
  x86/virt: Provide "nosnp" boot option for sev kernel command line
  x86/virt: Move SEV-specific parsing into arch/x86/virt/svm
This commit is contained in:
Linus Torvalds 2024-11-19 12:21:35 -08:00
commit 55db8eb456
11 changed files with 543 additions and 394 deletions

View File

@ -305,3 +305,8 @@ The available options are:
debug
Enable debug messages.
nosnp
Do not enable SEV-SNP (applies to host/hypervisor only). Setting
'nosnp' avoids the RMP check overhead in memory accesses when
users do not want to run SEV-SNP guests.

View File

@ -385,6 +385,19 @@ static void parse_mem_encrypt(struct setup_header *hdr)
hdr->xloadflags |= XLF_MEM_ENCRYPTION;
}
static void early_sev_detect(void)
{
/*
* Accessing video memory causes guest termination because
* the boot stage2 #VC handler of SEV-ES/SNP guests does not
* support MMIO handling and kexec -c adds screen_info to the
* boot parameters passed to the kexec kernel, which causes
* console output to be dumped to both video and serial.
*/
if (sev_status & MSR_AMD64_SEV_ES_ENABLED)
lines = cols = 0;
}
/*
* The compressed kernel image (ZO), has been moved so that its position
* is against the end of the buffer used to hold the uncompressed kernel
@ -440,6 +453,8 @@ asmlinkage __visible void *extract_kernel(void *rmode, unsigned char *output)
*/
early_tdx_detect();
early_sev_detect();
console_init();
/*

View File

@ -92,6 +92,9 @@ static struct ghcb *boot_ghcb __section(".data");
/* Bitmap of SEV features supported by the hypervisor */
static u64 sev_hv_features __ro_after_init;
/* Secrets page physical address from the CC blob */
static u64 secrets_pa __ro_after_init;
/* #VC handler runtime per-CPU data */
struct sev_es_runtime_data {
struct ghcb ghcb_page;
@ -141,33 +144,6 @@ static DEFINE_PER_CPU(struct sev_es_save_area *, sev_vmsa);
static DEFINE_PER_CPU(struct svsm_ca *, svsm_caa);
static DEFINE_PER_CPU(u64, svsm_caa_pa);
struct sev_config {
__u64 debug : 1,
/*
* Indicates when the per-CPU GHCB has been created and registered
* and thus can be used by the BSP instead of the early boot GHCB.
*
* For APs, the per-CPU GHCB is created before they are started
* and registered upon startup, so this flag can be used globally
* for the BSP and APs.
*/
ghcbs_initialized : 1,
/*
* Indicates when the per-CPU SVSM CA is to be used instead of the
* boot SVSM CA.
*
* For APs, the per-CPU SVSM CA is created as part of the AP
* bringup, so this flag can be used globally for the BSP and APs.
*/
use_cas : 1,
__reserved : 61;
};
static struct sev_config sev_cfg __read_mostly;
static __always_inline bool on_vc_stack(struct pt_regs *regs)
{
unsigned long sp = regs->sp;
@ -722,45 +698,13 @@ void noinstr __sev_es_nmi_complete(void)
__sev_put_ghcb(&state);
}
static u64 __init get_secrets_page(void)
{
u64 pa_data = boot_params.cc_blob_address;
struct cc_blob_sev_info info;
void *map;
/*
* The CC blob contains the address of the secrets page, check if the
* blob is present.
*/
if (!pa_data)
return 0;
map = early_memremap(pa_data, sizeof(info));
if (!map) {
pr_err("Unable to locate SNP secrets page: failed to map the Confidential Computing blob.\n");
return 0;
}
memcpy(&info, map, sizeof(info));
early_memunmap(map, sizeof(info));
/* smoke-test the secrets page passed */
if (!info.secrets_phys || info.secrets_len != PAGE_SIZE)
return 0;
return info.secrets_phys;
}
static u64 __init get_snp_jump_table_addr(void)
{
struct snp_secrets_page *secrets;
void __iomem *mem;
u64 pa, addr;
u64 addr;
pa = get_secrets_page();
if (!pa)
return 0;
mem = ioremap_encrypted(pa, PAGE_SIZE);
mem = ioremap_encrypted(secrets_pa, PAGE_SIZE);
if (!mem) {
pr_err("Unable to locate AP jump table address: failed to map the SNP secrets page.\n");
return 0;
@ -1010,6 +954,137 @@ void snp_accept_memory(phys_addr_t start, phys_addr_t end)
set_pages_state(vaddr, npages, SNP_PAGE_STATE_PRIVATE);
}
static void set_pte_enc(pte_t *kpte, int level, void *va)
{
struct pte_enc_desc d = {
.kpte = kpte,
.pte_level = level,
.va = va,
.encrypt = true
};
prepare_pte_enc(&d);
set_pte_enc_mask(kpte, d.pfn, d.new_pgprot);
}
static void unshare_all_memory(void)
{
unsigned long addr, end, size, ghcb;
struct sev_es_runtime_data *data;
unsigned int npages, level;
bool skipped_addr;
pte_t *pte;
int cpu;
/* Unshare the direct mapping. */
addr = PAGE_OFFSET;
end = PAGE_OFFSET + get_max_mapped();
while (addr < end) {
pte = lookup_address(addr, &level);
size = page_level_size(level);
npages = size / PAGE_SIZE;
skipped_addr = false;
if (!pte || !pte_decrypted(*pte) || pte_none(*pte)) {
addr += size;
continue;
}
/*
* Ensure that all the per-CPU GHCBs are made private at the
* end of the unsharing loop so that the switch to the slower
* MSR protocol happens last.
*/
for_each_possible_cpu(cpu) {
data = per_cpu(runtime_data, cpu);
ghcb = (unsigned long)&data->ghcb_page;
if (addr <= ghcb && ghcb <= addr + size) {
skipped_addr = true;
break;
}
}
if (!skipped_addr) {
set_pte_enc(pte, level, (void *)addr);
snp_set_memory_private(addr, npages);
}
addr += size;
}
/* Unshare all bss decrypted memory. */
addr = (unsigned long)__start_bss_decrypted;
end = (unsigned long)__start_bss_decrypted_unused;
npages = (end - addr) >> PAGE_SHIFT;
for (; addr < end; addr += PAGE_SIZE) {
pte = lookup_address(addr, &level);
if (!pte || !pte_decrypted(*pte) || pte_none(*pte))
continue;
set_pte_enc(pte, level, (void *)addr);
}
addr = (unsigned long)__start_bss_decrypted;
snp_set_memory_private(addr, npages);
__flush_tlb_all();
}
/* Stop new private<->shared conversions */
void snp_kexec_begin(void)
{
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
return;
if (!IS_ENABLED(CONFIG_KEXEC_CORE))
return;
/*
* Crash kernel ends up here with interrupts disabled: can't wait for
* conversions to finish.
*
* If race happened, just report and proceed.
*/
if (!set_memory_enc_stop_conversion())
pr_warn("Failed to stop shared<->private conversions\n");
}
void snp_kexec_finish(void)
{
struct sev_es_runtime_data *data;
unsigned int level, cpu;
unsigned long size;
struct ghcb *ghcb;
pte_t *pte;
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
return;
if (!IS_ENABLED(CONFIG_KEXEC_CORE))
return;
unshare_all_memory();
/*
* Switch to using the MSR protocol to change per-CPU GHCBs to
* private. All the per-CPU GHCBs have been switched back to private,
* so can't do any more GHCB calls to the hypervisor beyond this point
* until the kexec'ed kernel starts running.
*/
boot_ghcb = NULL;
sev_cfg.ghcbs_initialized = false;
for_each_possible_cpu(cpu) {
data = per_cpu(runtime_data, cpu);
ghcb = &data->ghcb_page;
pte = lookup_address((unsigned long)ghcb, &level);
size = page_level_size(level);
set_pte_enc(pte, level, (void *)ghcb);
snp_set_memory_private((unsigned long)ghcb, (size / PAGE_SIZE));
}
}
static int snp_set_vmsa(void *va, void *caa, int apic_id, bool make_vmsa)
{
int ret;
@ -1331,35 +1406,39 @@ int __init sev_es_efi_map_ghcbs(pgd_t *pgd)
return 0;
}
/* Writes to the SVSM CAA MSR are ignored */
static enum es_result __vc_handle_msr_caa(struct pt_regs *regs, bool write)
{
if (write)
return ES_OK;
regs->ax = lower_32_bits(this_cpu_read(svsm_caa_pa));
regs->dx = upper_32_bits(this_cpu_read(svsm_caa_pa));
return ES_OK;
}
static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
{
struct pt_regs *regs = ctxt->regs;
enum es_result ret;
u64 exit_info_1;
bool write;
/* Is it a WRMSR? */
exit_info_1 = (ctxt->insn.opcode.bytes[1] == 0x30) ? 1 : 0;
write = ctxt->insn.opcode.bytes[1] == 0x30;
if (regs->cx == MSR_SVSM_CAA) {
/* Writes to the SVSM CAA msr are ignored */
if (exit_info_1)
return ES_OK;
regs->ax = lower_32_bits(this_cpu_read(svsm_caa_pa));
regs->dx = upper_32_bits(this_cpu_read(svsm_caa_pa));
return ES_OK;
}
if (regs->cx == MSR_SVSM_CAA)
return __vc_handle_msr_caa(regs, write);
ghcb_set_rcx(ghcb, regs->cx);
if (exit_info_1) {
if (write) {
ghcb_set_rax(ghcb, regs->ax);
ghcb_set_rdx(ghcb, regs->dx);
}
ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_EXIT_MSR, exit_info_1, 0);
ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_EXIT_MSR, write, 0);
if ((ret == ES_OK) && (!exit_info_1)) {
if ((ret == ES_OK) && !write) {
regs->ax = ghcb->save.rax;
regs->dx = ghcb->save.rdx;
}
@ -2300,6 +2379,11 @@ bool __head snp_init(struct boot_params *bp)
if (!cc_info)
return false;
if (cc_info->secrets_phys && cc_info->secrets_len == PAGE_SIZE)
secrets_pa = cc_info->secrets_phys;
else
return false;
setup_cpuid_table(cc_info);
svsm_setup(cc_info);
@ -2374,23 +2458,6 @@ static int __init report_snp_info(void)
}
arch_initcall(report_snp_info);
static int __init init_sev_config(char *str)
{
char *s;
while ((s = strsep(&str, ","))) {
if (!strcmp(s, "debug")) {
sev_cfg.debug = true;
continue;
}
pr_info("SEV command-line option '%s' was not recognized\n", s);
}
return 1;
}
__setup("sev=", init_sev_config);
static void update_attest_input(struct svsm_call *call, struct svsm_attest_call *input)
{
/* If (new) lengths have been returned, propagate them up */
@ -2441,7 +2508,8 @@ int snp_issue_svsm_attest_req(u64 call_id, struct svsm_call *call,
}
EXPORT_SYMBOL_GPL(snp_issue_svsm_attest_req);
int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input, struct snp_guest_request_ioctl *rio)
int snp_issue_guest_request(struct snp_guest_req *req, struct snp_req_data *input,
struct snp_guest_request_ioctl *rio)
{
struct ghcb_state state;
struct es_em_ctxt ctxt;
@ -2465,12 +2533,12 @@ int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input, struct sn
vc_ghcb_invalidate(ghcb);
if (exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST) {
if (req->exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST) {
ghcb_set_rax(ghcb, input->data_gpa);
ghcb_set_rbx(ghcb, input->data_npages);
}
ret = sev_es_ghcb_hv_call(ghcb, &ctxt, exit_code, input->req_gpa, input->resp_gpa);
ret = sev_es_ghcb_hv_call(ghcb, &ctxt, req->exit_code, input->req_gpa, input->resp_gpa);
if (ret)
goto e_put;
@ -2485,7 +2553,7 @@ int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input, struct sn
case SNP_GUEST_VMM_ERR(SNP_GUEST_VMM_ERR_INVALID_LEN):
/* Number of expected pages are returned in RBX */
if (exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST) {
if (req->exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST) {
input->data_npages = ghcb_get_rbx(ghcb);
ret = -ENOSPC;
break;
@ -2513,16 +2581,11 @@ static struct platform_device sev_guest_device = {
static int __init snp_init_platform_device(void)
{
struct sev_guest_platform_data data;
u64 gpa;
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
return -ENODEV;
gpa = get_secrets_page();
if (!gpa)
return -ENODEV;
data.secrets_gpa = gpa;
data.secrets_gpa = secrets_pa;
if (platform_device_add_data(&sev_guest_device, &data, sizeof(data)))
return -ENODEV;

View File

@ -220,4 +220,31 @@ struct snp_psc_desc {
#define GHCB_ERR_INVALID_INPUT 5
#define GHCB_ERR_INVALID_EVENT 6
struct sev_config {
__u64 debug : 1,
/*
* Indicates when the per-CPU GHCB has been created and registered
* and thus can be used by the BSP instead of the early boot GHCB.
*
* For APs, the per-CPU GHCB is created before they are started
* and registered upon startup, so this flag can be used globally
* for the BSP and APs.
*/
ghcbs_initialized : 1,
/*
* Indicates when the per-CPU SVSM CA is to be used instead of the
* boot SVSM CA.
*
* For APs, the per-CPU SVSM CA is created as part of the AP
* bringup, so this flag can be used globally for the BSP and APs.
*/
use_cas : 1,
__reserved : 61;
};
extern struct sev_config sev_cfg;
#endif

View File

@ -120,6 +120,9 @@ struct snp_req_data {
};
#define MAX_AUTHTAG_LEN 32
#define AUTHTAG_LEN 16
#define AAD_LEN 48
#define MSG_HDR_VER 1
/* See SNP spec SNP_GUEST_REQUEST section for the structure */
enum msg_type {
@ -171,6 +174,19 @@ struct sev_guest_platform_data {
u64 secrets_gpa;
};
struct snp_guest_req {
void *req_buf;
size_t req_sz;
void *resp_buf;
size_t resp_sz;
u64 exit_code;
unsigned int vmpck_id;
u8 msg_version;
u8 msg_type;
};
/*
* The secrets page contains 96-bytes of reserved field that can be used by
* the guest OS. The guest OS uses the area to save the message sequence
@ -218,6 +234,27 @@ struct snp_secrets_page {
u8 rsvd4[3744];
} __packed;
struct snp_msg_desc {
/* request and response are in unencrypted memory */
struct snp_guest_msg *request, *response;
/*
* Avoid information leakage by double-buffering shared messages
* in fields that are in regular encrypted memory.
*/
struct snp_guest_msg secret_request, secret_response;
struct snp_secrets_page *secrets;
struct snp_req_data input;
void *certs_data;
struct aesgcm_ctx *ctx;
u32 *os_area_msg_seqno;
u8 *vmpck;
};
/*
* The SVSM Calling Area (CA) related structures.
*/
@ -285,6 +322,22 @@ struct svsm_attest_call {
u8 rsvd[4];
};
/* PTE descriptor used for the prepare_pte_enc() operations. */
struct pte_enc_desc {
pte_t *kpte;
int pte_level;
bool encrypt;
/* pfn of the kpte above */
unsigned long pfn;
/* physical address of @pfn */
unsigned long pa;
/* virtual address of @pfn */
void *va;
/* memory covered by the pte */
unsigned long size;
pgprot_t new_pgprot;
};
/*
* SVSM protocol structure
*/
@ -392,13 +445,18 @@ void snp_set_wakeup_secondary_cpu(void);
bool snp_init(struct boot_params *bp);
void __noreturn snp_abort(void);
void snp_dmi_setup(void);
int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input, struct snp_guest_request_ioctl *rio);
int snp_issue_guest_request(struct snp_guest_req *req, struct snp_req_data *input,
struct snp_guest_request_ioctl *rio);
int snp_issue_svsm_attest_req(u64 call_id, struct svsm_call *call, struct svsm_attest_call *input);
void snp_accept_memory(phys_addr_t start, phys_addr_t end);
u64 snp_get_unsupported_features(u64 status);
u64 sev_get_status(void);
void sev_show_status(void);
void snp_update_svsm_ca(void);
int prepare_pte_enc(struct pte_enc_desc *d);
void set_pte_enc_mask(pte_t *kpte, unsigned long pfn, pgprot_t new_prot);
void snp_kexec_finish(void);
void snp_kexec_begin(void);
#else /* !CONFIG_AMD_MEM_ENCRYPT */
@ -422,7 +480,8 @@ static inline void snp_set_wakeup_secondary_cpu(void) { }
static inline bool snp_init(struct boot_params *bp) { return false; }
static inline void snp_abort(void) { }
static inline void snp_dmi_setup(void) { }
static inline int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input, struct snp_guest_request_ioctl *rio)
static inline int snp_issue_guest_request(struct snp_guest_req *req, struct snp_req_data *input,
struct snp_guest_request_ioctl *rio)
{
return -ENOTTY;
}
@ -435,6 +494,10 @@ static inline u64 snp_get_unsupported_features(u64 status) { return 0; }
static inline u64 sev_get_status(void) { return 0; }
static inline void sev_show_status(void) { }
static inline void snp_update_svsm_ca(void) { }
static inline int prepare_pte_enc(struct pte_enc_desc *d) { return 0; }
static inline void set_pte_enc_mask(pte_t *kpte, unsigned long pfn, pgprot_t new_prot) { }
static inline void snp_kexec_finish(void) { }
static inline void snp_kexec_begin(void) { }
#endif /* CONFIG_AMD_MEM_ENCRYPT */

View File

@ -311,59 +311,82 @@ static int amd_enc_status_change_finish(unsigned long vaddr, int npages, bool en
return 0;
}
static void __init __set_clr_pte_enc(pte_t *kpte, int level, bool enc)
int prepare_pte_enc(struct pte_enc_desc *d)
{
pgprot_t old_prot, new_prot;
unsigned long pfn, pa, size;
pte_t new_pte;
pgprot_t old_prot;
pfn = pg_level_to_pfn(level, kpte, &old_prot);
if (!pfn)
return;
d->pfn = pg_level_to_pfn(d->pte_level, d->kpte, &old_prot);
if (!d->pfn)
return 1;
new_prot = old_prot;
if (enc)
pgprot_val(new_prot) |= _PAGE_ENC;
d->new_pgprot = old_prot;
if (d->encrypt)
pgprot_val(d->new_pgprot) |= _PAGE_ENC;
else
pgprot_val(new_prot) &= ~_PAGE_ENC;
pgprot_val(d->new_pgprot) &= ~_PAGE_ENC;
/* If prot is same then do nothing. */
if (pgprot_val(old_prot) == pgprot_val(new_prot))
return;
if (pgprot_val(old_prot) == pgprot_val(d->new_pgprot))
return 1;
pa = pfn << PAGE_SHIFT;
size = page_level_size(level);
d->pa = d->pfn << PAGE_SHIFT;
d->size = page_level_size(d->pte_level);
/*
* We are going to perform in-place en-/decryption and change the
* physical page attribute from C=1 to C=0 or vice versa. Flush the
* caches to ensure that data gets accessed with the correct C-bit.
* In-place en-/decryption and physical page attribute change
* from C=1 to C=0 or vice versa will be performed. Flush the
* caches to ensure that data gets accessed with the correct
* C-bit.
*/
clflush_cache_range(__va(pa), size);
if (d->va)
clflush_cache_range(d->va, d->size);
else
clflush_cache_range(__va(d->pa), d->size);
return 0;
}
void set_pte_enc_mask(pte_t *kpte, unsigned long pfn, pgprot_t new_prot)
{
pte_t new_pte;
/* Change the page encryption mask. */
new_pte = pfn_pte(pfn, new_prot);
set_pte_atomic(kpte, new_pte);
}
static void __init __set_clr_pte_enc(pte_t *kpte, int level, bool enc)
{
struct pte_enc_desc d = {
.kpte = kpte,
.pte_level = level,
.encrypt = enc
};
if (prepare_pte_enc(&d))
return;
/* Encrypt/decrypt the contents in-place */
if (enc) {
sme_early_encrypt(pa, size);
sme_early_encrypt(d.pa, d.size);
} else {
sme_early_decrypt(pa, size);
sme_early_decrypt(d.pa, d.size);
/*
* ON SNP, the page state in the RMP table must happen
* before the page table updates.
*/
early_snp_set_memory_shared((unsigned long)__va(pa), pa, 1);
early_snp_set_memory_shared((unsigned long)__va(d.pa), d.pa, 1);
}
/* Change the page encryption mask. */
new_pte = pfn_pte(pfn, new_prot);
set_pte_atomic(kpte, new_pte);
set_pte_enc_mask(kpte, d.pfn, d.new_pgprot);
/*
* If page is set encrypted in the page table, then update the RMP table to
* add this page as private.
*/
if (enc)
early_snp_set_memory_private((unsigned long)__va(pa), pa, 1);
early_snp_set_memory_private((unsigned long)__va(d.pa), d.pa, 1);
}
static int __init early_set_memory_enc_dec(unsigned long vaddr,
@ -467,6 +490,8 @@ void __init sme_early_init(void)
x86_platform.guest.enc_status_change_finish = amd_enc_status_change_finish;
x86_platform.guest.enc_tlb_flush_required = amd_enc_tlb_flush_required;
x86_platform.guest.enc_cache_flush_required = amd_enc_cache_flush_required;
x86_platform.guest.enc_kexec_begin = snp_kexec_begin;
x86_platform.guest.enc_kexec_finish = snp_kexec_finish;
/*
* AMD-SEV-ES intercepts the RDMSR to read the X2APIC ID in the

View File

@ -495,10 +495,10 @@ void __head sme_enable(struct boot_params *bp)
unsigned int eax, ebx, ecx, edx;
unsigned long feature_mask;
unsigned long me_mask;
bool snp;
bool snp_en;
u64 msr;
snp = snp_init(bp);
snp_en = snp_init(bp);
/* Check for the SME/SEV support leaf */
eax = 0x80000000;
@ -531,8 +531,11 @@ void __head sme_enable(struct boot_params *bp)
RIP_REL_REF(sev_status) = msr = __rdmsr(MSR_AMD64_SEV);
feature_mask = (msr & MSR_AMD64_SEV_ENABLED) ? AMD_SEV_BIT : AMD_SME_BIT;
/* The SEV-SNP CC blob should never be present unless SEV-SNP is enabled. */
if (snp && !(msr & MSR_AMD64_SEV_SNP_ENABLED))
/*
* Any discrepancies between the presence of a CC blob and SNP
* enablement abort the guest.
*/
if (snp_en ^ !!(msr & MSR_AMD64_SEV_SNP_ENABLED))
snp_abort();
/* Check if memory encryption is enabled */

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_KVM_AMD_SEV) += sev.o
obj-$(CONFIG_CPU_SUP_AMD) += cmdline.o

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD SVM-SEV command line parsing support
*
* Copyright (C) 2023 - 2024 Advanced Micro Devices, Inc.
*
* Author: Michael Roth <michael.roth@amd.com>
*/
#include <linux/string.h>
#include <linux/printk.h>
#include <linux/cache.h>
#include <linux/cpufeature.h>
#include <asm/sev-common.h>
struct sev_config sev_cfg __read_mostly;
static int __init init_sev_config(char *str)
{
char *s;
while ((s = strsep(&str, ","))) {
if (!strcmp(s, "debug")) {
sev_cfg.debug = true;
continue;
}
if (!strcmp(s, "nosnp")) {
if (!cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) {
setup_clear_cpu_cap(X86_FEATURE_SEV_SNP);
cc_platform_clear(CC_ATTR_HOST_SEV_SNP);
continue;
} else {
goto warn;
}
}
warn:
pr_info("SEV command-line option '%s' was not recognized\n", s);
}
return 1;
}
__setup("sev=", init_sev_config);

View File

@ -2,9 +2,7 @@ config SEV_GUEST
tristate "AMD SEV Guest driver"
default m
depends on AMD_MEM_ENCRYPT
select CRYPTO
select CRYPTO_AEAD2
select CRYPTO_GCM
select CRYPTO_LIB_AESGCM
select TSM_REPORTS
help
SEV-SNP firmware provides the guest a mechanism to communicate with

View File

@ -17,8 +17,7 @@
#include <linux/set_memory.h>
#include <linux/fs.h>
#include <linux/tsm.h>
#include <crypto/aead.h>
#include <linux/scatterlist.h>
#include <crypto/gcm.h>
#include <linux/psp-sev.h>
#include <linux/sockptr.h>
#include <linux/cleanup.h>
@ -31,44 +30,23 @@
#include <asm/sev.h>
#define DEVICE_NAME "sev-guest"
#define AAD_LEN 48
#define MSG_HDR_VER 1
#define SNP_REQ_MAX_RETRY_DURATION (60*HZ)
#define SNP_REQ_RETRY_DELAY (2*HZ)
#define SVSM_MAX_RETRIES 3
struct snp_guest_crypto {
struct crypto_aead *tfm;
u8 *iv, *authtag;
int iv_len, a_len;
};
struct snp_guest_dev {
struct device *dev;
struct miscdevice misc;
void *certs_data;
struct snp_guest_crypto *crypto;
/* request and response are in unencrypted memory */
struct snp_guest_msg *request, *response;
struct snp_msg_desc *msg_desc;
/*
* Avoid information leakage by double-buffering shared messages
* in fields that are in regular encrypted memory.
*/
struct snp_guest_msg secret_request, secret_response;
struct snp_secrets_page *secrets;
struct snp_req_data input;
union {
struct snp_report_req report;
struct snp_derived_key_req derived_key;
struct snp_ext_report_req ext_report;
} req;
u32 *os_area_msg_seqno;
u8 *vmpck;
};
/*
@ -85,12 +63,12 @@ MODULE_PARM_DESC(vmpck_id, "The VMPCK ID to use when communicating with the PSP.
/* Mutex to serialize the shared buffer access and command handling. */
static DEFINE_MUTEX(snp_cmd_mutex);
static bool is_vmpck_empty(struct snp_guest_dev *snp_dev)
static bool is_vmpck_empty(struct snp_msg_desc *mdesc)
{
char zero_key[VMPCK_KEY_LEN] = {0};
if (snp_dev->vmpck)
return !memcmp(snp_dev->vmpck, zero_key, VMPCK_KEY_LEN);
if (mdesc->vmpck)
return !memcmp(mdesc->vmpck, zero_key, VMPCK_KEY_LEN);
return true;
}
@ -112,30 +90,30 @@ static bool is_vmpck_empty(struct snp_guest_dev *snp_dev)
* vulnerable. If the sequence number were incremented for a fresh IV the ASP
* will reject the request.
*/
static void snp_disable_vmpck(struct snp_guest_dev *snp_dev)
static void snp_disable_vmpck(struct snp_msg_desc *mdesc)
{
dev_alert(snp_dev->dev, "Disabling VMPCK%d communication key to prevent IV reuse.\n",
pr_alert("Disabling VMPCK%d communication key to prevent IV reuse.\n",
vmpck_id);
memzero_explicit(snp_dev->vmpck, VMPCK_KEY_LEN);
snp_dev->vmpck = NULL;
memzero_explicit(mdesc->vmpck, VMPCK_KEY_LEN);
mdesc->vmpck = NULL;
}
static inline u64 __snp_get_msg_seqno(struct snp_guest_dev *snp_dev)
static inline u64 __snp_get_msg_seqno(struct snp_msg_desc *mdesc)
{
u64 count;
lockdep_assert_held(&snp_cmd_mutex);
/* Read the current message sequence counter from secrets pages */
count = *snp_dev->os_area_msg_seqno;
count = *mdesc->os_area_msg_seqno;
return count + 1;
}
/* Return a non-zero on success */
static u64 snp_get_msg_seqno(struct snp_guest_dev *snp_dev)
static u64 snp_get_msg_seqno(struct snp_msg_desc *mdesc)
{
u64 count = __snp_get_msg_seqno(snp_dev);
u64 count = __snp_get_msg_seqno(mdesc);
/*
* The message sequence counter for the SNP guest request is a 64-bit
@ -146,20 +124,20 @@ static u64 snp_get_msg_seqno(struct snp_guest_dev *snp_dev)
* invalid number and will fail the message request.
*/
if (count >= UINT_MAX) {
dev_err(snp_dev->dev, "request message sequence counter overflow\n");
pr_err("request message sequence counter overflow\n");
return 0;
}
return count;
}
static void snp_inc_msg_seqno(struct snp_guest_dev *snp_dev)
static void snp_inc_msg_seqno(struct snp_msg_desc *mdesc)
{
/*
* The counter is also incremented by the PSP, so increment it by 2
* and save in secrets page.
*/
*snp_dev->os_area_msg_seqno += 2;
*mdesc->os_area_msg_seqno += 2;
}
static inline struct snp_guest_dev *to_snp_dev(struct file *file)
@ -169,139 +147,38 @@ static inline struct snp_guest_dev *to_snp_dev(struct file *file)
return container_of(dev, struct snp_guest_dev, misc);
}
static struct snp_guest_crypto *init_crypto(struct snp_guest_dev *snp_dev, u8 *key, size_t keylen)
static struct aesgcm_ctx *snp_init_crypto(u8 *key, size_t keylen)
{
struct snp_guest_crypto *crypto;
struct aesgcm_ctx *ctx;
crypto = kzalloc(sizeof(*crypto), GFP_KERNEL_ACCOUNT);
if (!crypto)
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL_ACCOUNT);
if (!ctx)
return NULL;
crypto->tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
if (IS_ERR(crypto->tfm))
goto e_free;
if (crypto_aead_setkey(crypto->tfm, key, keylen))
goto e_free_crypto;
crypto->iv_len = crypto_aead_ivsize(crypto->tfm);
crypto->iv = kmalloc(crypto->iv_len, GFP_KERNEL_ACCOUNT);
if (!crypto->iv)
goto e_free_crypto;
if (crypto_aead_authsize(crypto->tfm) > MAX_AUTHTAG_LEN) {
if (crypto_aead_setauthsize(crypto->tfm, MAX_AUTHTAG_LEN)) {
dev_err(snp_dev->dev, "failed to set authsize to %d\n", MAX_AUTHTAG_LEN);
goto e_free_iv;
}
if (aesgcm_expandkey(ctx, key, keylen, AUTHTAG_LEN)) {
pr_err("Crypto context initialization failed\n");
kfree(ctx);
return NULL;
}
crypto->a_len = crypto_aead_authsize(crypto->tfm);
crypto->authtag = kmalloc(crypto->a_len, GFP_KERNEL_ACCOUNT);
if (!crypto->authtag)
goto e_free_iv;
return crypto;
e_free_iv:
kfree(crypto->iv);
e_free_crypto:
crypto_free_aead(crypto->tfm);
e_free:
kfree(crypto);
return NULL;
return ctx;
}
static void deinit_crypto(struct snp_guest_crypto *crypto)
static int verify_and_dec_payload(struct snp_msg_desc *mdesc, struct snp_guest_req *req)
{
crypto_free_aead(crypto->tfm);
kfree(crypto->iv);
kfree(crypto->authtag);
kfree(crypto);
}
static int enc_dec_message(struct snp_guest_crypto *crypto, struct snp_guest_msg *msg,
u8 *src_buf, u8 *dst_buf, size_t len, bool enc)
{
struct snp_guest_msg_hdr *hdr = &msg->hdr;
struct scatterlist src[3], dst[3];
DECLARE_CRYPTO_WAIT(wait);
struct aead_request *req;
int ret;
req = aead_request_alloc(crypto->tfm, GFP_KERNEL);
if (!req)
return -ENOMEM;
/*
* AEAD memory operations:
* +------ AAD -------+------- DATA -----+---- AUTHTAG----+
* | msg header | plaintext | hdr->authtag |
* | bytes 30h - 5Fh | or | |
* | | cipher | |
* +------------------+------------------+----------------+
*/
sg_init_table(src, 3);
sg_set_buf(&src[0], &hdr->algo, AAD_LEN);
sg_set_buf(&src[1], src_buf, hdr->msg_sz);
sg_set_buf(&src[2], hdr->authtag, crypto->a_len);
sg_init_table(dst, 3);
sg_set_buf(&dst[0], &hdr->algo, AAD_LEN);
sg_set_buf(&dst[1], dst_buf, hdr->msg_sz);
sg_set_buf(&dst[2], hdr->authtag, crypto->a_len);
aead_request_set_ad(req, AAD_LEN);
aead_request_set_tfm(req, crypto->tfm);
aead_request_set_callback(req, 0, crypto_req_done, &wait);
aead_request_set_crypt(req, src, dst, len, crypto->iv);
ret = crypto_wait_req(enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req), &wait);
aead_request_free(req);
return ret;
}
static int __enc_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg,
void *plaintext, size_t len)
{
struct snp_guest_crypto *crypto = snp_dev->crypto;
struct snp_guest_msg_hdr *hdr = &msg->hdr;
memset(crypto->iv, 0, crypto->iv_len);
memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno));
return enc_dec_message(crypto, msg, plaintext, msg->payload, len, true);
}
static int dec_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg,
void *plaintext, size_t len)
{
struct snp_guest_crypto *crypto = snp_dev->crypto;
struct snp_guest_msg_hdr *hdr = &msg->hdr;
/* Build IV with response buffer sequence number */
memset(crypto->iv, 0, crypto->iv_len);
memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno));
return enc_dec_message(crypto, msg, msg->payload, plaintext, len, false);
}
static int verify_and_dec_payload(struct snp_guest_dev *snp_dev, void *payload, u32 sz)
{
struct snp_guest_crypto *crypto = snp_dev->crypto;
struct snp_guest_msg *resp_msg = &snp_dev->secret_response;
struct snp_guest_msg *req_msg = &snp_dev->secret_request;
struct snp_guest_msg *resp_msg = &mdesc->secret_response;
struct snp_guest_msg *req_msg = &mdesc->secret_request;
struct snp_guest_msg_hdr *req_msg_hdr = &req_msg->hdr;
struct snp_guest_msg_hdr *resp_msg_hdr = &resp_msg->hdr;
struct aesgcm_ctx *ctx = mdesc->ctx;
u8 iv[GCM_AES_IV_SIZE] = {};
pr_debug("response [seqno %lld type %d version %d sz %d]\n",
resp_msg_hdr->msg_seqno, resp_msg_hdr->msg_type, resp_msg_hdr->msg_version,
resp_msg_hdr->msg_sz);
/* Copy response from shared memory to encrypted memory. */
memcpy(resp_msg, snp_dev->response, sizeof(*resp_msg));
memcpy(resp_msg, mdesc->response, sizeof(*resp_msg));
/* Verify that the sequence counter is incremented by 1 */
if (unlikely(resp_msg_hdr->msg_seqno != (req_msg_hdr->msg_seqno + 1)))
@ -316,29 +193,35 @@ static int verify_and_dec_payload(struct snp_guest_dev *snp_dev, void *payload,
* If the message size is greater than our buffer length then return
* an error.
*/
if (unlikely((resp_msg_hdr->msg_sz + crypto->a_len) > sz))
if (unlikely((resp_msg_hdr->msg_sz + ctx->authsize) > req->resp_sz))
return -EBADMSG;
/* Decrypt the payload */
return dec_payload(snp_dev, resp_msg, payload, resp_msg_hdr->msg_sz + crypto->a_len);
memcpy(iv, &resp_msg_hdr->msg_seqno, min(sizeof(iv), sizeof(resp_msg_hdr->msg_seqno)));
if (!aesgcm_decrypt(ctx, req->resp_buf, resp_msg->payload, resp_msg_hdr->msg_sz,
&resp_msg_hdr->algo, AAD_LEN, iv, resp_msg_hdr->authtag))
return -EBADMSG;
return 0;
}
static int enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8 type,
void *payload, size_t sz)
static int enc_payload(struct snp_msg_desc *mdesc, u64 seqno, struct snp_guest_req *req)
{
struct snp_guest_msg *msg = &snp_dev->secret_request;
struct snp_guest_msg *msg = &mdesc->secret_request;
struct snp_guest_msg_hdr *hdr = &msg->hdr;
struct aesgcm_ctx *ctx = mdesc->ctx;
u8 iv[GCM_AES_IV_SIZE] = {};
memset(msg, 0, sizeof(*msg));
hdr->algo = SNP_AEAD_AES_256_GCM;
hdr->hdr_version = MSG_HDR_VER;
hdr->hdr_sz = sizeof(*hdr);
hdr->msg_type = type;
hdr->msg_version = version;
hdr->msg_type = req->msg_type;
hdr->msg_version = req->msg_version;
hdr->msg_seqno = seqno;
hdr->msg_vmpck = vmpck_id;
hdr->msg_sz = sz;
hdr->msg_vmpck = req->vmpck_id;
hdr->msg_sz = req->req_sz;
/* Verify the sequence number is non-zero */
if (!hdr->msg_seqno)
@ -347,10 +230,17 @@ static int enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8
pr_debug("request [seqno %lld type %d version %d sz %d]\n",
hdr->msg_seqno, hdr->msg_type, hdr->msg_version, hdr->msg_sz);
return __enc_payload(snp_dev, msg, payload, sz);
if (WARN_ON((req->req_sz + ctx->authsize) > sizeof(msg->payload)))
return -EBADMSG;
memcpy(iv, &hdr->msg_seqno, min(sizeof(iv), sizeof(hdr->msg_seqno)));
aesgcm_encrypt(ctx, msg->payload, req->req_buf, req->req_sz, &hdr->algo,
AAD_LEN, iv, hdr->authtag);
return 0;
}
static int __handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
static int __handle_guest_request(struct snp_msg_desc *mdesc, struct snp_guest_req *req,
struct snp_guest_request_ioctl *rio)
{
unsigned long req_start = jiffies;
@ -365,7 +255,7 @@ static int __handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
* sequence number must be incremented or the VMPCK must be deleted to
* prevent reuse of the IV.
*/
rc = snp_issue_guest_request(exit_code, &snp_dev->input, rio);
rc = snp_issue_guest_request(req, &mdesc->input, rio);
switch (rc) {
case -ENOSPC:
/*
@ -375,8 +265,8 @@ static int __handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
* order to increment the sequence number and thus avoid
* IV reuse.
*/
override_npages = snp_dev->input.data_npages;
exit_code = SVM_VMGEXIT_GUEST_REQUEST;
override_npages = mdesc->input.data_npages;
req->exit_code = SVM_VMGEXIT_GUEST_REQUEST;
/*
* Override the error to inform callers the given extended
@ -415,7 +305,7 @@ static int __handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
* structure and any failure will wipe the VMPCK, preventing further
* use anyway.
*/
snp_inc_msg_seqno(snp_dev);
snp_inc_msg_seqno(mdesc);
if (override_err) {
rio->exitinfo2 = override_err;
@ -431,29 +321,35 @@ static int __handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
}
if (override_npages)
snp_dev->input.data_npages = override_npages;
mdesc->input.data_npages = override_npages;
return rc;
}
static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
struct snp_guest_request_ioctl *rio, u8 type,
void *req_buf, size_t req_sz, void *resp_buf,
u32 resp_sz)
static int snp_send_guest_request(struct snp_msg_desc *mdesc, struct snp_guest_req *req,
struct snp_guest_request_ioctl *rio)
{
u64 seqno;
int rc;
guard(mutex)(&snp_cmd_mutex);
/* Check if the VMPCK is not empty */
if (is_vmpck_empty(mdesc)) {
pr_err_ratelimited("VMPCK is disabled\n");
return -ENOTTY;
}
/* Get message sequence and verify that its a non-zero */
seqno = snp_get_msg_seqno(snp_dev);
seqno = snp_get_msg_seqno(mdesc);
if (!seqno)
return -EIO;
/* Clear shared memory's response for the host to populate. */
memset(snp_dev->response, 0, sizeof(struct snp_guest_msg));
memset(mdesc->response, 0, sizeof(struct snp_guest_msg));
/* Encrypt the userspace provided payload in snp_dev->secret_request. */
rc = enc_payload(snp_dev, seqno, rio->msg_version, type, req_buf, req_sz);
/* Encrypt the userspace provided payload in mdesc->secret_request. */
rc = enc_payload(mdesc, seqno, req);
if (rc)
return rc;
@ -461,27 +357,26 @@ static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
* Write the fully encrypted request to the shared unencrypted
* request page.
*/
memcpy(snp_dev->request, &snp_dev->secret_request,
sizeof(snp_dev->secret_request));
memcpy(mdesc->request, &mdesc->secret_request,
sizeof(mdesc->secret_request));
rc = __handle_guest_request(snp_dev, exit_code, rio);
rc = __handle_guest_request(mdesc, req, rio);
if (rc) {
if (rc == -EIO &&
rio->exitinfo2 == SNP_GUEST_VMM_ERR(SNP_GUEST_VMM_ERR_INVALID_LEN))
return rc;
dev_alert(snp_dev->dev,
"Detected error from ASP request. rc: %d, exitinfo2: 0x%llx\n",
rc, rio->exitinfo2);
pr_alert("Detected error from ASP request. rc: %d, exitinfo2: 0x%llx\n",
rc, rio->exitinfo2);
snp_disable_vmpck(snp_dev);
snp_disable_vmpck(mdesc);
return rc;
}
rc = verify_and_dec_payload(snp_dev, resp_buf, resp_sz);
rc = verify_and_dec_payload(mdesc, req);
if (rc) {
dev_alert(snp_dev->dev, "Detected unexpected decode failure from ASP. rc: %d\n", rc);
snp_disable_vmpck(snp_dev);
pr_alert("Detected unexpected decode failure from ASP. rc: %d\n", rc);
snp_disable_vmpck(mdesc);
return rc;
}
@ -495,13 +390,12 @@ struct snp_req_resp {
static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
{
struct snp_guest_crypto *crypto = snp_dev->crypto;
struct snp_report_req *report_req = &snp_dev->req.report;
struct snp_msg_desc *mdesc = snp_dev->msg_desc;
struct snp_report_resp *report_resp;
struct snp_guest_req req = {};
int rc, resp_len;
lockdep_assert_held(&snp_cmd_mutex);
if (!arg->req_data || !arg->resp_data)
return -EINVAL;
@ -513,13 +407,21 @@ static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_io
* response payload. Make sure that it has enough space to cover the
* authtag.
*/
resp_len = sizeof(report_resp->data) + crypto->a_len;
resp_len = sizeof(report_resp->data) + mdesc->ctx->authsize;
report_resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT);
if (!report_resp)
return -ENOMEM;
rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg, SNP_MSG_REPORT_REQ,
report_req, sizeof(*report_req), report_resp->data, resp_len);
req.msg_version = arg->msg_version;
req.msg_type = SNP_MSG_REPORT_REQ;
req.vmpck_id = vmpck_id;
req.req_buf = report_req;
req.req_sz = sizeof(*report_req);
req.resp_buf = report_resp->data;
req.resp_sz = resp_len;
req.exit_code = SVM_VMGEXIT_GUEST_REQUEST;
rc = snp_send_guest_request(mdesc, &req, arg);
if (rc)
goto e_free;
@ -534,14 +436,13 @@ static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_io
static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
{
struct snp_derived_key_req *derived_key_req = &snp_dev->req.derived_key;
struct snp_guest_crypto *crypto = snp_dev->crypto;
struct snp_derived_key_resp derived_key_resp = {0};
struct snp_msg_desc *mdesc = snp_dev->msg_desc;
struct snp_guest_req req = {};
int rc, resp_len;
/* Response data is 64 bytes and max authsize for GCM is 16 bytes. */
u8 buf[64 + 16];
lockdep_assert_held(&snp_cmd_mutex);
if (!arg->req_data || !arg->resp_data)
return -EINVAL;
@ -550,7 +451,7 @@ static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_reque
* response payload. Make sure that it has enough space to cover the
* authtag.
*/
resp_len = sizeof(derived_key_resp.data) + crypto->a_len;
resp_len = sizeof(derived_key_resp.data) + mdesc->ctx->authsize;
if (sizeof(buf) < resp_len)
return -ENOMEM;
@ -558,8 +459,16 @@ static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_reque
sizeof(*derived_key_req)))
return -EFAULT;
rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg, SNP_MSG_KEY_REQ,
derived_key_req, sizeof(*derived_key_req), buf, resp_len);
req.msg_version = arg->msg_version;
req.msg_type = SNP_MSG_KEY_REQ;
req.vmpck_id = vmpck_id;
req.req_buf = derived_key_req;
req.req_sz = sizeof(*derived_key_req);
req.resp_buf = buf;
req.resp_sz = resp_len;
req.exit_code = SVM_VMGEXIT_GUEST_REQUEST;
rc = snp_send_guest_request(mdesc, &req, arg);
if (rc)
return rc;
@ -579,13 +488,12 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques
{
struct snp_ext_report_req *report_req = &snp_dev->req.ext_report;
struct snp_guest_crypto *crypto = snp_dev->crypto;
struct snp_msg_desc *mdesc = snp_dev->msg_desc;
struct snp_report_resp *report_resp;
struct snp_guest_req req = {};
int ret, npages = 0, resp_len;
sockptr_t certs_address;
lockdep_assert_held(&snp_cmd_mutex);
if (sockptr_is_null(io->req_data) || sockptr_is_null(io->resp_data))
return -EINVAL;
@ -614,7 +522,7 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques
* the host. If host does not supply any certs in it, then copy
* zeros to indicate that certificate data was not provided.
*/
memset(snp_dev->certs_data, 0, report_req->certs_len);
memset(mdesc->certs_data, 0, report_req->certs_len);
npages = report_req->certs_len >> PAGE_SHIFT;
cmd:
/*
@ -622,19 +530,27 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques
* response payload. Make sure that it has enough space to cover the
* authtag.
*/
resp_len = sizeof(report_resp->data) + crypto->a_len;
resp_len = sizeof(report_resp->data) + mdesc->ctx->authsize;
report_resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT);
if (!report_resp)
return -ENOMEM;
snp_dev->input.data_npages = npages;
ret = handle_guest_request(snp_dev, SVM_VMGEXIT_EXT_GUEST_REQUEST, arg, SNP_MSG_REPORT_REQ,
&report_req->data, sizeof(report_req->data),
report_resp->data, resp_len);
mdesc->input.data_npages = npages;
req.msg_version = arg->msg_version;
req.msg_type = SNP_MSG_REPORT_REQ;
req.vmpck_id = vmpck_id;
req.req_buf = &report_req->data;
req.req_sz = sizeof(report_req->data);
req.resp_buf = report_resp->data;
req.resp_sz = resp_len;
req.exit_code = SVM_VMGEXIT_EXT_GUEST_REQUEST;
ret = snp_send_guest_request(mdesc, &req, arg);
/* If certs length is invalid then copy the returned length */
if (arg->vmm_error == SNP_GUEST_VMM_ERR_INVALID_LEN) {
report_req->certs_len = snp_dev->input.data_npages << PAGE_SHIFT;
report_req->certs_len = mdesc->input.data_npages << PAGE_SHIFT;
if (copy_to_sockptr(io->req_data, report_req, sizeof(*report_req)))
ret = -EFAULT;
@ -643,7 +559,7 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques
if (ret)
goto e_free;
if (npages && copy_to_sockptr(certs_address, snp_dev->certs_data, report_req->certs_len)) {
if (npages && copy_to_sockptr(certs_address, mdesc->certs_data, report_req->certs_len)) {
ret = -EFAULT;
goto e_free;
}
@ -673,15 +589,6 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long
if (!input.msg_version)
return -EINVAL;
mutex_lock(&snp_cmd_mutex);
/* Check if the VMPCK is not empty */
if (is_vmpck_empty(snp_dev)) {
dev_err_ratelimited(snp_dev->dev, "VMPCK is disabled\n");
mutex_unlock(&snp_cmd_mutex);
return -ENOTTY;
}
switch (ioctl) {
case SNP_GET_REPORT:
ret = get_report(snp_dev, &input);
@ -703,8 +610,6 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long
break;
}
mutex_unlock(&snp_cmd_mutex);
if (input.exitinfo2 && copy_to_user(argp, &input, sizeof(input)))
return -EFAULT;
@ -819,8 +724,6 @@ static int sev_svsm_report_new(struct tsm_report *report, void *data)
man_len = SZ_4K;
certs_len = SEV_FW_BLOB_MAX_SIZE;
guard(mutex)(&snp_cmd_mutex);
if (guid_is_null(&desc->service_guid)) {
call_id = SVSM_ATTEST_CALL(SVSM_ATTEST_SERVICES);
} else {
@ -955,14 +858,6 @@ static int sev_report_new(struct tsm_report *report, void *data)
if (!buf)
return -ENOMEM;
guard(mutex)(&snp_cmd_mutex);
/* Check if the VMPCK is not empty */
if (is_vmpck_empty(snp_dev)) {
dev_err_ratelimited(snp_dev->dev, "VMPCK is disabled\n");
return -ENOTTY;
}
cert_table = buf + report_size;
struct snp_ext_report_req ext_req = {
.data = { .vmpl = desc->privlevel },
@ -1088,6 +983,7 @@ static int __init sev_guest_probe(struct platform_device *pdev)
struct snp_secrets_page *secrets;
struct device *dev = &pdev->dev;
struct snp_guest_dev *snp_dev;
struct snp_msg_desc *mdesc;
struct miscdevice *misc;
void __iomem *mapping;
int ret;
@ -1112,43 +1008,47 @@ static int __init sev_guest_probe(struct platform_device *pdev)
if (!snp_dev)
goto e_unmap;
mdesc = devm_kzalloc(&pdev->dev, sizeof(struct snp_msg_desc), GFP_KERNEL);
if (!mdesc)
goto e_unmap;
/* Adjust the default VMPCK key based on the executing VMPL level */
if (vmpck_id == -1)
vmpck_id = snp_vmpl;
ret = -EINVAL;
snp_dev->vmpck = get_vmpck(vmpck_id, secrets, &snp_dev->os_area_msg_seqno);
if (!snp_dev->vmpck) {
mdesc->vmpck = get_vmpck(vmpck_id, secrets, &mdesc->os_area_msg_seqno);
if (!mdesc->vmpck) {
dev_err(dev, "Invalid VMPCK%d communication key\n", vmpck_id);
goto e_unmap;
}
/* Verify that VMPCK is not zero. */
if (is_vmpck_empty(snp_dev)) {
if (is_vmpck_empty(mdesc)) {
dev_err(dev, "Empty VMPCK%d communication key\n", vmpck_id);
goto e_unmap;
}
platform_set_drvdata(pdev, snp_dev);
snp_dev->dev = dev;
snp_dev->secrets = secrets;
mdesc->secrets = secrets;
/* Allocate the shared page used for the request and response message. */
snp_dev->request = alloc_shared_pages(dev, sizeof(struct snp_guest_msg));
if (!snp_dev->request)
mdesc->request = alloc_shared_pages(dev, sizeof(struct snp_guest_msg));
if (!mdesc->request)
goto e_unmap;
snp_dev->response = alloc_shared_pages(dev, sizeof(struct snp_guest_msg));
if (!snp_dev->response)
mdesc->response = alloc_shared_pages(dev, sizeof(struct snp_guest_msg));
if (!mdesc->response)
goto e_free_request;
snp_dev->certs_data = alloc_shared_pages(dev, SEV_FW_BLOB_MAX_SIZE);
if (!snp_dev->certs_data)
mdesc->certs_data = alloc_shared_pages(dev, SEV_FW_BLOB_MAX_SIZE);
if (!mdesc->certs_data)
goto e_free_response;
ret = -EIO;
snp_dev->crypto = init_crypto(snp_dev, snp_dev->vmpck, VMPCK_KEY_LEN);
if (!snp_dev->crypto)
mdesc->ctx = snp_init_crypto(mdesc->vmpck, VMPCK_KEY_LEN);
if (!mdesc->ctx)
goto e_free_cert_data;
misc = &snp_dev->misc;
@ -1156,10 +1056,10 @@ static int __init sev_guest_probe(struct platform_device *pdev)
misc->name = DEVICE_NAME;
misc->fops = &snp_guest_fops;
/* initial the input address for guest request */
snp_dev->input.req_gpa = __pa(snp_dev->request);
snp_dev->input.resp_gpa = __pa(snp_dev->response);
snp_dev->input.data_gpa = __pa(snp_dev->certs_data);
/* Initialize the input addresses for guest request */
mdesc->input.req_gpa = __pa(mdesc->request);
mdesc->input.resp_gpa = __pa(mdesc->response);
mdesc->input.data_gpa = __pa(mdesc->certs_data);
/* Set the privlevel_floor attribute based on the vmpck_id */
sev_tsm_ops.privlevel_floor = vmpck_id;
@ -1174,17 +1074,20 @@ static int __init sev_guest_probe(struct platform_device *pdev)
ret = misc_register(misc);
if (ret)
goto e_free_cert_data;
goto e_free_ctx;
snp_dev->msg_desc = mdesc;
dev_info(dev, "Initialized SEV guest driver (using VMPCK%d communication key)\n", vmpck_id);
return 0;
e_free_ctx:
kfree(mdesc->ctx);
e_free_cert_data:
free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE);
free_shared_pages(mdesc->certs_data, SEV_FW_BLOB_MAX_SIZE);
e_free_response:
free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg));
free_shared_pages(mdesc->response, sizeof(struct snp_guest_msg));
e_free_request:
free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg));
free_shared_pages(mdesc->request, sizeof(struct snp_guest_msg));
e_unmap:
iounmap(mapping);
return ret;
@ -1193,11 +1096,12 @@ static int __init sev_guest_probe(struct platform_device *pdev)
static void __exit sev_guest_remove(struct platform_device *pdev)
{
struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev);
struct snp_msg_desc *mdesc = snp_dev->msg_desc;
free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE);
free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg));
free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg));
deinit_crypto(snp_dev->crypto);
free_shared_pages(mdesc->certs_data, SEV_FW_BLOB_MAX_SIZE);
free_shared_pages(mdesc->response, sizeof(struct snp_guest_msg));
free_shared_pages(mdesc->request, sizeof(struct snp_guest_msg));
kfree(mdesc->ctx);
misc_deregister(&snp_dev->misc);
}