qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Dov Murik <dovmurik@linux.vnet.ibm.com>
To: qemu-devel@nongnu.org
Cc: Tom Lendacky <thomas.lendacky@amd.com>,
	Ashish Kalra <ashish.kalra@amd.com>,
	Brijesh Singh <brijesh.singh@amd.com>,
	Juan Quintela <quintela@redhat.com>,
	James Bottomley <jejb@linux.ibm.com>,
	Jon Grimm <jon.grimm@amd.com>,
	Tobin Feldman-Fitzthum <tobin@ibm.com>,
	Dov Murik <dovmurik@linux.vnet.ibm.com>,
	"Dr. David Alan Gilbert" <dgilbert@redhat.com>,
	Hubertus Franke <frankeh@us.ibm.com>,
	Tobin Feldman-Fitzthum <tobin@linux.ibm.com>
Subject: [RFC PATCH 12/26] migration: Add helpers to save confidential RAM
Date: Tue,  2 Mar 2021 15:48:08 -0500	[thread overview]
Message-ID: <20210302204822.81901-13-dovmurik@linux.vnet.ibm.com> (raw)
In-Reply-To: <20210302204822.81901-1-dovmurik@linux.vnet.ibm.com>

QEMU cannot read the memory of memory-encrypted guests, which is
required for sending RAM to the migration target.  Instead, QEMU asks a
migration helper running on an auxiliary vcpu in the guest to extract
pages from memory; these pages are encrypted with a transfer key that is
known to the source and target guests, but not to both QEMUs.

The interaction with the guest migration helper is performed using two
shared (unencrypted) pages which both QEMU and guest can read from and
write to.  The details of the mailbox protocol are described in
migration/confidential-ram.c.

Signed-off-by: Dov Murik <dovmurik@linux.vnet.ibm.com>
---
 migration/confidential-ram.h |  17 ++++
 migration/confidential-ram.c | 184 +++++++++++++++++++++++++++++++++++
 migration/meson.build        |   6 +-
 migration/trace-events       |   3 +
 4 files changed, 209 insertions(+), 1 deletion(-)
 create mode 100644 migration/confidential-ram.h
 create mode 100644 migration/confidential-ram.c

