Skip to contentSkip to conversation

KVM: SEV-SNP support for running an SVSM

RFC PATCH In review Tom Lendacky 2 participants 9 messages Aug 27, 2024 – Nov 28, 2024

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover KVM: SEV-SNP support for running an SVSM

This series is meant to start the discussion around running a guest with a Secure VM Service Module (SVSM) and how to transition a vCPU between one VM Privilege Level (VMPL) and another. This is Proof-of-Concept level code, so definitely not something looking to be merged.

When running under an SVSM, VMPL switches are needed for validating memory and creating vCPU VM Save Area (VMSA) pages. Going forward, different services running in the SVSM will require VMPL switching, e.g. a virtual TPM service or Alternate Injection support. Therefore VMPL switches need to be as fast as possible. The implementation in this series has KVM managing the creation of VMPL levels and transitioning between the levels without transitioning to the userspace VMM.

Going forward, the userspace VMM may need to be aware of VMPL levels. It may be necessary to transition VMPL creation (AP Creation at a specific VMPL level) to the userspace VMM. But keeping VMPL switching within KVM is highly desired for performance reasons.

This PoC code does have some restrictions. For example, when running with Restricted Injection, all injections are blocked as the SVSM is not expecting any injections (currently). This allows for a single APIC instance for now.

The patches can be further split and the change logs improved, but wanted to get this out and get the discussion going.

Implemented in this RFC:
- APIC ID list retrieval to allow for only measuring the BSP and allowing the guest to start all of the APs without having to use a broadcast SIPI
- vCPU creation at a specific VMPL
- vCPU execution at a specific VMPL
- Maintain per-VMPL SEV features
- Implement minimal Restricted Injection
- Blocks all injection when enabled
- SVSM support
- SNP init flag for SVSM support
- Measuring data with specific VMPL permissions
- Measuring only the BSP

Things not yet implemented:
- APIC instance separation
- Restricted Injection support that is multi-VMPL aware

The series is based off of a slightly older kvm next branch:
git://git.kernel.org/pub/scm/virt/kvm/kvm.git next

7c626ce4bae1 ("Linux 6.11-rc3")

---

Carlos Bilbao (1):
KVM: SVM: Maintain per-VMPL SEV features in kvm_sev_info

Tom Lendacky (6):
KVM: SVM: Implement GET_AP_APIC_IDS NAE event
KVM: SEV: Allow for VMPL level specification in AP create
KVM: SVM: Invoke a specified VMPL level VMSA for the vCPU
KVM: SVM: Prevent injection when restricted injection is active
KVM: SVM: Support launching an SVSM with Restricted Injection set
KVM: SVM: Support initialization of an SVSM

 arch/x86/include/asm/sev-common.h |   7 +
 arch/x86/include/asm/svm.h        |   9 +
 arch/x86/include/uapi/asm/kvm.h   |  10 +
 arch/x86/include/uapi/asm/svm.h   |   3 +
 arch/x86/kvm/svm/sev.c            | 530 +++++++++++++++++++++++++-----
 arch/x86/kvm/svm/svm.c            |  25 +-
 arch/x86/kvm/svm/svm.h            |  71 +++-
 arch/x86/kvm/x86.c                |   9 +
 include/uapi/linux/kvm.h          |   3 +
 9 files changed, 575 insertions(+), 92 deletions(-)
Signature · 1 line
2.43.2
#1 lore raw

RFC PATCH 1/7 KVM: SVM: Implement GET_AP_APIC_IDS NAE event

Implement the GET_APIC_IDS NAE event to gather and return the list of APIC IDs for all vCPUs in the guest.

---
 arch/x86/include/asm/sev-common.h |  1 +
 arch/x86/include/uapi/asm/svm.h   |  1 +
 arch/x86/kvm/svm/sev.c            | 84 ++++++++++++++++++++++++++++++-
 3 files changed, 85 insertions(+), 1 deletion(-)
