Skip to content

Commit dee5a47

Browse files
codomaniabonzini
authored andcommitted
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>
1 parent 136d8bc commit dee5a47

3 files changed

Lines changed: 303 additions & 0 deletions

File tree

Documentation/virt/kvm/x86/amd-memory-encryption.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,60 @@ Returns: 0 on success, -negative on error
490490
See SNP_LAUNCH_START in the SEV-SNP specification [snp-fw-abi]_ for further
491491
details on the input parameters in ``struct kvm_sev_snp_launch_start``.
492492

493+
19. KVM_SEV_SNP_LAUNCH_UPDATE
494+
-----------------------------
495+
496+
The KVM_SEV_SNP_LAUNCH_UPDATE command is used for loading userspace-provided
497+
data into a guest GPA range, measuring the contents into the SNP guest context
498+
created by KVM_SEV_SNP_LAUNCH_START, and then encrypting/validating that GPA
499+
range so that it will be immediately readable using the encryption key
500+
associated with the guest context once it is booted, after which point it can
501+
attest the measurement associated with its context before unlocking any
502+
secrets.
503+
504+
It is required that the GPA ranges initialized by this command have had the
505+
KVM_MEMORY_ATTRIBUTE_PRIVATE attribute set in advance. See the documentation
506+
for KVM_SET_MEMORY_ATTRIBUTES for more details on this aspect.
507+
508+
Upon success, this command is not guaranteed to have processed the entire
509+
range requested. Instead, the ``gfn_start``, ``uaddr``, and ``len`` fields of
510+
``struct kvm_sev_snp_launch_update`` will be updated to correspond to the
511+
remaining range that has yet to be processed. The caller should continue
512+
calling this command until those fields indicate the entire range has been
513+
processed, e.g. ``len`` is 0, ``gfn_start`` is equal to the last GFN in the
514+
range plus 1, and ``uaddr`` is the last byte of the userspace-provided source
515+
buffer address plus 1. In the case where ``type`` is KVM_SEV_SNP_PAGE_TYPE_ZERO,
516+
``uaddr`` will be ignored completely.
517+
518+
Parameters (in): struct kvm_sev_snp_launch_update
519+
520+
Returns: 0 on success, < 0 on error, -EAGAIN if caller should retry
521+
522+
::
523+
524+
struct kvm_sev_snp_launch_update {
525+
__u64 gfn_start; /* Guest page number to load/encrypt data into. */
526+
__u64 uaddr; /* Userspace address of data to be loaded/encrypted. */
527+
__u64 len; /* 4k-aligned length in bytes to copy into guest memory.*/
528+
__u8 type; /* The type of the guest pages being initialized. */
529+
__u8 pad0;
530+
__u16 flags; /* Must be zero. */
531+
__u32 pad1;
532+
__u64 pad2[4];
533+
534+
};
535+
536+
where the allowed values for page_type are #define'd as::
537+
538+
KVM_SEV_SNP_PAGE_TYPE_NORMAL
539+
KVM_SEV_SNP_PAGE_TYPE_ZERO
540+
KVM_SEV_SNP_PAGE_TYPE_UNMEASURED
541+
KVM_SEV_SNP_PAGE_TYPE_SECRETS
542+
KVM_SEV_SNP_PAGE_TYPE_CPUID
543+
544+
See the SEV-SNP spec [snp-fw-abi]_ for further details on how each page type is
545+
used/measured.
546+
493547
Device attribute API
494548
====================
495549

arch/x86/include/uapi/asm/kvm.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ enum sev_cmd_id {
699699

700700
/* SNP-specific commands */
701701
KVM_SEV_SNP_LAUNCH_START = 100,
702+
KVM_SEV_SNP_LAUNCH_UPDATE,
702703

703704
KVM_SEV_NR_MAX,
704705
};
@@ -835,6 +836,24 @@ struct kvm_sev_snp_launch_start {
835836
__u64 pad1[4];
836837
};
837838