diff --git a/migration/confidential-ram.h b/migration/confidential-ram.h
new file mode 100644
index 0000000000..0d49718d31
--- /dev/null
+++ b/migration/confidential-ram.h
@@ -0,0 +1,17 @@
+/*
+ * QEMU migration for confidential guest's RAM
+ */
+
+#ifndef QEMU_CONFIDENTIAL_RAM_H
+#define QEMU_CONFIDENTIAL_RAM_H
+
+#include "exec/cpu-common.h"
+#include "qemu-file.h"
+
+void cgs_mh_init(void);
+void cgs_mh_cleanup(void);
+
+int cgs_mh_save_encrypted_page(QEMUFile *f, ram_addr_t src_gpa, uint32_t size,
+                               uint64_t *bytes_sent);
+
+#endif
diff --git a/migration/confidential-ram.c b/migration/confidential-ram.c
new file mode 100644
index 0000000000..65a588e7f6
--- /dev/null
+++ b/migration/confidential-ram.c
@@ -0,0 +1,184 @@
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/rcu.h"
+#include "qemu/coroutine.h"
+#include "qemu/timer.h"
+#include "io/channel.h"
+#include "qapi/error.h"
+#include "exec/memory.h"
+#include "trace.h"
+#include "confidential-ram.h"
+
+enum cgs_mig_helper_cmd {
+    /* Initialize migration helper in guest */
+    CGS_MIG_HELPER_CMD_INIT = 0,
+
+    /*
+     * Fetch a page from gpa, encrypt it, and save result into the shared page
+     */
+    CGS_MIG_HELPER_CMD_ENCRYPT,
+
+    /* Read the shared page, decrypt it, and save result into gpa */
+    CGS_MIG_HELPER_CMD_DECRYPT,
+
+    /* Reset migration helper in guest */
+    CGS_MIG_HELPER_CMD_RESET,
+
+    CGS_MIG_HELPER_CMD_MAX
+};
+
+struct QEMU_PACKED CGSMigHelperCmdParams {
+    uint64_t cmd_type;
+    uint64_t gpa;
+    int32_t prefetch;
+    int32_t ret;
+    int32_t go;
+    int32_t done;
+};
+typedef struct CGSMigHelperCmdParams CGSMigHelperCmdParams;
+
+struct QEMU_PACKED CGSMigHelperPageHeader {
+    uint32_t len;
+    uint8_t data[0];
+};
+typedef struct CGSMigHelperPageHeader CGSMigHelperPageHeader;
+
+struct CGSMigHelperState {
+    CGSMigHelperCmdParams *cmd_params;
+    CGSMigHelperPageHeader *io_page_hdr;
+    uint8_t *io_page;
+    bool initialized;
+};
+typedef struct CGSMigHelperState CGSMigHelperState;
+
+static CGSMigHelperState cmhs = {0};
+
+#define MH_BUSYLOOP_TIMEOUT       100000000LL
+#define MH_REQUEST_TIMEOUT_MS     100
+#define MH_REQUEST_TIMEOUT_NS     (MH_REQUEST_TIMEOUT_MS * 1000 * 1000)
+
+/*
+ * The migration helper shared area is hard-coded at gpa 0x820000 with size of
+ * 2 pages (0x2000 bytes).  Instead of hard-coding, the address and size may be
+ * fetched from OVMF itself using a pc_system_ovmf_table_find call to query
+ * OVMF's GUIDed structure for a migration helper GUID.
+ */
+#define MH_SHARED_CMD_PARAMS_ADDR    0x820000
+#define MH_SHARED_IO_PAGE_HDR_ADDR   (MH_SHARED_CMD_PARAMS_ADDR + 0x800)
+#define MH_SHARED_IO_PAGE_ADDR       (MH_SHARED_CMD_PARAMS_ADDR + 0x1000)
+
+void cgs_mh_init(void)
+{
+    RCU_READ_LOCK_GUARD();
+    cmhs.cmd_params = qemu_map_ram_ptr(NULL, MH_SHARED_CMD_PARAMS_ADDR);
+    cmhs.io_page_hdr = qemu_map_ram_ptr(NULL, MH_SHARED_IO_PAGE_HDR_ADDR);
+    cmhs.io_page = qemu_map_ram_ptr(NULL, MH_SHARED_IO_PAGE_ADDR);
+}
+
+static int send_command_to_cgs_mig_helper(uint64_t cmd_type, uint64_t gpa)
+{
+    /*
+     * The cmd_params struct is on a page shared with the guest migration
+     * helper.  We use a volatile struct to force writes to memory so that the
+     * guest can see them.
+     */
+    volatile CGSMigHelperCmdParams *params = cmhs.cmd_params;
+    int64_t counter, request_timeout_at;
+
+    /*
+     * At this point io_page and io_page_hdr should be already filled according
+     * to the requested cmd_type.
+     */
+
+    params->cmd_type = cmd_type;
+    params->gpa = gpa;
+    params->prefetch = 0;
+    params->ret = -1;
+    params->done = 0;
+
+    /*
+     * Force writes of all command parameters before writing the 'go' flag.
+     * The guest migration handler waits for the go flag and then reads the
+     * command parameters.
+     */
+    smp_wmb();
+
+    /* Tell the migration helper to start working on this command */
+    params->go = 1;
+
+    /*
+     * Wait for the guest migration helper to process the command and mark the
+     * done flag
+     */
+    request_timeout_at = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) +
+                         MH_REQUEST_TIMEOUT_NS;
+    do {
+        counter = 0;
+        while (!params->done && (counter < MH_BUSYLOOP_TIMEOUT)) {
+            counter++;
+        }
+    } while (!params->done &&
+             qemu_clock_get_ns(QEMU_CLOCK_REALTIME) < request_timeout_at);
+
+    if (!params->done) {
+        error_report("Migration helper command %" PRIu64 " timed-out for "
+                     "gpa 0x%" PRIx64, cmd_type, gpa);
+        return -EIO;
+    }
+
+    return params->ret;
+}
+
+static void init_cgs_mig_helper_if_needed(void)
+{
+    int ret;
+
+    if (cmhs.initialized) {
+        return;
+    }
+
+    ret = send_command_to_cgs_mig_helper(CGS_MIG_HELPER_CMD_INIT, 0);
+    if (ret == 0) {
+        cmhs.initialized = true;
+    }
+}
+
+void cgs_mh_cleanup(void)
+{
+    send_command_to_cgs_mig_helper(CGS_MIG_HELPER_CMD_RESET, 0);
+}
+
+int cgs_mh_save_encrypted_page(QEMUFile *f, ram_addr_t src_gpa, uint32_t size,
+                               uint64_t *bytes_sent)
+{
+    int ret;
+
+    init_cgs_mig_helper_if_needed();
+
+    /* Ask the migration helper to encrypt the page at src_gpa */
+    trace_encrypted_ram_save_page(size, src_gpa);
+    ret = send_command_to_cgs_mig_helper(CGS_MIG_HELPER_CMD_ENCRYPT, src_gpa);
+    if (ret) {
+        error_report("Error cgs_mh_save_encrypted_page ret=%d", ret);
+        return -1;
+    }
+
+    /* Sanity check for response header */
+    if (cmhs.io_page_hdr->len > 1024) {
+        error_report("confidential-ram: migration helper response is too large "
+                     "(len=%u)", cmhs.io_page_hdr->len);
+        return -EINVAL;
+    }
+
+    qemu_put_be32(f, cmhs.io_page_hdr->len);
+    qemu_put_buffer(f, cmhs.io_page_hdr->data, cmhs.io_page_hdr->len);
+    *bytes_sent = 4 + cmhs.io_page_hdr->len;
+
+    qemu_put_be32(f, size);
+    qemu_put_buffer(f, cmhs.io_page, size);
+    *bytes_sent += 4 + size;
+
+    return ret;
+}
diff --git a/migration/meson.build b/migration/meson.build
index 9645f44005..95fe7bdd12 100644
--- a/migration/meson.build
+++ b/migration/meson.build
@@ -30,4 +30,8 @@ softmmu_ss.add(when: ['CONFIG_RDMA', rdma], if_true: files('rdma.c'))
 softmmu_ss.add(when: 'CONFIG_LIVE_BLOCK_MIGRATION', if_true: files('block.c'))
 softmmu_ss.add(when: zstd, if_true: files('multifd-zstd.c'))
 