arch/x86/include/asm/sev-common.h +1 -0
index 98726c2b04f8..d63c861ef91f 100644
@@ -136,6 +136,7 @@ enum psc_op {
 
 #define GHCB_HV_FT_SNP			BIT_ULL(0)
 #define GHCB_HV_FT_SNP_AP_CREATION	BIT_ULL(1)
+#define GHCB_HV_FT_APIC_ID_LIST		BIT_ULL(4)
 #define GHCB_HV_FT_SNP_MULTI_VMPL	BIT_ULL(5)
 
 /*
arch/x86/include/uapi/asm/svm.h +1 -0
index 1814b413fd57..f8fa3c4c0322 100644
@@ -115,6 +115,7 @@
 #define SVM_VMGEXIT_AP_CREATE_ON_INIT		0
 #define SVM_VMGEXIT_AP_CREATE			1
 #define SVM_VMGEXIT_AP_DESTROY			2
+#define SVM_VMGEXIT_GET_APIC_IDS		0x80000017
 #define SVM_VMGEXIT_SNP_RUN_VMPL		0x80000018
 #define SVM_VMGEXIT_HV_FEATURES			0x8000fffd
 #define SVM_VMGEXIT_TERM_REQUEST		0x8000fffe
arch/x86/kvm/svm/sev.c +83 -1

108 lines not on this page · raw message

Signature · 1 line
2.43.2
maintainer
#2 lore raw

Re: [RFC PATCH 1/7] KVM: SVM: Implement GET_AP_APIC_IDS NAE event

Review static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r · line 4134

Quoted 6 lines · Tom Lendacky
(quoted diff) +2 -0
@@ -4124,6 +4130,77 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r
 	return 1; /* resume guest */
 }
 
+struct sev_apic_id_desc {
+	u32	num_entries;

"count" - like in the spec. :-P

Quoted 7 lines
(quoted diff) +7 -0
+	u32	apic_ids[];
+};
+
+static void sev_get_apic_ids(struct vcpu_svm *svm)
+{
+	struct ghcb *ghcb = svm->sev_es.ghcb;
+	struct kvm_vcpu *vcpu = &svm->vcpu, *loop_vcpu;

Review static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r · line 4151

Quoted 10 lines
(quoted diff) +10 -0
+	struct kvm *kvm = vcpu->kvm;
+	unsigned int id_desc_size;
+	struct sev_apic_id_desc *desc;
+	kvm_pfn_t pfn;
+	gpa_t gpa;
+	u64 pages;
+	unsigned long i;
+	int n;
+
+	pages = vcpu->arch.regs[VCPU_REGS_RAX];

Probably should be "num_pages" and a comment should explain what it is:

"State to Hypervisor: is the
number of guest contiguous pages
provided to hold the list of APIC
IDs"

Makes it much easier to follow the code.

Review static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r · line 4156

Quoted 5 lines
(quoted diff) +5 -0
+	/* Each APIC ID is 32-bits in size, so make sure there is room */
+	n = atomic_read(&kvm->online_vcpus);
+	/*TODO: is this possible? */
+	if (n < 0)
+		return;

It doesn't look like it but if you wanna be real paranoid you can slap a WARN_ONCE() here or so to scream loudly.

Review static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r · line 4167

Quoted 11 lines
(quoted diff) +11 -0
+	id_desc_size = sizeof(*desc);
+	id_desc_size += n * sizeof(desc->apic_ids[0]);
+	if (id_desc_size > (pages * PAGE_SIZE)) {
+		vcpu->arch.regs[VCPU_REGS_RAX] = PFN_UP(id_desc_size);
+		return;
+	}
+
+	gpa = svm->vmcb->control.exit_info_1;
+
+	ghcb_set_sw_exit_info_1(ghcb, 2);
+	ghcb_set_sw_exit_info_2(ghcb, 5);

Uuh, more magic numbers. I guess we need this:

https://lore.kernel.org/r/[email protected]

and more.

And can we write those only once at the end of the function?

Review static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r · line 4171

Quoted 4 lines
(quoted diff) +4 -0
+	if (!page_address_valid(vcpu, gpa))
+		return;
+
+	pfn = gfn_to_pfn(kvm, gpa_to_gfn(gpa));

Looking at the tree, that gfn_to_pfn() thing is gone now and we're supposed to it this way. Not tested ofc:

arch/x86/kvm/svm/sev.c +3 -3
index 5af227ba15a3..47e1f72a574d 100644
@@ -4134,7 +4134,7 @@ static void sev_get_apic_ids(struct vcpu_svm *svm)
 	struct kvm *kvm = vcpu->kvm;
 	unsigned int id_desc_size;
 	struct sev_apic_id_desc *desc;
-	kvm_pfn_t pfn;
+	struct page *page;
 	gpa_t gpa;
 	u64 pages;
 	unsigned long i;
@@ -4163,8 +4163,8 @@ static void sev_get_apic_ids(struct vcpu_svm *svm)
 	if (!page_address_valid(vcpu, gpa))
 		return;
 
-	pfn = gfn_to_pfn(kvm, gpa_to_gfn(gpa));
-	if (is_error_noslot_pfn(pfn))
+	page = gfn_to_page(kvm, gpa_to_gfn(gpa));
+	if (!page)
 		return;
 
 	if (!pages)

Review static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r · line 4176

Quoted 5 lines
(quoted diff) +5 -0
+	if (is_error_noslot_pfn(pfn))
+		return;
+
+	if (!pages)
+		return;

That test needs to go right under the assignment of "pages".

Review static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r · line 4184

Quoted 8 lines
(quoted diff) +8 -0
+	/* Allocate a buffer to hold the APIC IDs */
+	desc = kvzalloc(id_desc_size, GFP_KERNEL_ACCOUNT);
+	if (!desc)
+		return;
+
+	desc->num_entries = n;
+	kvm_for_each_vcpu(i, loop_vcpu, kvm) {
+		/*TODO: is this possible? */

Well:

#define kvm_for_each_vcpu(idx, vcpup, kvm)                 \
        xa_for_each_range(&kvm->vcpu_array, idx, vcpup, 0, \
                          (atomic_read(&kvm->online_vcpus) - 1))
			   ^^^^^^^^^^^^^^

but, what's stopping kvm_vm_ioctl_create_vcpu() from incrementing it?

I'm guessing this would happen when you start the guest only but I haz no idea.

Quoted 14 lines
(quoted diff) +14 -0
+		if (i > n)
+			break;
+
+		desc->apic_ids[i] = loop_vcpu->vcpu_id;
+	}
+
+	if (!kvm_write_guest(kvm, gpa, desc, id_desc_size)) {
+		/* IDs were successfully written */
+		ghcb_set_sw_exit_info_1(ghcb, 0);
+		ghcb_set_sw_exit_info_2(ghcb, 0);
+	}
+
+	kvfree(desc);
+}
Signature · 4 lines
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette
#3 lore raw

