@@ -7179,6 +7179,89 @@ Please note that the kernel is allowed to use the kvm_run structure as the
primary storage for certain register types. Therefore, the kernel may use the
values in kvm_run even if the corresponding bit in kvm_dirty_regs is not set.
+::
+
+ /* KVM_EXIT_SNP_REQ_CERTS */
+ struct kvm_exit_snp_req_certs {
+ __u64 gfn;
+ __u32 npages;
+ __u32 ret;
+ };
+
+This event provides a way to request certificate data from userspace and
+have it written into guest memory. This is intended to handle attestation
+requests made by SEV-SNP guests (using the Extended Guest Requests GHCB
+command as defined by the GHCB 2.0 specification for SEV-SNP guests),
+where additional certificate data corresponding to the endorsement key
+used by firmware to sign an attestation report can be optionally provided
+by userspace to pass along to the guest together with the
+firmware-provided attestation report.
+
+KVM will supply in `gfn` the non-private guest page that userspace should
+use to write the contents of certificate data. The format of this
+certificate data is defined in the GHCB 2.0 specification (see section
+"SNP Extended Guest Request"). KVM will also supply in `npages` the
+number of contiguous pages available for writing the certificate data
+into.
+
+ - If the supplied number of pages is sufficient, userspace must write
+ the certificate table blob (in the format defined by the GHCB spec)
+ into the address corresponding to `gfn` and set `ret` to 0 to indicate
+ success. If no certificate data is available, then userspace can
+ either write an empty certificate table into the address corresponding
+ to `gfn`, or it can disable ``KVM_EXIT_SNP_REQ_CERTS`` (via
+ ``KVM_CAP_EXIT_SNP_REQ_CERTS``), in which case KVM will handle
+ returning an empty certificate table to the guest.
+
+ - If the number of pages supplied is not sufficient, userspace must set
+ the required number of pages in `npages` and then set `ret` to
+ ``ENOSPC``.
+
+ - If the certificate cannot be immediately provided, userspace should set
+ `ret` to ``EAGAIN``, which will inform the guest to retry the request
+ later. One scenario where this would be useful is if the certificate
+ is in the process of being updated and cannot be fetched until the
+ update completes (see the NOTE below regarding how file-locking can
+ be used to orchestrate such updates between management/guests).
+
+ - If some other error occurred, userspace must set `ret` to ``EIO``.
+ (This is to reserve special meaning for unused error codes in the
+ future.)
+
+NOTE: The endorsement key used by firmware may change as a result of
+management activities like updating SEV-SNP firmware or loading new
+endorsement keys, so some care should be taken to keep the returned
+certificate data in sync with the actual endorsement key in use by
+firmware at the time the attestation request is sent to SNP firmware. The
+recommended scheme to do this is to use file locking (e.g. via fcntl()'s
+F_OFD_SETLK) in the following manner:
+
+ - The VMM should obtain a shared/read or exclusive/write lock on the
+ certificate blob file before reading it and returning it to KVM, and
+ continue to hold the lock until the attestation request is actually
+ sent to firmware. To facilitate this, the VMM can set the
+ ``immediate_exit`` flag of kvm_run just after supplying the
+ certificate data, and just before and resuming the vCPU. This will
+ ensure the vCPU will exit again to userspace with ``-EINTR`` after
+ it finishes fetching the attestation request from firmware, at which
+ point the VMM can safely drop the file lock.
+
+ - Tools/libraries that perform updates to SNP firmware TCB values or
+ endorsement keys (e.g. via /dev/sev interfaces such as ``SNP_COMMIT``,
+ ``SNP_SET_CONFIG``, or ``SNP_VLEK_LOAD``, see
+ Documentation/virt/coco/sev-guest.rst for more details) in such a way
+ that the certificate blob needs to be updated, should similarly take an
+ exclusive lock on the certificate blob for the duration of any updates
+ to endorsement keys or the certificate blob contents to ensure that
+ VMMs using the above scheme will not return certificate blob data that
+ is out of sync with the endorsement key used by firmware.
+
+This scheme is recommended so that tools could naturally opt to use
+it rather than every service provider coming up with a different solution
+that they will need to work into some custom QEMU/VMM (or other related
+management tools that handle firmware updates/configuration) to solve the
+same problem, but userspace will need to ensure all such tools are using
+this scheme in order for it to work as intended.
.. _cap_enable:
@@ -9024,6 +9107,23 @@ Do not use KVM_X86_SW_PROTECTED_VM for "real" VMs, and especially not in
production. The behavior and effective ABI for software-protected VMs is
unstable.
+8.42 KVM_CAP_EXIT_SNP_REQ_CERTS
+-------------------------------
+
+:Capability: KVM_CAP_EXIT_SNP_REQ_CERTS
+:Architectures: x86
+:Type: vm
+
+This capability, if enabled, will cause KVM to exit to userspace with
+KVM_EXIT_SNP_REQ_CERTS exit reason to allow for fetching SNP attestation
+certificates from userspace.
+
+Calling KVM_CHECK_EXTENSION for this capability will return a non-zero
+value to indicate KVM support for KVM_EXIT_SNP_REQ_CERTS.
+
+The 1st argument to KVM_ENABLE_CAP should be 1 to indicate userspace support
+for handling this event.
+
9. Known KVM API problems
=========================
@@ -1459,6 +1459,7 @@ struct kvm_arch {
struct kvm_x86_msr_filter __rcu *msr_filter;
u32 hypercall_exit_enabled;
+ bool snp_certs_enabled;
/* Guest can access the SGX PROVISIONKEY. */
bool sgx_provisioning_allowed;
@@ -4088,6 +4088,30 @@ static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_
return ret;
}
+static int snp_complete_req_certs(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct vmcb_control_area *control = &svm->vmcb->control;
+
+ if (vcpu->run->snp_req_certs.ret) {
+ if (vcpu->run->snp_req_certs.ret == ENOSPC) {
+ vcpu->arch.regs[VCPU_REGS_RBX] = vcpu->run->snp_req_certs.npages;
+ ghcb_set_sw_exit_info_2(svm->sev_es.ghcb,
+ SNP_GUEST_ERR(SNP_GUEST_VMM_ERR_INVALID_LEN, 0));
+ } else if (vcpu->run->snp_req_certs.ret == EAGAIN) {
+ ghcb_set_sw_exit_info_2(svm->sev_es.ghcb,
+ SNP_GUEST_ERR(SNP_GUEST_VMM_ERR_BUSY, 0));
+ } else {
+ ghcb_set_sw_exit_info_2(svm->sev_es.ghcb,
+ SNP_GUEST_ERR(SNP_GUEST_VMM_ERR_GENERIC, 0));
+ }
+
+ return 1; /* resume guest */
+ }
+
+ return snp_handle_guest_req(svm, control->exit_info_1, control->exit_info_2);
+}
+
static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_gpa)
{
struct kvm *kvm = svm->vcpu.kvm;
@@ -4103,12 +4127,10 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r
/*
* As per GHCB spec, requests of type MSG_REPORT_REQ also allow for
* additional certificate data to be provided alongside the attestation
- * report via the guest-provided data pages indicated by RAX/RBX. The
- * certificate data is optional and requires additional KVM enablement
- * to provide an interface for userspace to provide it, but KVM still
- * needs to be able to handle extended guest requests either way. So
- * provide a stub implementation that will always return an empty
- * certificate table in the guest-provided data pages.
+ * report via the guest-provided data pages indicated by RAX/RBX. If
+ * userspace enables KVM_EXIT_SNP_REQ_CERTS, then exit to userspace
+ * to fetch the certificate data. Otherwise, return an empty certificate
+ * table in the guest-provided data pages.
*/
if (msg_type == SNP_MSG_REPORT_REQ) {
struct kvm_vcpu *vcpu = &svm->vcpu;
@@ -4124,6 +4146,15 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r
if (!PAGE_ALIGNED(data_gpa))
goto request_invalid;
+ if (vcpu->kvm->arch.snp_certs_enabled) {
+ vcpu->run->exit_reason = KVM_EXIT_SNP_REQ_CERTS;
+ vcpu->run->snp_req_certs.gfn = gpa_to_gfn(data_gpa);
+ vcpu->run->snp_req_certs.npages = data_npages;
+ vcpu->run->snp_req_certs.ret = 0;
+ vcpu->arch.complete_userspace_io = snp_complete_req_certs;
+ return 0; /* fetch certs from userspace */
+ }
+
/*
* As per GHCB spec (see "SNP Extended Guest Request"), the
* certificate table is terminated by 24-bytes of zeroes.
@@ -4774,6 +4774,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_READONLY_MEM:
r = kvm ? kvm_arch_has_readonly_mem(kvm) : 1;
break;
+ case KVM_CAP_EXIT_SNP_REQ_CERTS:
+ r = 1;
+ break;
default:
break;
}
@@ -6734,6 +6737,14 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
mutex_unlock(&kvm->lock);
break;
}
+ case KVM_CAP_EXIT_SNP_REQ_CERTS:
+ if (cap->args[0] != 1) {
+ r = -EINVAL;
+ break;
+ }
+ kvm->arch.snp_certs_enabled = true;
+ r = 0;
+ break;
default:
r = -EINVAL;
break;
@@ -135,6 +135,12 @@ struct kvm_xen_exit {
} u;
};
+struct kvm_exit_snp_req_certs {
+ __u64 gfn;
+ __u32 npages;
+ __u32 ret;
+};
+
#define KVM_S390_GET_SKEYS_NONE 1
#define KVM_S390_SKEYS_MAX 1048576
@@ -178,6 +184,7 @@ struct kvm_xen_exit {
#define KVM_EXIT_NOTIFY 37
#define KVM_EXIT_LOONGARCH_IOCSR 38
#define KVM_EXIT_MEMORY_FAULT 39
+#define KVM_EXIT_SNP_REQ_CERTS 40
/* For KVM_EXIT_INTERNAL_ERROR */
/* Emulate instruction failed. */
@@ -446,6 +453,8 @@ struct kvm_run {
__u64 gpa;
__u64 size;
} memory_fault;
+ /* KVM_EXIT_SNP_REQ_CERTS */
+ struct kvm_exit_snp_req_certs snp_req_certs;
/* Fix the size of the union. */
char padding[256];
};
@@ -929,6 +938,7 @@ struct kvm_enable_cap {
#define KVM_CAP_PRE_FAULT_MEMORY 236
#define KVM_CAP_X86_APIC_BUS_CYCLES_NS 237
#define KVM_CAP_X86_GUEST_MODE 238
+#define KVM_CAP_EXIT_SNP_REQ_CERTS 239
struct kvm_irq_routing_irqchip {
__u32 irqchip;
@@ -95,5 +95,13 @@ struct snp_ext_report_req {
#define SNP_GUEST_VMM_ERR_INVALID_LEN 1
#define SNP_GUEST_VMM_ERR_BUSY 2
+/*
+ * The GHCB spec essentially states that all non-zero error codes other than
+ * those explicitly defined above should be treated as an error by the guest.
+ * Define a generic error to cover that case, and choose a value that is not
+ * likely to overlap with new explicit error codes should more be added to
+ * the GHCB spec later.
+ */
+#define SNP_GUEST_VMM_ERR_GENERIC ((u32)~0U)
#endif /* __UAPI_LINUX_SEV_GUEST_H_ */