-specific_ss.add(when: 'CONFIG_SOFTMMU', if_true: files('dirtyrate.c', 'ram.c'))
+specific_ss.add(when: 'CONFIG_SOFTMMU', if_true: files(
+  'dirtyrate.c',
+  'ram.c',
+  'confidential-ram.c',
+))
diff --git a/migration/trace-events b/migration/trace-events
index 668c562fed..929de4ca98 100644
--- a/migration/trace-events
+++ b/migration/trace-events
@@ -343,3 +343,6 @@ migration_block_save_pending(uint64_t pending) "Enter save live pending  %" PRIu
 # page_cache.c
 migration_pagecache_init(int64_t max_num_items) "Setting cache buckets to %" PRId64
 migration_pagecache_insert(void) "Error allocating page"
+
+# confidential-ram.c
+encrypted_ram_save_page(uint32_t size, uint64_t gpa) "size: %u, gpa: 0x%" PRIx64
-- 
2.20.1



  parent reply	other threads:[~2021-03-02 20:54 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-02 20:47 [RFC PATCH 00/26] Confidential guest live migration Dov Murik
2021-03-02 20:47 ` [RFC PATCH 01/26] linux-headers: Add definitions of KVM page encryption bitmap ioctls Dov Murik
2021-03-02 20:47 ` [RFC PATCH 02/26] kvm: add support to sync the page encryption state bitmap Dov Murik
2021-03-02 20:47 ` [RFC PATCH 03/26] machine: Add auxcpus=N suboption to -smp Dov Murik
2021-03-02 20:48 ` [RFC PATCH 04/26] hw/boards: Add aux flag to CPUArchId Dov Murik
2021-03-02 20:48 ` [RFC PATCH 05/26] hw/i386: Mark auxiliary vcpus in possible_cpus Dov Murik
2021-03-02 20:48 ` [RFC PATCH 06/26] hw/acpi: Don't include auxiliary vcpus in ACPI tables Dov Murik
2021-03-02 20:48 ` [RFC PATCH 07/26] cpu: Add boolean aux field to CPUState Dov Murik
2021-03-02 20:48 ` [RFC PATCH 08/26] hw/i386: Set CPUState.aux=true for auxiliary vcpus Dov Murik
2021-03-02 20:48 ` [RFC PATCH 09/26] softmmu: Don't sync aux vcpus in pre_loadvm Dov Murik
2021-03-02 20:48 ` [RFC PATCH 10/26] softmmu: Add cpu_synchronize_without_aux_post_init Dov Murik
2021-03-02 20:48 ` [RFC PATCH 11/26] softmmu: Add pause_all_vcpus_except_aux Dov Murik
2021-03-02 20:48 ` Dov Murik [this message]
2021-03-02 20:48 ` [RFC PATCH 13/26] migration: Add helpers to load confidential RAM Dov Murik
2021-03-02 20:48 ` [RFC PATCH 14/26] migration: Introduce gpa_inside_migration_helper_shared_area Dov Murik
2021-03-02 20:48 ` [RFC PATCH 15/26] migration: Save confidential guest RAM using migration helper Dov Murik
2021-03-02 20:48 ` [RFC PATCH 16/26] migration: Load " Dov Murik
2021-03-02 20:48 ` [RFC PATCH 17/26] migration: Stop VM after loading confidential RAM Dov Murik
2021-03-02 20:48 ` [RFC PATCH 18/26] migration: Stop non-aux vcpus before copying the last pages Dov Murik
2021-03-02 20:48 ` [RFC PATCH 19/26] migration: Don't sync vcpus when migrating confidential guests Dov Murik
2021-03-02 20:48 ` [RFC PATCH 20/26] migration: When starting target, don't sync auxiliary vcpus Dov Murik
2021-03-02 20:48 ` [RFC PATCH 21/26] migration: Call migration handler cleanup routines Dov Murik
2021-03-02 20:48 ` [RFC PATCH 22/26] hw/isa/lpc_ich9: Allow updating an already-running VM Dov Murik
2021-03-02 20:48 ` [RFC PATCH 23/26] target/i386: Re-sync kvm-clock after confidential guest migration Dov Murik
2021-03-02 20:48 ` [RFC PATCH 24/26] migration: Add start-migrate-incoming QMP command Dov Murik
2021-03-02 20:48 ` [RFC PATCH 25/26] target/i386: SEV: Allow migration unless there are no aux vcpus Dov Murik
2021-03-02 20:48 ` [RFC PATCH 26/26] docs: Add confidential guest live migration documentation Dov Murik
2021-03-02 21:24 ` [RFC PATCH 00/26] Confidential guest live migration no-reply
2021-03-03  8:08   ` Dov Murik
2021-03-04  9:10 ` Paolo Bonzini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210302204822.81901-13-dovmurik@linux.vnet.ibm.com \
    --to=dovmurik@linux.vnet.ibm.com \
    --cc=ashish.kalra@amd.com \
    --cc=brijesh.singh@amd.com \
    --cc=dgilbert@redhat.com \
    --cc=frankeh@us.ibm.com \
    --cc=jejb@linux.ibm.com \
    --cc=jon.grimm@amd.com \
    --cc=qemu-devel@nongnu.org \
    --cc=quintela@redhat.com \
    --cc=thomas.lendacky@amd.com \
    --cc=tobin@ibm.com \
    --cc=tobin@linux.ibm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).