839+
/* Kept in sync with firmware values for simplicity. */
840+
#define KVM_SEV_SNP_PAGE_TYPE_NORMAL 0x1
841+
#define KVM_SEV_SNP_PAGE_TYPE_ZERO 0x3
842+
#define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED 0x4
843+
#define KVM_SEV_SNP_PAGE_TYPE_SECRETS 0x5
844+
#define KVM_SEV_SNP_PAGE_TYPE_CPUID 0x6
845+
846+
struct kvm_sev_snp_launch_update {
847+
__u64 gfn_start;
848+
__u64 uaddr;
849+
__u64 len;
850+
__u8 type;
851+
__u8 pad0;
852+
__u16 flags;
853+
__u32 pad1;
854+
__u64 pad2[4];
855+
};
856+
838857
#define KVM_X2APIC_API_USE_32BIT_IDS (1ULL << 0)
839858
#define KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK (1ULL << 1)
840859

arch/x86/kvm/svm/sev.c

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,45 @@ static void sev_decommission(unsigned int handle)
259259
sev_guest_decommission(&decommission, NULL);
260260
}
261261

262+
/*
263+
* Certain page-states, such as Pre-Guest and Firmware pages (as documented
264+
* in Chapter 5 of the SEV-SNP Firmware ABI under "Page States") cannot be
265+
* directly transitioned back to normal/hypervisor-owned state via RMPUPDATE
266+
* unless they are reclaimed first.
267+
*
268+
* Until they are reclaimed and subsequently transitioned via RMPUPDATE, they
269+
* might not be usable by the host due to being set as immutable or still
270+
* being associated with a guest ASID.
271+
*/
272+
static int snp_page_reclaim(u64 pfn)
273+
{
274+
struct sev_data_snp_page_reclaim data = {0};
275+
int err, rc;
276+
277+
data.paddr = __sme_set(pfn << PAGE_SHIFT);
278+
rc = sev_do_cmd(SEV_CMD_SNP_PAGE_RECLAIM, &data, &err);
279+
if (WARN_ONCE(rc, "Failed to reclaim PFN %llx", pfn))
280+
snp_leak_pages(pfn, 1);
281+
282+
return rc;
283+
}
284+
285+
/*
286+
* Transition a page to hypervisor-owned/shared state in the RMP table. This
287+
* should not fail under normal conditions, but leak the page should that
288+
* happen since it will no longer be usable by the host due to RMP protections.
289+
*/
290+
static int host_rmp_make_shared(u64 pfn, enum pg_level level)
291+
{
292+
int rc;
293+
294+
rc = rmp_make_shared(pfn, level);
295+
if (WARN_ON_ONCE(rc))
296+
snp_leak_pages(pfn, page_level_size(level) >> PAGE_SHIFT);
297+
298+
return rc;
299+
}
300+
262301
static void sev_unbind_asid(struct kvm *kvm, unsigned int handle)
263302
{
264303
struct sev_data_deactivate deactivate;
@@ -2121,6 +2160,194 @@ static int snp_launch_start(struct kvm *kvm, struct kvm_sev_cmd *argp)
21212160
return rc;
21222161
}
21232162

