KVM: SEV: Add KVM_SEV_SNP_LAUNCH_UPDATE command

A key aspect of a launching an SNP guest is initializing it with a
known/measured payload which is then encrypted into guest memory as
pre-validated private pages and then measured into the cryptographic
launch context created with KVM_SEV_SNP_LAUNCH_START so that the guest
can attest itself after booting.

Since all private pages are provided by guest_memfd, make use of the
kvm_gmem_populate() interface to handle this. The general flow is that
guest_memfd will handle allocating the pages associated with the GPA
ranges being initialized by each particular call of
KVM_SEV_SNP_LAUNCH_UPDATE, copying data from userspace into those pages,
and then the post_populate callback will do the work of setting the
RMP entries for these pages to private and issuing the SNP firmware
calls to encrypt/measure them.

For more information see the SEV-SNP specification.

Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
Co-developed-by: Michael Roth <michael.roth@amd.com>
Signed-off-by: Michael Roth <michael.roth@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Message-ID: <20240501085210.2213060-7-michael.roth@amd.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Brijesh Singh 2024-05-01 03:51:56 -05:00 committed by Paolo Bonzini
parent 136d8bc931
commit dee5a47cc7
3 changed files with 303 additions and 0 deletions

View File

@ -490,6 +490,60 @@ Returns: 0 on success, -negative on error
See SNP_LAUNCH_START in the SEV-SNP specification [snp-fw-abi]_ for further
details on the input parameters in ``struct kvm_sev_snp_launch_start``.
19. KVM_SEV_SNP_LAUNCH_UPDATE
-----------------------------
The KVM_SEV_SNP_LAUNCH_UPDATE command is used for loading userspace-provided
data into a guest GPA range, measuring the contents into the SNP guest context
created by KVM_SEV_SNP_LAUNCH_START, and then encrypting/validating that GPA
range so that it will be immediately readable using the encryption key
associated with the guest context once it is booted, after which point it can
attest the measurement associated with its context before unlocking any
secrets.
It is required that the GPA ranges initialized by this command have had the
KVM_MEMORY_ATTRIBUTE_PRIVATE attribute set in advance. See the documentation
for KVM_SET_MEMORY_ATTRIBUTES for more details on this aspect.
Upon success, this command is not guaranteed to have processed the entire
range requested. Instead, the ``gfn_start``, ``uaddr``, and ``len`` fields of
``struct kvm_sev_snp_launch_update`` will be updated to correspond to the
remaining range that has yet to be processed. The caller should continue
calling this command until those fields indicate the entire range has been
processed, e.g. ``len`` is 0, ``gfn_start`` is equal to the last GFN in the
range plus 1, and ``uaddr`` is the last byte of the userspace-provided source
buffer address plus 1. In the case where ``type`` is KVM_SEV_SNP_PAGE_TYPE_ZERO,
``uaddr`` will be ignored completely.
Parameters (in): struct kvm_sev_snp_launch_update
Returns: 0 on success, < 0 on error, -EAGAIN if caller should retry
::
struct kvm_sev_snp_launch_update {
__u64 gfn_start; /* Guest page number to load/encrypt data into. */
__u64 uaddr; /* Userspace address of data to be loaded/encrypted. */
__u64 len; /* 4k-aligned length in bytes to copy into guest memory.*/
__u8 type; /* The type of the guest pages being initialized. */
__u8 pad0;
__u16 flags; /* Must be zero. */
__u32 pad1;
__u64 pad2[4];
};
where the allowed values for page_type are #define'd as::
KVM_SEV_SNP_PAGE_TYPE_NORMAL
KVM_SEV_SNP_PAGE_TYPE_ZERO
KVM_SEV_SNP_PAGE_TYPE_UNMEASURED
KVM_SEV_SNP_PAGE_TYPE_SECRETS
KVM_SEV_SNP_PAGE_TYPE_CPUID
See the SEV-SNP spec [snp-fw-abi]_ for further details on how each page type is
used/measured.
Device attribute API
====================

View File

@ -699,6 +699,7 @@ enum sev_cmd_id {
/* SNP-specific commands */
KVM_SEV_SNP_LAUNCH_START = 100,
KVM_SEV_SNP_LAUNCH_UPDATE,
KVM_SEV_NR_MAX,
};
@ -835,6 +836,24 @@ struct kvm_sev_snp_launch_start {
__u64 pad1[4];
};
/* Kept in sync with firmware values for simplicity. */
#define KVM_SEV_SNP_PAGE_TYPE_NORMAL 0x1
#define KVM_SEV_SNP_PAGE_TYPE_ZERO 0x3
#define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED 0x4
#define KVM_SEV_SNP_PAGE_TYPE_SECRETS 0x5
#define KVM_SEV_SNP_PAGE_TYPE_CPUID 0x6
struct kvm_sev_snp_launch_update {
__u64 gfn_start;
__u64 uaddr;
__u64 len;
__u8 type;
__u8 pad0;
__u16 flags;
__u32 pad1;
__u64 pad2[4];
};
#define KVM_X2APIC_API_USE_32BIT_IDS (1ULL << 0)
#define KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK (1ULL << 1)