RFC PATCH 2/7 KVM: SEV: Allow for VMPL level specification in AP create

In reply to Tom Lendacky

Update AP creation to support ADD/DESTROY of VMSAs at levels other than VMPL0 in order to run under an SVSM at VMPL1 or lower. To maintain backwards compatibility, the VMPL is specified in bits 16 to 19 of the AP Creation request in SW_EXITINFO1 of the GHCB.

In order to track the VMSAs at different levels, create arrays for the VMSAs, GHCBs, registered GHCBs and others. When switching VMPL levels, these entries will be used to set the VMSA and GHCB physical addresses in the VMCB for the VMPL level.

In order ensure that the proper responses are returned in the proper GHCB, the GHCB must be unmapped at the current level and saved for restoration later when switching back to that VMPL level.

Additional checks are applied to prevent a non-VMPL0 vCPU from being able to perform an AP creation request at VMPL0. Additionally, a vCPU cannot replace its own VMSA.

---
 arch/x86/include/asm/svm.h      |   9 ++
 arch/x86/include/uapi/asm/svm.h |   2 +
 arch/x86/kvm/svm/sev.c          | 146 +++++++++++++++++++++++---------
 arch/x86/kvm/svm/svm.c          |   6 +-
 arch/x86/kvm/svm/svm.h          |  45 ++++++++--
 arch/x86/kvm/x86.c              |   9 ++
 6 files changed, 169 insertions(+), 48 deletions(-)
