Inter-revision diff: patch 38

Comparing v4 (message) to v5 (message)

--- 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
 
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help