The command is used to bootstrap SEV guest from unencrypted boot images. The command creates a new VM encryption key (VEK) using the guest owner's public DH certificates, and session data. The VEK will be used to encrypt the guest memory. Signed-off-by: Brijesh Singh <brijesh.singh@xxxxxxx> --- arch/x86/kvm/svm.c | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/svm.c b/arch/x86/kvm/svm.c index fb63398..b5fa8c0 100644 --- a/arch/x86/kvm/svm.c +++ b/arch/x86/kvm/svm.c @@ -37,6 +37,7 @@ #include <linux/amd-iommu.h> #include <linux/hashtable.h> #include <linux/psp-sev.h> +#include <linux/file.h> #include <asm/apic.h> #include <asm/perf_event.h> @@ -497,6 +498,10 @@ static inline bool gif_set(struct vcpu_svm *svm) /* Secure Encrypted Virtualization */ static unsigned int max_sev_asid; static unsigned long *sev_asid_bitmap; +static void sev_deactivate_handle(struct kvm *kvm); +static void sev_decommission_handle(struct kvm *kvm); +static int sev_asid_new(void); +static void sev_asid_free(int asid); static bool kvm_sev_enabled(void) { @@ -1534,6 +1539,17 @@ static inline int avic_free_vm_id(int id) return 0; } +static void sev_vm_destroy(struct kvm *kvm) +{ + if (!sev_guest(kvm)) + return; + + /* release the firmware resources */ + sev_deactivate_handle(kvm); + sev_decommission_handle(kvm); + sev_asid_free(sev_get_asid(kvm)); +} + static void avic_vm_destroy(struct kvm *kvm) { unsigned long flags; @@ -1551,6 +1567,12 @@ static void avic_vm_destroy(struct kvm *kvm) spin_unlock_irqrestore(&svm_vm_data_hash_lock, flags); } +static void svm_vm_destroy(struct kvm *kvm) +{ + avic_vm_destroy(kvm); + sev_vm_destroy(kvm); +} + static int avic_vm_init(struct kvm *kvm) { unsigned long flags; @@ -5502,6 +5524,282 @@ static inline void avic_post_state_restore(struct kvm_vcpu *vcpu) avic_handle_ldr_update(vcpu); } +static int sev_asid_new(void) +{ + int pos; + + if (!max_sev_asid) + return -EINVAL; + + pos = find_first_zero_bit(sev_asid_bitmap, max_sev_asid); + if (pos >= max_sev_asid) + return -EBUSY; + + set_bit(pos, sev_asid_bitmap); + return pos + 1; +} + +static void sev_asid_free(int asid) +{ + int cpu, pos; + struct svm_cpu_data *sd; + + pos = asid - 1; + clear_bit(pos, sev_asid_bitmap); + + for_each_possible_cpu(cpu) { + sd = per_cpu(svm_data, cpu); + sd->sev_vmcbs[pos] = NULL; + } +} + +static int sev_issue_cmd(struct kvm *kvm, int id, void *data, int *error) +{ + int ret; + struct fd f; + int fd = sev_get_fd(kvm); + + f = fdget(fd); + if (!f.file) + return -EBADF; + + ret = sev_issue_cmd_external_user(f.file, id, data, 0, error); + fdput(f); + + return ret; +} + +static void sev_decommission_handle(struct kvm *kvm) +{ + int ret, error; + struct sev_data_decommission *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return; + + data->handle = sev_get_handle(kvm); + ret = sev_guest_decommission(data, &error); + if (ret) + pr_err("SEV: DECOMMISSION %d (%#x)\n", ret, error); + + kfree(data); +} + +static void sev_deactivate_handle(struct kvm *kvm) +{ + int ret, error; + struct sev_data_deactivate *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return; + + data->handle = sev_get_handle(kvm); + ret = sev_guest_deactivate(data, &error); + if (ret) { + pr_err("SEV: DEACTIVATE %d (%#x)\n", ret, error); + goto buffer_free; + } + + wbinvd_on_all_cpus(); + + ret = sev_guest_df_flush(&error); + if (ret) + pr_err("SEV: DF_FLUSH %d (%#x)\n", ret, error); + +buffer_free: + kfree(data); +} + +static int sev_activate_asid(unsigned int handle, int asid, int *error) +{ + int ret; + struct sev_data_activate *data; + + wbinvd_on_all_cpus(); + + ret = sev_guest_df_flush(error); + if (ret) { + pr_err("SEV: DF_FLUSH %d (%#x)\n", ret, *error); + return ret; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->handle = handle; + data->asid = asid; + ret = sev_guest_activate(data, error); + if (ret) + pr_err("SEV: ACTIVATE %d (%#x)\n", ret, *error); + + kfree(data); + return ret; +} + +static int sev_pre_start(struct kvm *kvm, int *asid) +{ + int ret; + + /* If guest has active SEV handle then deactivate before creating the + * encryption context. + */ + if (sev_guest(kvm)) { + sev_deactivate_handle(kvm); + sev_decommission_handle(kvm); + *asid = sev_get_asid(kvm); /* reuse the asid */ + ret = 0; + } else { + /* Allocate new asid for this launch */ + ret = sev_asid_new(); + if (ret < 0) { + pr_err("SEV: failed to get free asid\n"); + return ret; + } + *asid = ret; + ret = 0; + } + + return ret; +} + +static int sev_post_start(struct kvm *kvm, int asid, int handle, + int sev_fd, int *error) +{ + int ret; + + /* activate asid */ + ret = sev_activate_asid(handle, asid, error); + if (ret) + return ret; + + kvm->arch.sev_info.handle = handle; + kvm->arch.sev_info.asid = asid; + kvm->arch.sev_info.sev_fd = sev_fd; + + return 0; +} + +static int sev_launch_start(struct kvm *kvm, struct kvm_sev_cmd *argp) +{ + int ret, asid = 0; + void *dh_cert_addr = NULL; + void *session_addr = NULL; + struct kvm_sev_launch_start params; + struct sev_data_launch_start *start; + int *error = &argp->error; + struct fd f; + + f = fdget(argp->sev_fd); + if (!f.file) + return -EBADF; + + /* Get parameter from the user */ + ret = -EFAULT; + if (copy_from_user(¶ms, (void *)argp->data, + sizeof(struct kvm_sev_launch_start))) + goto err_1; + + ret = -ENOMEM; + start = kzalloc(sizeof(*start), GFP_KERNEL); + if (!start) + goto err_1; + + ret = sev_pre_start(kvm, &asid); + if (ret) + goto err_2; + + start->handle = params.handle; + start->policy = params.policy; + + /* Copy DH certificate from userspace */ + if (params.dh_cert_length && params.dh_cert_data) { + dh_cert_addr = kmalloc(params.dh_cert_length, GFP_KERNEL); + if (!dh_cert_addr) { + ret = -EFAULT; + goto err_3; + } + if (copy_from_user(dh_cert_addr, (void *)params.dh_cert_data, + params.dh_cert_length)) { + ret = -EFAULT; + goto err_3; + } + + start->dh_cert_address = __psp_pa(dh_cert_addr); + start->dh_cert_length = params.dh_cert_length; + } + + /* Copy session data from userspace */ + if (params.session_length && params.session_data) { + session_addr = kmalloc(params.dh_cert_length, GFP_KERNEL); + if (!session_addr) { + ret = -EFAULT; + goto err_3; + } + if (copy_from_user(session_addr, (void *)params.session_data, + params.session_length)) { + ret = -EFAULT; + goto err_3; + } + start->session_data_address = __psp_pa(session_addr); + start->session_data_length = params.session_length; + } + + /* launch start */ + ret = sev_issue_cmd_external_user(f.file, SEV_CMD_LAUNCH_START, + start, 0, error); + if (ret) { + pr_err("SEV: LAUNCH_START ret=%d (%#010x)\n", ret, *error); + goto err_3; + } + + ret = sev_post_start(kvm, asid, start->handle, argp->sev_fd, error); + if (ret) + goto err_3; + + params.handle = start->handle; + if (copy_to_user((void *) argp->data, ¶ms, + sizeof(struct kvm_sev_launch_start))) + ret = -EFAULT; +err_3: + if (ret && asid) /* free asid if we have encountered error */ + sev_asid_free(asid); + kfree(dh_cert_addr); + kfree(session_addr); +err_2: + kfree(start); +err_1: + fdput(f); + return ret; +} + +static int amd_memory_encryption_cmd(struct kvm *kvm, void __user *argp) +{ + int r = -ENOTTY; + struct kvm_sev_cmd sev_cmd; + + if (copy_from_user(&sev_cmd, argp, sizeof(struct kvm_sev_cmd))) + return -EFAULT; + + mutex_lock(&kvm->lock); + + switch (sev_cmd.id) { + case KVM_SEV_LAUNCH_START: { + r = sev_launch_start(kvm, &sev_cmd); + break; + } + default: + break; + } + + mutex_unlock(&kvm->lock); + if (copy_to_user(argp, &sev_cmd, sizeof(struct kvm_sev_cmd))) + r = -EFAULT; + return r; +} + static struct kvm_x86_ops svm_x86_ops __ro_after_init = { .cpu_has_kvm_support = has_svm, .disabled_by_bios = is_disabled, @@ -5518,7 +5816,7 @@ static struct kvm_x86_ops svm_x86_ops __ro_after_init = { .vcpu_reset = svm_vcpu_reset, .vm_init = avic_vm_init, - .vm_destroy = avic_vm_destroy, + .vm_destroy = svm_vm_destroy, .prepare_guest_switch = svm_prepare_guest_switch, .vcpu_load = svm_vcpu_load, @@ -5617,6 +5915,8 @@ static struct kvm_x86_ops svm_x86_ops __ro_after_init = { .pmu_ops = &amd_pmu_ops, .deliver_posted_interrupt = svm_deliver_avic_intr, .update_pi_irte = svm_update_pi_irte, + + .memory_encryption_op = amd_memory_encryption_cmd, }; static int __init svm_init(void) -- To unsubscribe from this list: send the line "unsubscribe linux-efi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html