arch/x86/include/asm/svm.h +9 -0
index f0dea3750ca9..26339d94c00f 100644
@@ -294,6 +294,15 @@ static_assert((X2AVIC_MAX_PHYSICAL_ID & AVIC_PHYSICAL_MAX_INDEX_MASK) == X2AVIC_
 	(SVM_SEV_FEAT_RESTRICTED_INJECTION |	\
 	 SVM_SEV_FEAT_ALTERNATE_INJECTION)
 
+enum {
+	SVM_SEV_VMPL0 = 0,
+	SVM_SEV_VMPL1,
+	SVM_SEV_VMPL2,
+	SVM_SEV_VMPL3,
+
+	SVM_SEV_VMPL_MAX
+};
+
 struct vmcb_seg {
 	u16 selector;
 	u16 attrib;
arch/x86/include/uapi/asm/svm.h +2 -0
index f8fa3c4c0322..4a963dd12bb4 100644
@@ -115,6 +115,8 @@
 #define SVM_VMGEXIT_AP_CREATE_ON_INIT		0
 #define SVM_VMGEXIT_AP_CREATE			1
 #define SVM_VMGEXIT_AP_DESTROY			2
+#define SVM_VMGEXIT_AP_VMPL_MASK		GENMASK(19, 16)
+#define SVM_VMGEXIT_AP_VMPL_SHIFT		16
 #define SVM_VMGEXIT_GET_APIC_IDS		0x80000017
 #define SVM_VMGEXIT_SNP_RUN_VMPL		0x80000018
 #define SVM_VMGEXIT_HV_FEATURES			0x8000fffd
arch/x86/kvm/svm/sev.c +107 -39

296 lines not on this page · raw message

arch/x86/kvm/svm/svm.c +4 -2
index d6f252555ab3..ca4bc53fb14a 100644
@@ -1463,8 +1463,10 @@ static int svm_vcpu_create(struct kvm_vcpu *vcpu)
 	svm->vmcb01.pa = __sme_set(page_to_pfn(vmcb01_page) << PAGE_SHIFT);
 	svm_switch_vmcb(svm, &svm->vmcb01);
 
-	if (vmsa_page)
-		svm->sev_es.vmsa = page_address(vmsa_page);
+	if (vmsa_page) {
+		vmpl_vmsa(svm, SVM_SEV_VMPL0) = page_address(vmsa_page);
+		vmpl_vmsa_hpa(svm, SVM_SEV_VMPL0) = __pa(page_address(vmsa_page));
+	}
 
 	svm->guest_state_loaded = false;
 
arch/x86/kvm/svm/svm.h +38 -7

66 lines not on this page · raw message

arch/x86/kvm/x86.c +9 -0
index ef3d3511e4af..3efc3a89499c 100644
@@ -11469,6 +11469,15 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
 		kvm_vcpu_block(vcpu);
 		kvm_vcpu_srcu_read_lock(vcpu);
 
+		/*
+		 * It is possible that the vCPU has never run before. If the
+		 * request is to update the protected guest state (AP Create),
+		 * then ensure that the vCPU can now run.
+		 */
+		if (kvm_test_request(KVM_REQ_UPDATE_PROTECTED_GUEST_STATE, vcpu) &&
+		    vcpu->arch.mp_state == KVM_MP_STATE_UNINITIALIZED)
+			vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+
 		if (kvm_apic_accept_events(vcpu) < 0) {
 			r = 0;
 			goto out;
Signature · 1 line
2.43.2
#4 lore raw

RFC PATCH 3/7 KVM: SVM: Invoke a specified VMPL level VMSA for the vCPU

In reply to Tom Lendacky

Implement the SNP Run VMPL NAE event and MSR protocol to allow a guest to request a different VMPL level VMSA be run for the vCPU. This allows the guest to "call" an SVSM to process an SVSM request.

---
 arch/x86/include/asm/sev-common.h |   6 ++
 arch/x86/kvm/svm/sev.c            | 126 +++++++++++++++++++++++++++++-
 arch/x86/kvm/svm/svm.c            |  13 +++
 arch/x86/kvm/svm/svm.h            |  18 ++++-
 4 files changed, 158 insertions(+), 5 deletions(-)
arch/x86/include/asm/sev-common.h +6 -0
index d63c861ef91f..6f7134aada83 100644
@@ -114,6 +114,8 @@ enum psc_op {
 
 /* GHCB Run at VMPL Request/Response */
 #define GHCB_MSR_VMPL_REQ		0x016
+#define GHCB_MSR_VMPL_LEVEL_POS		32
+#define GHCB_MSR_VMPL_LEVEL_MASK	GENMASK_ULL(7, 0)
 #define GHCB_MSR_VMPL_REQ_LEVEL(v)			\
 	/* GHCBData[39:32] */				\
 	(((u64)(v) & GENMASK_ULL(7, 0) << 32) |		\
@@ -121,6 +123,10 @@ enum psc_op {
 	GHCB_MSR_VMPL_REQ)
 
 #define GHCB_MSR_VMPL_RESP		0x017
+#define GHCB_MSR_VMPL_ERROR_POS		32
+#define GHCB_MSR_VMPL_ERROR_MASK	GENMASK_ULL(31, 0)
+#define GHCB_MSR_VMPL_RSVD_POS		12
+#define GHCB_MSR_VMPL_RSVD_MASK		GENMASK_ULL(19, 0)
 #define GHCB_MSR_VMPL_RESP_VAL(v)			\
 	/* GHCBData[63:32] */				\
 	(((u64)(v) & GENMASK_ULL(63, 32)) >> 32)
arch/x86/kvm/svm/sev.c +123 -3

174 lines not on this page · raw message

arch/x86/kvm/svm/svm.c +13 -0
index ca4bc53fb14a..586c26627bb1 100644
@@ -4253,6 +4253,19 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu,
 	}
 	vcpu->arch.regs_dirty = 0;
 
+	if (sev_snp_is_rinj_active(vcpu)) {
+		/*
+		 * When SEV-SNP is running with restricted injection, the V_IRQ
+		 * bit may be cleared on exit because virtual interrupt support
+		 * is ignored. To support multiple VMPLs, some of which may not
+		 * be running with restricted injection, ensure to reset the
+		 * V_IRQ bit if a virtual interrupt is meant to be active (the
+		 * virtual interrupt priority mask is non-zero).
+		 */
+		if (svm->vmcb->control.int_ctl & V_INTR_PRIO_MASK)
+			svm->vmcb->control.int_ctl |= V_IRQ_MASK;
+	}
+
 	if (unlikely(svm->vmcb->control.exit_code == SVM_EXIT_NMI))
 		kvm_before_interrupt(vcpu, KVM_HANDLING_NMI);
 
arch/x86/kvm/svm/svm.h +16 -2
index 45a37d16b6f7..d1ef349556f7 100644
@@ -198,6 +198,18 @@ struct svm_nested_state {
 	bool force_msr_bitmap_recalc;
 };
 
+struct vmpl_switch_sa {
+	u32 int_state;
+	u32 exit_int_info;
+	u32 exit_int_info_err;
+
+	unsigned long cr0;
+	unsigned long cr2;
+	unsigned long cr4;
+	unsigned long cr8;
+	u64 efer;
+};
+
 #define vmpl_vmsa(s, v)				((s)->sev_es.vmsa_info[(v)].vmsa)
 #define vmpl_vmsa_gpa(s, v)			((s)->sev_es.vmsa_info[(v)].gpa)
 #define vmpl_vmsa_hpa(s, v)			((s)->sev_es.vmsa_info[(v)].hpa)
@@ -256,6 +268,8 @@ struct vcpu_sev_es_state {
 	struct mutex snp_vmsa_mutex; /* Used to handle concurrent updates of VMSA. */
 	unsigned int snp_current_vmpl;
 	unsigned int snp_target_vmpl;
+
+	struct vmpl_switch_sa vssa[SVM_SEV_VMPL_MAX];
 };
 
 struct vcpu_svm {
@@ -776,7 +790,7 @@ int sev_cpu_init(struct svm_cpu_data *sd);
 int sev_dev_get_attr(u32 group, u64 attr, u64 *val);
 extern unsigned int max_sev_asid;
 void sev_handle_rmp_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code);
-void sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu);
+bool sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu);
 int sev_gmem_prepare(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn, int max_order);
 void sev_gmem_invalidate(kvm_pfn_t start, kvm_pfn_t end);
 int sev_private_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn);
@@ -800,7 +814,7 @@ static inline int sev_cpu_init(struct svm_cpu_data *sd) { return 0; }
 static inline int sev_dev_get_attr(u32 group, u64 attr, u64 *val) { return -ENXIO; }
 #define max_sev_asid 0
 static inline void sev_handle_rmp_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code) {}
