--- v4
+++ v5
@@ -1,363 +1,172 @@
-Version 2 of GHCB specification added the support two SNP Guest Request
-Message NAE event. The events allows for an SEV-SNP guest to make request
-to the SEV-SNP firmware through hypervisor using the SNP_GUEST_REQUEST
-API define in the SEV-SNP firmware specification.
-
-The SNP_GUEST_REQUEST requires two unique pages, one page for the request
-and one page for the response. The response page need to be in the firmware
-state. The GHCB specification says that both the pages need to be in the
-hypervisor state but before executing the SEV-SNP command the response page
-need to be in the firmware state.
-
-The SNP_EXT_GUEST_REQUEST is similar to SNP_GUEST_REQUEST with the
-difference of an additional certificate blob that can be passed through
-the SNP_SET_CONFIG ioctl defined in the CCP driver. The CCP driver exposes
-snp_guest_ext_guest_request() that is used by the KVM to get the both
-report and additional data at once.
-
-In order to minimize the page state transition during the command handling,
-pre-allocate a firmware page on guest creation. Use the pre-allocated
-firmware page to complete the command execution and copy the result in the
-guest response page.
-
-Ratelimit the handling of SNP_GUEST_REQUEST NAE to avoid the possibility
-of a guest creating a denial of service attack aginst the SNP firmware.
-
-Now that KVM supports all the VMGEXIT NAEs required for the base SEV-SNP
-feature, set the hypervisor feature to advertise it.
+SEV-SNP VMs can ask the hypervisor to change the page state in the RMP
+table to be private or shared using the Page State Change NAE event
+as defined in the GHCB specification version 2.
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
- arch/x86/kvm/svm/sev.c | 223 ++++++++++++++++++++++++++++++++++++++++-
- arch/x86/kvm/svm/svm.h | 6 +-
- 2 files changed, 225 insertions(+), 4 deletions(-)
+ arch/x86/include/asm/sev-common.h | 7 +++
+ arch/x86/kvm/svm/sev.c | 82 +++++++++++++++++++++++++++++--
+ 2 files changed, 84 insertions(+), 5 deletions(-)
+diff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h
+index 4980f77aa1d5..5ee30bb2cdb8 100644
+--- a/arch/x86/include/asm/sev-common.h
++++ b/arch/x86/include/asm/sev-common.h
+@@ -126,6 +126,13 @@ enum psc_op {
+ /* SNP Page State Change NAE event */
+ #define VMGEXIT_PSC_MAX_ENTRY 253
+
++/* The page state change hdr structure in not valid */
++#define PSC_INVALID_HDR 1
++/* The hdr.cur_entry or hdr.end_entry is not valid */
++#define PSC_INVALID_ENTRY 2
++/* Page state change encountered undefined error */
++#define PSC_UNDEF_ERR 3
++
+ struct psc_hdr {
+ u16 cur_entry;
+ u16 end_entry;
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
-index 53a60edc810e..4cb4c1d7e444 100644
+index 6d9483ec91ab..0de85ed63e9b 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
-@@ -18,6 +18,8 @@
- #include <linux/processor.h>
- #include <linux/trace_events.h>
- #include <linux/sev.h>
-+#include <linux/kvm_host.h>
-+#include <linux/sev-guest.h>
- #include <asm/fpu/internal.h>
+@@ -2731,6 +2731,7 @@ static int sev_es_validate_vmgexit(struct vcpu_svm *svm, u64 *exit_code)
+ case SVM_VMGEXIT_AP_JUMP_TABLE:
+ case SVM_VMGEXIT_UNSUPPORTED_EVENT:
+ case SVM_VMGEXIT_HV_FEATURES:
++ case SVM_VMGEXIT_PSC:
+ break;
+ default:
+ goto vmgexit_err;
+@@ -3004,13 +3005,13 @@ static int __snp_handle_page_state_change(struct kvm_vcpu *vcpu, enum psc_op op,
+ */
+ rc = snp_check_and_build_npt(vcpu, gpa, level);
+ if (rc)
+- return -EINVAL;
++ return PSC_UNDEF_ERR;
- #include <asm/trapnr.h>
-@@ -1534,6 +1536,7 @@ static int sev_receive_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)
+ if (op == SNP_PAGE_STATE_PRIVATE) {
+ hva_t hva;
- static void *snp_context_create(struct kvm *kvm, struct kvm_sev_cmd *argp)
- {
-+ struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
- struct sev_data_snp_gctx_create data = {};
- void *context;
- int rc;
-@@ -1543,14 +1546,24 @@ static void *snp_context_create(struct kvm *kvm, struct kvm_sev_cmd *argp)
- if (!context)
- return NULL;
+ if (snp_gpa_to_hva(kvm, gpa, &hva))
+- return -EINVAL;
++ return PSC_UNDEF_ERR;
-- data.gctx_paddr = __psp_pa(context);
-- rc = __sev_issue_cmd(argp->sev_fd, SEV_CMD_SNP_GCTX_CREATE, &data, &argp->error);
-- if (rc) {
-+ /* Allocate a firmware buffer used during the guest command handling. */
-+ sev->snp_resp_page = snp_alloc_firmware_page(GFP_KERNEL_ACCOUNT);
-+ if (!sev->snp_resp_page) {
- snp_free_firmware_page(context);
- return NULL;
- }
+ /*
+ * Verify that the hva range is registered. This enforcement is
+@@ -3022,7 +3023,7 @@ static int __snp_handle_page_state_change(struct kvm_vcpu *vcpu, enum psc_op op,
+ rc = is_hva_registered(kvm, hva, page_level_size(level));
+ mutex_unlock(&kvm->lock);
+ if (!rc)
+- return -EINVAL;
++ return PSC_UNDEF_ERR;
-+ data.gctx_paddr = __psp_pa(context);
-+ rc = __sev_issue_cmd(argp->sev_fd, SEV_CMD_SNP_GCTX_CREATE, &data, &argp->error);
-+ if (rc)
-+ goto e_free;
-+
- return context;
-+
-+e_free:
-+ snp_free_firmware_page(context);
-+ snp_free_firmware_page(sev->snp_resp_page);
-+ return NULL;
- }
+ /*
+ * Mark the userspace range unmerable before adding the pages
+@@ -3032,7 +3033,7 @@ static int __snp_handle_page_state_change(struct kvm_vcpu *vcpu, enum psc_op op,
+ rc = snp_mark_unmergable(kvm, hva, page_level_size(level));
+ mmap_write_unlock(kvm->mm);
+ if (rc)
+- return -EINVAL;
++ return PSC_UNDEF_ERR;
+ }
- static int snp_bind_asid(struct kvm *kvm, int *error)
-@@ -1618,6 +1631,12 @@ static int snp_launch_start(struct kvm *kvm, struct kvm_sev_cmd *argp)
- if (rc)
- goto e_free_context;
+ write_lock(&kvm->mmu_lock);
+@@ -3062,8 +3063,11 @@ static int __snp_handle_page_state_change(struct kvm_vcpu *vcpu, enum psc_op op,
+ case SNP_PAGE_STATE_PRIVATE:
+ rc = rmp_make_private(pfn, gpa, level, sev->asid, false);
+ break;
++ case SNP_PAGE_STATE_PSMASH:
++ case SNP_PAGE_STATE_UNSMASH:
++ /* TODO: Add support to handle it */
+ default:
+- rc = -EINVAL;
++ rc = PSC_INVALID_ENTRY;
+ break;
+ }
-+ /* Used for rate limiting SNP guest message request, use the default settings */
-+ ratelimit_default_init(&sev->snp_guest_msg_rs);
-+
-+ /* Allocate memory used for the certs data in SNP guest request */
-+ sev->snp_certs_data = kmalloc(SEV_FW_BLOB_MAX_SIZE, GFP_KERNEL_ACCOUNT);
-+
- return 0;
-
- e_free_context:
-@@ -2218,6 +2237,9 @@ static int snp_decommission_context(struct kvm *kvm)
- snp_free_firmware_page(sev->snp_context);
- sev->snp_context = NULL;
-
-+ /* Free the response page. */
-+ snp_free_firmware_page(sev->snp_resp_page);
-+
+@@ -3081,6 +3085,65 @@ static int __snp_handle_page_state_change(struct kvm_vcpu *vcpu, enum psc_op op,
return 0;
}
-@@ -2268,6 +2290,9 @@ void sev_vm_destroy(struct kvm *kvm)
- sev_unbind_asid(kvm, sev->handle);
- }
-
++static inline unsigned long map_to_psc_vmgexit_code(int rc)
++{
++ switch (rc) {
++ case PSC_INVALID_HDR:
++ return ((1ul << 32) | 1);
++ case PSC_INVALID_ENTRY:
++ return ((1ul << 32) | 2);
++ case RMPUPDATE_FAIL_OVERLAP:
++ return ((3ul << 32) | 2);
++ default: return (4ul << 32);
++ }
++}
+
-+ kfree(sev->snp_certs_data);
-+
- sev_asid_free(sev);
- }
-
-@@ -2663,6 +2688,8 @@ static int sev_es_validate_vmgexit(struct vcpu_svm *svm)
- case SVM_VMGEXIT_UNSUPPORTED_EVENT:
- case SVM_VMGEXIT_HV_FT:
- case SVM_VMGEXIT_PSC:
-+ case SVM_VMGEXIT_GUEST_REQUEST:
-+ case SVM_VMGEXIT_EXT_GUEST_REQUEST:
- break;
- default:
- goto vmgexit_err;
-@@ -3053,6 +3080,181 @@ static unsigned long snp_handle_psc(struct vcpu_svm *svm, struct ghcb *ghcb)
- return rc ? map_to_psc_vmgexit_code(rc) : 0;
- }
-
-+static int snp_build_guest_buf(struct vcpu_svm *svm, struct sev_data_snp_guest_request *data,
-+ gpa_t req_gpa, gpa_t resp_gpa)
++static unsigned long snp_handle_page_state_change(struct vcpu_svm *svm)
+{
+ struct kvm_vcpu *vcpu = &svm->vcpu;
-+ struct kvm *kvm = vcpu->kvm;
-+ kvm_pfn_t req_pfn, resp_pfn;
-+ struct kvm_sev_info *sev;
++ int level, op, rc = PSC_UNDEF_ERR;
++ struct snp_psc_desc *info;
++ struct psc_entry *entry;
++ u16 cur, end;
++ gpa_t gpa;
+
-+ if (!IS_ALIGNED(req_gpa, PAGE_SIZE) || !IS_ALIGNED(resp_gpa, PAGE_SIZE)) {
-+ pr_err_ratelimited("svm: guest request (%#llx) or response (%#llx) is not page aligned\n",
-+ req_gpa, resp_gpa);
-+ return -EINVAL;
++ if (!sev_snp_guest(vcpu->kvm))
++ return PSC_INVALID_HDR;
++
++ if (!setup_vmgexit_scratch(svm, true, sizeof(*info))) {
++ pr_err("vmgexit: scratch area is not setup.\n");
++ return PSC_INVALID_HDR;
+ }
+
-+ req_pfn = gfn_to_pfn(kvm, gpa_to_gfn(req_gpa));
-+ if (is_error_noslot_pfn(req_pfn)) {
-+ pr_err_ratelimited("svm: guest request invalid gpa=%#llx\n", req_gpa);
-+ return -EINVAL;
++ info = (struct snp_psc_desc *)svm->ghcb_sa;
++ cur = info->hdr.cur_entry;
++ end = info->hdr.end_entry;
++
++ if (cur >= VMGEXIT_PSC_MAX_ENTRY ||
++ end >= VMGEXIT_PSC_MAX_ENTRY || cur > end)
++ return PSC_INVALID_ENTRY;
++
++ for (; cur <= end; cur++) {
++ entry = &info->entries[cur];
++ gpa = gfn_to_gpa(entry->gfn);
++ level = RMP_TO_X86_PG_LEVEL(entry->pagesize);
++ op = entry->operation;
++
++ if (!IS_ALIGNED(gpa, page_level_size(level))) {
++ rc = PSC_INVALID_ENTRY;
++ goto out;
++ }
++
++ rc = __snp_handle_page_state_change(vcpu, op, gpa, level);
++ if (rc)
++ goto out;
+ }
+
-+ resp_pfn = gfn_to_pfn(kvm, gpa_to_gfn(resp_gpa));
-+ if (is_error_noslot_pfn(resp_pfn)) {
-+ pr_err_ratelimited("svm: guest response invalid gpa=%#llx\n", resp_gpa);
-+ return -EINVAL;
-+ }
-+
-+ sev = &to_kvm_svm(kvm)->sev_info;
-+
-+ data->gctx_paddr = __psp_pa(sev->snp_context);
-+ data->req_paddr = __sme_set(req_pfn << PAGE_SHIFT);
-+ data->res_paddr = __psp_pa(sev->snp_resp_page);
-+
-+ return 0;
-+}
-+
-+static void snp_handle_guest_request(struct vcpu_svm *svm, struct ghcb *ghcb,
-+ gpa_t req_gpa, gpa_t resp_gpa)
-+{
-+ struct sev_data_snp_guest_request data = {};
-+ struct kvm_vcpu *vcpu = &svm->vcpu;
-+ struct kvm *kvm = vcpu->kvm;
-+ struct kvm_sev_info *sev;
-+ int rc, err = 0;
-+
-+ if (!sev_snp_guest(vcpu->kvm)) {
-+ rc = -ENODEV;
-+ goto e_fail;
-+ }
-+
-+ sev = &to_kvm_svm(kvm)->sev_info;
-+
-+ if (!__ratelimit(&sev->snp_guest_msg_rs)) {
-+ pr_info_ratelimited("svm: too many guest message requests\n");
-+ rc = -EAGAIN;
-+ goto e_fail;
-+ }
-+
-+ rc = snp_build_guest_buf(svm, &data, req_gpa, resp_gpa);
-+ if (rc)
-+ goto e_fail;
-+
-+ sev = &to_kvm_svm(kvm)->sev_info;
-+
-+ mutex_lock(&kvm->lock);
-+
-+ rc = sev_issue_cmd(kvm, SEV_CMD_SNP_GUEST_REQUEST, &data, &err);
-+ if (rc) {
-+ mutex_unlock(&kvm->lock);
-+
-+ /* If we have a firmware error code then use it. */
-+ if (err)
-+ rc = err;
-+
-+ goto e_fail;
-+ }
-+
-+ /* Copy the response after the firmware returns success. */
-+ rc = kvm_write_guest(kvm, resp_gpa, sev->snp_resp_page, PAGE_SIZE);
-+
-+ mutex_unlock(&kvm->lock);
-+
-+e_fail:
-+ ghcb_set_sw_exit_info_2(ghcb, rc);
-+}
-+
-+static void snp_handle_ext_guest_request(struct vcpu_svm *svm, struct ghcb *ghcb,
-+ gpa_t req_gpa, gpa_t resp_gpa)
-+{
-+ struct sev_data_snp_guest_request req = {};
-+ struct kvm_vcpu *vcpu = &svm->vcpu;
-+ struct kvm *kvm = vcpu->kvm;
-+ unsigned long data_npages;
-+ struct kvm_sev_info *sev;
-+ unsigned long err;
-+ u64 data_gpa;
-+ int rc;
-+
-+ if (!sev_snp_guest(vcpu->kvm)) {
-+ rc = -ENODEV;
-+ goto e_fail;
-+ }
-+
-+ sev = &to_kvm_svm(kvm)->sev_info;
-+
-+ if (!__ratelimit(&sev->snp_guest_msg_rs)) {
-+ pr_info_ratelimited("svm: too many guest message requests\n");
-+ rc = -EAGAIN;
-+ goto e_fail;
-+ }
-+
-+ if (!sev->snp_certs_data) {
-+ pr_err("svm: certs data memory is not allocated\n");
-+ rc = -EFAULT;
-+ goto e_fail;
-+ }
-+
-+ data_gpa = ghcb_get_rax(ghcb);
-+ data_npages = ghcb_get_rbx(ghcb);
-+
-+ if (!IS_ALIGNED(data_gpa, PAGE_SIZE)) {
-+ pr_err_ratelimited("svm: certs data GPA is not page aligned (%#llx)\n", data_gpa);
-+ rc = -EINVAL;
-+ goto e_fail;
-+ }
-+
-+ /* Verify that requested blob will fit in our intermediate buffer */
-+ if ((data_npages << PAGE_SHIFT) > SEV_FW_BLOB_MAX_SIZE) {
-+ rc = -EINVAL;
-+ goto e_fail;
-+ }
-+
-+ rc = snp_build_guest_buf(svm, &req, req_gpa, resp_gpa);
-+ if (rc)
-+ goto e_fail;
-+
-+ mutex_lock(&kvm->lock);
-+ rc = snp_guest_ext_guest_request(&req, (unsigned long)sev->snp_certs_data,
-+ &data_npages, &err);
-+ if (rc) {
-+ mutex_unlock(&kvm->lock);
-+
-+ /*
-+ * If buffer length is small then return the expected
-+ * length in rbx.
-+ */
-+ if (err == SNP_GUEST_REQ_INVALID_LEN) {
-+ vcpu->arch.regs[VCPU_REGS_RBX] = data_npages;
-+ ghcb_set_sw_exit_info_2(ghcb, err);
-+ return;
-+ }
-+
-+ /* If we have a firmware error code then use it. */
-+ if (err)
-+ rc = (int)err;
-+
-+ goto e_fail;
-+ }
-+
-+ /* Copy the response after the firmware returns success. */
-+ rc = kvm_write_guest(kvm, resp_gpa, sev->snp_resp_page, PAGE_SIZE);
-+
-+ mutex_unlock(&kvm->lock);
-+
-+ if (rc)
-+ goto e_fail;
-+
-+ /* Copy the certificate blob in the guest memory */
-+ if (data_npages &&
-+ kvm_write_guest(kvm, data_gpa, sev->snp_certs_data, data_npages << PAGE_SHIFT))
-+ rc = -EFAULT;
-+
-+e_fail:
-+ ghcb_set_sw_exit_info_2(ghcb, rc);
++out:
++ info->hdr.cur_entry = cur;
++ return rc ? map_to_psc_vmgexit_code(rc) : 0;
+}
+
static int sev_handle_vmgexit_msr_protocol(struct vcpu_svm *svm)
{
struct vmcb_control_area *control = &svm->vmcb->control;
-@@ -3306,6 +3508,21 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
- ghcb_set_sw_exit_info_2(ghcb, rc);
+@@ -3315,6 +3378,15 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
+ ret = 1;
break;
}
-+ case SVM_VMGEXIT_GUEST_REQUEST: {
-+ snp_handle_guest_request(svm, ghcb, control->exit_info_1, control->exit_info_2);
++ case SVM_VMGEXIT_PSC: {
++ unsigned long rc;
+
+ ret = 1;
-+ break;
-+ }
-+ case SVM_VMGEXIT_EXT_GUEST_REQUEST: {
-+ snp_handle_ext_guest_request(svm,
-+ ghcb,
-+ control->exit_info_1,
-+ control->exit_info_2);
+
-+ ret = 1;
++ rc = snp_handle_page_state_change(svm);
++ svm_set_ghcb_sw_exit_info_2(vcpu, rc);
+ break;
+ }
case SVM_VMGEXIT_UNSUPPORTED_EVENT:
vcpu_unimpl(vcpu,
"vmgexit: unsupported event - exit_info_1=%#llx, exit_info_2=%#llx\n",
-diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
-index ccdaaa4e1fb1..9fcfc0a51737 100644
---- a/arch/x86/kvm/svm/svm.h
-+++ b/arch/x86/kvm/svm/svm.h
-@@ -18,6 +18,7 @@
- #include <linux/kvm_types.h>
- #include <linux/kvm_host.h>
- #include <linux/bits.h>
-+#include <linux/ratelimit.h>
-
- #include <asm/svm.h>
- #include <asm/sev-common.h>
-@@ -68,6 +69,9 @@ struct kvm_sev_info {
- struct kvm *enc_context_owner; /* Owner of copied encryption context */
- struct misc_cg *misc_cg; /* For misc cgroup accounting */
- void *snp_context; /* SNP guest context page */
-+ void *snp_resp_page; /* SNP guest response page */
-+ struct ratelimit_state snp_guest_msg_rs; /* Rate limit the SNP guest message */
-+ void *snp_certs_data;
- };
-
- struct kvm_svm {
-@@ -550,7 +554,7 @@ void svm_vcpu_unblocking(struct kvm_vcpu *vcpu);
- #define GHCB_VERSION_MAX 2ULL
- #define GHCB_VERSION_MIN 1ULL
-
--#define GHCB_HV_FT_SUPPORTED 0
-+#define GHCB_HV_FT_SUPPORTED GHCB_HV_FT_SNP
-
- extern unsigned int max_sev_asid;
-
--
2.17.1