View File

@ -259,6 +259,45 @@ static void sev_decommission(unsigned int handle)
sev_guest_decommission(&decommission, NULL);
}
/*
* Certain page-states, such as Pre-Guest and Firmware pages (as documented
* in Chapter 5 of the SEV-SNP Firmware ABI under "Page States") cannot be
* directly transitioned back to normal/hypervisor-owned state via RMPUPDATE
* unless they are reclaimed first.
*
* Until they are reclaimed and subsequently transitioned via RMPUPDATE, they
* might not be usable by the host due to being set as immutable or still
* being associated with a guest ASID.
*/
static int snp_page_reclaim(u64 pfn)
{
struct sev_data_snp_page_reclaim data = {0};
int err, rc;
data.paddr = __sme_set(pfn << PAGE_SHIFT);
rc = sev_do_cmd(SEV_CMD_SNP_PAGE_RECLAIM, &data, &err);
if (WARN_ONCE(rc, "Failed to reclaim PFN %llx", pfn))
snp_leak_pages(pfn, 1);
return rc;
}
/*
* Transition a page to hypervisor-owned/shared state in the RMP table. This
* should not fail under normal conditions, but leak the page should that
* happen since it will no longer be usable by the host due to RMP protections.
*/
static int host_rmp_make_shared(u64 pfn, enum pg_level level)
{
int rc;
rc = rmp_make_shared(pfn, level);
if (WARN_ON_ONCE(rc))
snp_leak_pages(pfn, page_level_size(level) >> PAGE_SHIFT);
return rc;
}
static void sev_unbind_asid(struct kvm *kvm, unsigned int handle)
{
struct sev_data_deactivate deactivate;
@ -2121,6 +2160,194 @@ e_free_context:
return rc;
}
struct sev_gmem_populate_args {
__u8 type;
int sev_fd;
int fw_error;
};
static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn_start, kvm_pfn_t pfn,
void __user *src, int order, void *opaque)
{
struct sev_gmem_populate_args *sev_populate_args = opaque;
struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
int n_private = 0, ret, i;
int npages = (1 << order);
gfn_t gfn;
if (WARN_ON_ONCE(sev_populate_args->type != KVM_SEV_SNP_PAGE_TYPE_ZERO && !src))
return -EINVAL;
for (gfn = gfn_start, i = 0; gfn < gfn_start + npages; gfn++, i++) {
struct sev_data_snp_launch_update fw_args = {0};
bool assigned;
int level;
if (!kvm_mem_is_private(kvm, gfn)) {
pr_debug("%s: Failed to ensure GFN 0x%llx has private memory attribute set\n",
__func__, gfn);
ret = -EINVAL;
goto err;
}
ret = snp_lookup_rmpentry((u64)pfn + i, &assigned, &level);
if (ret || assigned) {
pr_debug("%s: Failed to ensure GFN 0x%llx RMP entry is initial shared state, ret: %d assigned: %d\n",
__func__, gfn, ret, assigned);
ret = -EINVAL;
goto err;
}
if (src) {
void *vaddr = kmap_local_pfn(pfn + i);
ret = copy_from_user(vaddr, src + i * PAGE_SIZE, PAGE_SIZE);
if (ret)
goto err;
kunmap_local(vaddr);
}
ret = rmp_make_private(pfn + i, gfn << PAGE_SHIFT, PG_LEVEL_4K,
sev_get_asid(kvm), true);
if (ret)
goto err;
n_private++;
fw_args.gctx_paddr = __psp_pa(sev->snp_context);
fw_args.address = __sme_set(pfn_to_hpa(pfn + i));
fw_args.page_size = PG_LEVEL_TO_RMP(PG_LEVEL_4K);
fw_args.page_type = sev_populate_args->type;
ret = __sev_issue_cmd(sev_populate_args->sev_fd, SEV_CMD_SNP_LAUNCH_UPDATE,
&fw_args, &sev_populate_args->fw_error);
if (ret)
goto fw_err;
}
return 0;
fw_err:
/*
* If the firmware command failed handle the reclaim and cleanup of that
* PFN specially vs. prior pages which can be cleaned up below without
* needing to reclaim in advance.
*
* Additionally, when invalid CPUID function entries are detected,
* firmware writes the expected values into the page and leaves it
* unencrypted so it can be used for debugging and error-reporting.
*
* Copy this page back into the source buffer so userspace can use this
* information to provide information on which CPUID leaves/fields
* failed CPUID validation.
*/
if (!snp_page_reclaim(pfn + i) && !host_rmp_make_shared(pfn + i, PG_LEVEL_4K) &&
sev_populate_args->type == KVM_SEV_SNP_PAGE_TYPE_CPUID &&
sev_populate_args->fw_error == SEV_RET_INVALID_PARAM) {
void *vaddr = kmap_local_pfn(pfn + i);
if (copy_to_user(src + i * PAGE_SIZE, vaddr, PAGE_SIZE))
pr_debug("Failed to write CPUID page back to userspace\n");
kunmap_local(vaddr);
}
/* pfn + i is hypervisor-owned now, so skip below cleanup for it. */
n_private--;
err:
pr_debug("%s: exiting with error ret %d (fw_error %d), restoring %d gmem PFNs to shared.\n",
__func__, ret, sev_populate_args->fw_error, n_private);
for (i = 0; i < n_private; i++)
host_rmp_make_shared(pfn + i, PG_LEVEL_4K);
return ret;
}
static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
{
struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
struct sev_gmem_populate_args sev_populate_args = {0};
struct kvm_sev_snp_launch_update params;
struct kvm_memory_slot *memslot;
long npages, count;
void __user *src;
int ret = 0;
if (!sev_snp_guest(kvm) || !sev->snp_context)
return -EINVAL;
if (copy_from_user(&params, u64_to_user_ptr(argp->data), sizeof(params)))
return -EFAULT;
pr_debug("%s: GFN start 0x%llx length 0x%llx type %d flags %d\n", __func__,
params.gfn_start, params.len, params.type, params.flags);
if (!PAGE_ALIGNED(params.len) || params.flags ||
(params.type != KVM_SEV_SNP_PAGE_TYPE_NORMAL &&
params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO &&
params.type != KVM_SEV_SNP_PAGE_TYPE_UNMEASURED &&
params.type != KVM_SEV_SNP_PAGE_TYPE_SECRETS &&
params.type != KVM_SEV_SNP_PAGE_TYPE_CPUID))
return -EINVAL;
npages = params.len / PAGE_SIZE;
/*
* For each GFN that's being prepared as part of the initial guest
* state, the following pre-conditions are verified:
*
* 1) The backing memslot is a valid private memslot.
* 2) The GFN has been set to private via KVM_SET_MEMORY_ATTRIBUTES
* beforehand.
* 3) The PFN of the guest_memfd has not already been set to private
* in the RMP table.
*
* The KVM MMU relies on kvm->mmu_invalidate_seq to retry nested page
* faults if there's a race between a fault and an attribute update via
* KVM_SET_MEMORY_ATTRIBUTES, and a similar approach could be utilized
* here. However, kvm->slots_lock guards against both this as well as
* concurrent memslot updates occurring while these checks are being
* performed, so use that here to make it easier to reason about the
* initial expected state and better guard against unexpected
* situations.
*/
mutex_lock(&kvm->slots_lock);
memslot = gfn_to_memslot(kvm, params.gfn_start);
if (!kvm_slot_can_be_private(memslot)) {
ret = -EINVAL;
goto out;
}
sev_populate_args.sev_fd = argp->sev_fd;
sev_populate_args.type = params.type;
src = params.type == KVM_SEV_SNP_PAGE_TYPE_ZERO ? NULL : u64_to_user_ptr(params.uaddr);
count = kvm_gmem_populate(kvm, params.gfn_start, src, npages,
sev_gmem_post_populate, &sev_populate_args);
if (count < 0) {
argp->error = sev_populate_args.fw_error;
pr_debug("%s: kvm_gmem_populate failed, ret %ld (fw_error %d)\n",
__func__, count, argp->error);
ret = -EIO;
} else {
params.gfn_start += count;
params.len -= count * PAGE_SIZE;
if (params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO)
params.uaddr += count * PAGE_SIZE;
ret = 0;
if (copy_to_user(u64_to_user_ptr(argp->data), &params, sizeof(params)))
ret = -EFAULT;
}
out:
mutex_unlock(&kvm->slots_lock);
return ret;
}
int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
{
struct kvm_sev_cmd sev_cmd;
@ -2220,6 +2447,9 @@ int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
case KVM_SEV_SNP_LAUNCH_START:
r = snp_launch_start(kvm, &sev_cmd);
break;
case KVM_SEV_SNP_LAUNCH_UPDATE:
r = snp_launch_update(kvm, &sev_cmd);
break;
default:
r = -EINVAL;
goto out;