-static inline void sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu) {}
+static inline bool sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu) { return false; }
 static inline int sev_gmem_prepare(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn, int max_order)
 {
 	return 0;
Signature · 1 line
2.43.2
#5 lore raw

RFC PATCH 4/7 KVM: SVM: Maintain per-VMPL SEV features in kvm_sev_info

In reply to Tom Lendacky

From: Carlos Bilbao <[email protected]>

Make struct kvm_sev_info maintain separate SEV features per VMPL, allowing distinct SEV features depending on VMs privilege level.

---
 arch/x86/kvm/svm/sev.c | 22 +++++++++++++++-------
 arch/x86/kvm/svm/svm.h |  4 ++--
 2 files changed, 17 insertions(+), 9 deletions(-)
arch/x86/kvm/svm/sev.c +15 -7
index e0f5122061e6..c6c9306c86ef 100644
@@ -144,7 +144,7 @@ static bool sev_vcpu_has_debug_swap(struct vcpu_svm *svm)
 	struct kvm_vcpu *vcpu = &svm->vcpu;
 	struct kvm_sev_info *sev = &to_kvm_svm(vcpu->kvm)->sev_info;
 
-	return sev->vmsa_features & SVM_SEV_FEAT_DEBUG_SWAP;
+	return sev->vmsa_features[cur_vmpl(svm)] & SVM_SEV_FEAT_DEBUG_SWAP;
 }
 
 /* Must be called with the sev_bitmap_lock held */