2163+
struct sev_gmem_populate_args {
2164+
__u8 type;
2165+
int sev_fd;
2166+
int fw_error;
2167+
};
2168+
2169+
static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn_start, kvm_pfn_t pfn,
2170+
void __user *src, int order, void *opaque)
2171+
{
2172+
struct sev_gmem_populate_args *sev_populate_args = opaque;
2173+
struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
2174+
int n_private = 0, ret, i;
2175+
int npages = (1 << order);
2176+
gfn_t gfn;
2177+
2178+
if (WARN_ON_ONCE(sev_populate_args->type != KVM_SEV_SNP_PAGE_TYPE_ZERO && !src))
2179+
return -EINVAL;
2180+
2181+
for (gfn = gfn_start, i = 0; gfn < gfn_start + npages; gfn++, i++) {
2182+
struct sev_data_snp_launch_update fw_args = {0};
2183+
bool assigned;
2184+
int level;
2185+
2186+
if (!kvm_mem_is_private(kvm, gfn)) {
2187+
pr_debug("%s: Failed to ensure GFN 0x%llx has private memory attribute set\n",
2188+
__func__, gfn);
2189+
ret = -EINVAL;
2190+
goto err;
2191+
}
2192+
2193+
ret = snp_lookup_rmpentry((u64)pfn + i, &assigned, &level);
2194+
if (ret || assigned) {
2195+
pr_debug("%s: Failed to ensure GFN 0x%llx RMP entry is initial shared state, ret: %d assigned: %d\n",
2196+
__func__, gfn, ret, assigned);
2197+
ret = -EINVAL;
2198+
goto err;
2199+
}
2200+
2201+
if (src) {
2202+
void *vaddr = kmap_local_pfn(pfn + i);
2203+
2204+
ret = copy_from_user(vaddr, src + i * PAGE_SIZE, PAGE_SIZE);
2205+
if (ret)
2206+
goto err;
2207+
kunmap_local(vaddr);
2208+
}
2209+
2210+
ret = rmp_make_private(pfn + i, gfn << PAGE_SHIFT, PG_LEVEL_4K,
2211+
sev_get_asid(kvm), true);
2212+
if (ret)
2213+
goto err;
2214+
2215+
n_private++;
2216+
2217+
fw_args.gctx_paddr = __psp_pa(sev->snp_context);
2218+
fw_args.address = __sme_set(pfn_to_hpa(pfn + i));
2219+
fw_args.page_size = PG_LEVEL_TO_RMP(PG_LEVEL_4K);
2220+
fw_args.page_type = sev_populate_args->type;
2221+
2222+
ret = __sev_issue_cmd(sev_populate_args->sev_fd, SEV_CMD_SNP_LAUNCH_UPDATE,
2223+
&fw_args, &sev_populate_args->fw_error);
2224+
if (ret)
2225+
goto fw_err;
2226+
}
2227+
2228+
return 0;
2229+
2230+
fw_err:
2231+
/*
2232+
* If the firmware command failed handle the reclaim and cleanup of that
2233+
* PFN specially vs. prior pages which can be cleaned up below without
2234+
* needing to reclaim in advance.
2235+
*
2236+
* Additionally, when invalid CPUID function entries are detected,
2237+
* firmware writes the expected values into the page and leaves it
2238+
* unencrypted so it can be used for debugging and error-reporting.
2239+
*
2240+
* Copy this page back into the source buffer so userspace can use this
2241+
* information to provide information on which CPUID leaves/fields
2242+
* failed CPUID validation.
2243+
*/
2244+
if (!snp_page_reclaim(pfn + i) && !host_rmp_make_shared(pfn + i, PG_LEVEL_4K) &&
2245+
sev_populate_args->type == KVM_SEV_SNP_PAGE_TYPE_CPUID &&
2246+
sev_populate_args->fw_error == SEV_RET_INVALID_PARAM) {
2247+
void *vaddr = kmap_local_pfn(pfn + i);
2248+
2249+
if (copy_to_user(src + i * PAGE_SIZE, vaddr, PAGE_SIZE))
2250+
pr_debug("Failed to write CPUID page back to userspace\n");
2251+
2252+
kunmap_local(vaddr);
2253+
}
2254+
2255+
/* pfn + i is hypervisor-owned now, so skip below cleanup for it. */
2256+
n_private--;
2257+
2258+
err:
2259+
pr_debug("%s: exiting with error ret %d (fw_error %d), restoring %d gmem PFNs to shared.\n",
2260+
__func__, ret, sev_populate_args->fw_error, n_private);
2261+
for (i = 0; i < n_private; i++)
2262+
host_rmp_make_shared(pfn + i, PG_LEVEL_4K);
2263+
2264+
return ret;
2265+
}
2266+
2267+
static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
2268+
{
2269+
struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
2270+
struct sev_gmem_populate_args sev_populate_args = {0};
2271+
struct kvm_sev_snp_launch_update params;
2272+
struct kvm_memory_slot *memslot;
2273+
long npages, count;
2274+
void __user *src;
2275+
int ret = 0;
2276+
2277+
if (!sev_snp_guest(kvm) || !sev->snp_context)
2278+
return -EINVAL;
2279+
2280+
if (copy_from_user(&params, u64_to_user_ptr(argp->data), sizeof(params)))
2281+
return -EFAULT;
2282+
2283+
pr_debug("%s: GFN start 0x%llx length 0x%llx type %d flags %d\n", __func__,
2284+
params.gfn_start, params.len, params.type, params.flags);
2285+
2286+
if (!PAGE_ALIGNED(params.len) || params.flags ||
2287+
(params.type != KVM_SEV_SNP_PAGE_TYPE_NORMAL &&
2288+
params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO &&
2289+
params.type != KVM_SEV_SNP_PAGE_TYPE_UNMEASURED &&
2290+
params.type != KVM_SEV_SNP_PAGE_TYPE_SECRETS &&
2291+
params.type != KVM_SEV_SNP_PAGE_TYPE_CPUID))
2292+
return -EINVAL;
2293+
2294+
npages = params.len / PAGE_SIZE;
2295+
2296+
/*
2297+
* For each GFN that's being prepared as part of the initial guest
2298+
* state, the following pre-conditions are verified:
2299+
*
2300+
* 1) The backing memslot is a valid private memslot.
2301+
* 2) The GFN has been set to private via KVM_SET_MEMORY_ATTRIBUTES
2302+
* beforehand.
2303+
* 3) The PFN of the guest_memfd has not already been set to private
2304+
* in the RMP table.
2305+
*
2306+
* The KVM MMU relies on kvm->mmu_invalidate_seq to retry nested page
2307+
* faults if there's a race between a fault and an attribute update via
2308+
* KVM_SET_MEMORY_ATTRIBUTES, and a similar approach could be utilized
2309+
* here. However, kvm->slots_lock guards against both this as well as
2310+
* concurrent memslot updates occurring while these checks are being
2311+
* performed, so use that here to make it easier to reason about the
2312+
* initial expected state and better guard against unexpected
2313+
* situations.
2314+
*/
2315+
mutex_lock(&kvm->slots_lock);
2316+
2317+
memslot = gfn_to_memslot(kvm, params.gfn_start);
2318+
if (!kvm_slot_can_be_private(memslot)) {
2319+
ret = -EINVAL;
2320+
goto out;
2321+
}
2322+
2323+
sev_populate_args.sev_fd = argp->sev_fd;
2324+
sev_populate_args.type = params.type;
2325+
src = params.type == KVM_SEV_SNP_PAGE_TYPE_ZERO ? NULL : u64_to_user_ptr(params.uaddr);
2326+
2327+
count = kvm_gmem_populate(kvm, params.gfn_start, src, npages,
2328+
sev_gmem_post_populate, &sev_populate_args);
2329+
if (count < 0) {
2330+
argp->error = sev_populate_args.fw_error;
2331+
pr_debug("%s: kvm_gmem_populate failed, ret %ld (fw_error %d)\n",
2332+
__func__, count, argp->error);
2333+
ret = -EIO;
2334+
} else {
2335+
params.gfn_start += count;
2336+
params.len -= count * PAGE_SIZE;
2337+
if (params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO)
2338+
params.uaddr += count * PAGE_SIZE;
2339+
2340+
ret = 0;
2341+
if (copy_to_user(u64_to_user_ptr(argp->data), &params, sizeof(params)))
2342+
ret = -EFAULT;
2343+
}
2344+
2345+
out:
2346+
mutex_unlock(&kvm->slots_lock);
2347+
2348+
return ret;
2349+
}
2350+
21242351
int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
21252352
{
21262353
struct kvm_sev_cmd sev_cmd;
@@ -2220,6 +2447,9 @@ int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
22202447
case KVM_SEV_SNP_LAUNCH_START:
22212448
r = snp_launch_start(kvm, &sev_cmd);
22222449
break;
2450+
case KVM_SEV_SNP_LAUNCH_UPDATE:
2451+
r = snp_launch_update(kvm, &sev_cmd);
2452+
break;
22232453
default:
22242454
r = -EINVAL;
22252455
goto out;

0 commit comments

Comments
 (0)