@@ -428,7 +428,7 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,
 
 	sev->active = true;
 	sev->es_active = es_active;
-	sev->vmsa_features = data->vmsa_features;
+	sev->vmsa_features[SVM_SEV_VMPL0] = data->vmsa_features;
 	sev->ghcb_version = data->ghcb_version;
 
 	/*
@@ -440,7 +440,7 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,
 		sev->ghcb_version = GHCB_VERSION_DEFAULT;
 
 	if (vm_type == KVM_X86_SNP_VM)
-		sev->vmsa_features |= SVM_SEV_FEAT_SNP_ACTIVE;
+		sev->vmsa_features[SVM_SEV_VMPL0] |= SVM_SEV_FEAT_SNP_ACTIVE;
 
 	ret = sev_asid_new(sev);
 	if (ret)
@@ -468,7 +468,7 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,
 	sev_asid_free(sev);
 	sev->asid = 0;
 e_no_asid:
-	sev->vmsa_features = 0;
+	sev->vmsa_features[SVM_SEV_VMPL0] = 0;
 	sev->es_active = false;
 	sev->active = false;
 	return ret;
@@ -852,7 +852,7 @@ static int sev_es_sync_vmsa(struct vcpu_svm *svm)
 	save->xss  = svm->vcpu.arch.ia32_xss;
 	save->dr6  = svm->vcpu.arch.dr6;
 
-	save->sev_features = sev->vmsa_features;
+	save->sev_features = sev->vmsa_features[SVM_SEV_VMPL0];
 
 	/*
 	 * Skip FPU and AVX setup with KVM_SEV_ES_INIT to avoid
@@ -1985,7 +1985,7 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm)
 	dst->pages_locked = src->pages_locked;
 	dst->enc_context_owner = src->enc_context_owner;
 	dst->es_active = src->es_active;
-	dst->vmsa_features = src->vmsa_features;
+	memcpy(dst->vmsa_features, src->vmsa_features, sizeof(dst->vmsa_features));
 
 	src->asid = 0;
 	src->active = false;
@@ -4034,8 +4034,16 @@ static int sev_snp_ap_creation(struct vcpu_svm *svm)
 
 		/* Interrupt injection mode shouldn't change for AP creation */
 		sev_features = vcpu->arch.regs[VCPU_REGS_RAX];
-		sev_features ^= sev->vmsa_features;
 
+		/*
+		 * The SNPActive feature must at least be set. If the SEV
+		 * features of this AP are zero, this is the first vCPU created at
+		 * this VMPL.
+		 */
+		if (!sev->vmsa_features[vmpl])
+			sev->vmsa_features[vmpl] = sev_features | SVM_SEV_FEAT_SNP_ACTIVE;
+
+		sev_features ^= sev->vmsa_features[vmpl];
 		if (sev_features & SVM_SEV_FEAT_INT_INJ_MODES) {
 			vcpu_unimpl(vcpu, "vmgexit: invalid AP injection mode [%#lx] from guest\n",
 				    vcpu->arch.regs[VCPU_REGS_RAX]);
arch/x86/kvm/svm/svm.h +2 -2
index d1ef349556f7..55f1f6ffb871 100644
@@ -87,7 +87,7 @@ struct kvm_sev_info {
 	unsigned long pages_locked; /* Number of pages locked */
 	struct list_head regions_list;  /* List of registered regions */
 	u64 ap_jump_table;	/* SEV-ES AP Jump Table address */
-	u64 vmsa_features;
+	u64 vmsa_features[SVM_SEV_VMPL_MAX];
 	u16 ghcb_version;	/* Highest guest GHCB protocol version allowed */
 	struct kvm *enc_context_owner; /* Owner of copied encryption context */
 	struct list_head mirror_vms; /* List of VMs mirroring */
@@ -416,7 +416,7 @@ static __always_inline bool sev_snp_guest(struct kvm *kvm)
 #ifdef CONFIG_KVM_AMD_SEV
 	struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
 
-	return (sev->vmsa_features & SVM_SEV_FEAT_SNP_ACTIVE) &&
+	return (sev->vmsa_features[SVM_SEV_VMPL0] & SVM_SEV_FEAT_SNP_ACTIVE) &&
 	       !WARN_ON_ONCE(!sev_es_guest(kvm));
 #else
 	return false;
Signature · 1 line
2.43.2
#6 lore raw

RFC PATCH 5/7 KVM: SVM: Prevent injection when restricted injection is active

In reply to Tom Lendacky

Prevent injection of exceptions/interrupts when restricted injection is active. This is not full support for restricted injection, but the SVSM is not expecting any injections at all.

---
 arch/x86/kvm/svm/sev.c | 30 ++++++++++++++++++++++++++++++
 arch/x86/kvm/svm/svm.c |  6 ++++++
 arch/x86/kvm/svm/svm.h |  3 +++
 3 files changed, 39 insertions(+)
arch/x86/kvm/svm/sev.c +30 -0
index c6c9306c86ef..4324a72d35ea 100644
@@ -5227,3 +5227,33 @@ int sev_private_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn)
 
 	return level;
 }
+
+bool sev_snp_is_rinj_active(struct kvm_vcpu *vcpu)
+{
+	struct kvm_sev_info *sev;
+	int vmpl;
+
+	if (!sev_snp_guest(vcpu->kvm))
+		return false;
+
+	sev = &to_kvm_svm(vcpu->kvm)->sev_info;
+	vmpl = to_svm(vcpu)->sev_es.snp_current_vmpl;
+
+	return sev->vmsa_features[vmpl] & SVM_SEV_FEAT_RESTRICTED_INJECTION;
+}
+
+bool sev_snp_nmi_blocked(struct kvm_vcpu *vcpu)
+{
+	WARN_ON_ONCE(!sev_snp_is_rinj_active(vcpu));
+
+	/* NMIs are blocked when restricted injection is active */
+	return true;
+}
+
+bool sev_snp_interrupt_blocked(struct kvm_vcpu *vcpu)
+{
+	WARN_ON_ONCE(!sev_snp_is_rinj_active(vcpu));
+
+	/* Interrupts are blocked when restricted injection is active */
+	return true;
+}
arch/x86/kvm/svm/svm.c +6 -0
index 586c26627bb1..632c74cb41f4 100644
@@ -3780,6 +3780,9 @@ bool svm_nmi_blocked(struct kvm_vcpu *vcpu)
 	if (!gif_set(svm))
 		return true;
 
+	if (sev_snp_is_rinj_active(vcpu))
+		return sev_snp_nmi_blocked(vcpu);
+
 	if (is_guest_mode(vcpu) && nested_exit_on_nmi(svm))
 		return false;
 
@@ -3812,6 +3815,9 @@ bool svm_interrupt_blocked(struct kvm_vcpu *vcpu)
 	if (!gif_set(svm))
 		return true;
 
+	if (sev_snp_is_rinj_active(vcpu))
+		return sev_snp_interrupt_blocked(vcpu);
+
 	if (is_guest_mode(vcpu)) {
 		/* As long as interrupts are being delivered...  */
 		if ((svm->nested.ctl.int_ctl & V_INTR_MASKING_MASK)
arch/x86/kvm/svm/svm.h +3 -0
index 55f1f6ffb871..029eb54a8472 100644
@@ -761,6 +761,9 @@ void sev_es_vcpu_reset(struct vcpu_svm *svm);
 void sev_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector);
 void sev_es_prepare_switch_to_guest(struct vcpu_svm *svm, struct sev_es_save_area *hostsa);
 void sev_es_unmap_ghcb(struct vcpu_svm *svm);
+bool sev_snp_is_rinj_active(struct kvm_vcpu *vcpu);
+bool sev_snp_nmi_blocked(struct kvm_vcpu *vcpu);
+bool sev_snp_interrupt_blocked(struct kvm_vcpu *vcpu);
 
 #ifdef CONFIG_KVM_AMD_SEV
 int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp);
Signature · 1 line
2.43.2
#7 lore raw

RFC PATCH 6/7 KVM: SVM: Support launching an SVSM with Restricted Injection set

In reply to Tom Lendacky

Allow Restricted Injection to be set in SEV_FEATURES. When set, attempts to inject any interrupts other than #HV will make VMRUN fail. This is done to further reduce the security exposure within the SVSM.

---
 arch/x86/kvm/svm/sev.c | 1 +
 1 file changed, 1 insertion(+)
arch/x86/kvm/svm/sev.c +1 -0
index 4324a72d35ea..3aa9489786ee 100644
@@ -3078,6 +3078,7 @@ void __init sev_hardware_setup(void)
 		sev_es_debug_swap_enabled = false;
 
 	sev_supported_vmsa_features = 0;
+	sev_supported_vmsa_features |= SVM_SEV_FEAT_RESTRICTED_INJECTION;
 	if (sev_es_debug_swap_enabled)
 		sev_supported_vmsa_features |= SVM_SEV_FEAT_DEBUG_SWAP;
 }
Signature · 1 line
2.43.2
#8 lore raw

RFC PATCH 7/7 KVM: SVM: Support initialization of an SVSM

In reply to Tom Lendacky

Allow for setting VMPL permission as part of the launch sequence and ssing an SNP init flag, limit measuring of the guest vCPUs, to just the BSP.

Indicate full multi-VMPL support to the guest through the GHCB feature bitmap.

---
 arch/x86/include/uapi/asm/kvm.h |  10 +++
 arch/x86/kvm/svm/sev.c          | 123 ++++++++++++++++++++++++--------
 arch/x86/kvm/svm/svm.h          |   1 +
 include/uapi/linux/kvm.h        |   3 +
 4 files changed, 107 insertions(+), 30 deletions(-)
arch/x86/include/uapi/asm/kvm.h +10 -0
index bf57a824f722..c60557bb4253 100644
@@ -465,6 +465,7 @@ struct kvm_sync_regs {
 /* vendor-specific groups and attributes for system fd */
 #define KVM_X86_GRP_SEV			1
 #  define KVM_X86_SEV_VMSA_FEATURES	0
+#  define KVM_X86_SEV_SNP_INIT_FLAGS	1
 
 struct kvm_vmx_nested_state_data {
 	__u8 vmcs12[KVM_STATE_NESTED_VMX_VMCS_SIZE];
@@ -703,6 +704,8 @@ enum sev_cmd_id {
 	KVM_SEV_SNP_LAUNCH_UPDATE,
 	KVM_SEV_SNP_LAUNCH_FINISH,
 
+	KVM_SEV_SNP_LAUNCH_UPDATE_VMPLS,
+
 	KVM_SEV_NR_MAX,
 };
 
@@ -856,6 +859,13 @@ struct kvm_sev_snp_launch_update {
 	__u64 pad2[4];
 };
 
+struct kvm_sev_snp_launch_update_vmpls {
+	struct kvm_sev_snp_launch_update lu;
+	__u8 vmpl3_perms;
+	__u8 vmpl2_perms;
+	__u8 vmpl1_perms;
+};
+
 #define KVM_SEV_SNP_ID_BLOCK_SIZE	96
 #define KVM_SEV_SNP_ID_AUTH_SIZE	4096
 #define KVM_SEV_SNP_FINISH_DATA_SIZE	32
arch/x86/kvm/svm/sev.c +93 -30

239 lines not on this page · raw message

arch/x86/kvm/svm/svm.h +1 -0
index 029eb54a8472..97a1b1b4cb5f 100644
@@ -98,6 +98,7 @@ struct kvm_sev_info {
 	void *guest_req_buf;    /* Bounce buffer for SNP Guest Request input */
 	void *guest_resp_buf;   /* Bounce buffer for SNP Guest Request output */
 	struct mutex guest_req_mutex; /* Must acquire before using bounce buffers */
+	unsigned int snp_init_flags;
 };
 
 struct kvm_svm {
include/uapi/linux/kvm.h +3 -0
index 637efc055145..49833912432a 100644
@@ -1399,6 +1399,9 @@ struct kvm_enc_region {
 #define KVM_GET_SREGS2             _IOR(KVMIO,  0xcc, struct kvm_sregs2)
 #define KVM_SET_SREGS2             _IOW(KVMIO,  0xcd, struct kvm_sregs2)
 
+/* Enable SVSM support */
+#define KVM_SEV_SNP_SVSM			(1 << 0)
+
 #define KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE    (1 << 0)
 #define KVM_DIRTY_LOG_INITIALLY_SET            (1 << 1)
 
Signature · 1 line
2.43.2