[RFC PATCH 2/6] i386/sev: extend sev-guest property to include SEV-SNP

Brijesh Singh
2021-07-09T21:56:15Z

To launch the SEV-SNP guest, a user can specify up to 8 parameters.
Passing all parameters through command line can be difficult. To simplify
the launch parameter passing, introduce a .ini-like config file that can be
used for passing the parameters to the launch flow.

The contents of the config file will look like this:

$ cat snp-launch.init

# SNP launch parameters
[SEV-SNP]
init_flags = 0
policy = 0x1000
id_block = "YWFhYWFhYWFhYWFhYWFhCg=="

Add 'snp' property that can be used to indicate that SEV guest launch
should enable the SNP support.

SEV-SNP guest launch examples:

1) launch without additional parameters

$(QEMU_CLI) \
-object sev-guest,id=sev0,snp=on

2) launch with optional parameters
$(QEMU_CLI) \
-object sev-guest,id=sev0,snp=on,launch-config=<file>

Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
docs/amd-memory-encryption.txt | 81 +++++++++++-
qapi/qom.json | 6 +
target/i386/sev.c | 227 +++++++++++++++++++++++++++++++++
3 files changed, 312 insertions(+), 2 deletions(-)

diff --git a/docs/amd-memory-encryption.txt b/docs/amd-memory-encryption.txt
index ffca382b5f..322bf38f68 100644
--- a/docs/amd-memory-encryption.txt
+++ b/docs/amd-memory-encryption.txt
@@ -22,8 +22,8 @@ support for notifying a guest's operating system when certain types of VMEXITs
are about to occur. This allows the guest to selectively share information with
the hypervisor to satisfy the requested function.

-Launching
----------
+Launching (SEV and SEV-ES)
+--------------------------
Boot images (such as bios) must be encrypted before a guest can be booted. The
MEMORY_ENCRYPT_OP ioctl provides commands to encrypt the images: LAUNCH_START,
LAUNCH_UPDATE_DATA, LAUNCH_MEASURE and LAUNCH_FINISH. These four commands
@@ -113,6 +113,83 @@ a SEV-ES guest:
- Requires in-kernel irqchip - the burden is placed on the hypervisor to
manage booting APs.

+Launching (SEV-SNP)
+-------------------
+Boot images (such as bios) must be encrypted before a guest can be booted. The
+MEMORY_ENCRYPT_OP ioctl provides commands to encrypt the images:
+KVM_SNP_INIT, SNP_LAUNCH_START, SNP_LAUNCH_UPDATE, and SNP_LAUNCH_FINISH. These
+four commands together generate a fresh memory encryption key for the VM,
+encrypt the boot images for a successful launch.
+
+KVM_SNP_INIT is called first to initialize the SEV-SNP firmware and SNP
+features in the KVM. The feature flags value can be provided through the
+launch-config file.
+
++------------+-------+----------+---------------------------------+
+| key | type | default | meaning |
++------------+-------+----------+---------------------------------+
+| init_flags | hex | 0 | SNP feature flags |
++-----------------------------------------------------------------+
+
+Note: currently the init_flags must be zero.
+
+SNP_LAUNCH_START is called first to create a cryptographic launch context
+within the firmware. To create this context, guest owner must provide a guest
+policy and other parameters as described in the SEV-SNP firmware
+specification. The launch parameters should be specified in the launch-config
+ini file and should be treated as a binary blob and must be passed as-is to
+the SEV-SNP firmware.
+
+The SNP_LAUNCH_START uses the following parameters from the launch-config
+file. See the SEV-SNP specification for more details.
+
++--------+-------+----------+----------------------------------------------+
+| key | type | default | meaning |
++--------+-------+----------+----------------------------------------------+
+| policy | hex | 0x30000 | a 64-bit guest policy |
+| imi_en | bool | 0 | 1 when IMI is enabled |
+| ma_end | bool | 0 | 1 when migration agent is used |
+| gosvw | string| 0 | 16-byte base64 encoded string for the guest |
+| | | | OS visible workaround. |
++--------+-------+----------+----------------------------------------------+
+
+SNP_LAUNCH_UPDATE encrypts the memory region using the cryptographic context
+created via the SNP_LAUNCH_START command. If required, this command can be called
+multiple times to encrypt different memory regions. The command also calculates
+the measurement of the memory contents as it encrypts.
+
+SNP_LAUNCH_FINISH finalizes the guest launch flow. Optionally, while finalizing
+the launch the firmware can perform checks on the launch digest computing
+through the SNP_LAUNCH_UPDATE. To perform the check the user must supply
+the id block, authentication blob and host data that should be included in the
+attestation report. See the SEV-SNP spec for further details.
+
+The SNP_LAUNCH_FINISH uses the following parameters from the launch-config file.
+
++------------+-------+----------+----------------------------------------------+
+| key | type | default | meaning |
++------------+-------+----------+----------------------------------------------+
+| id_block | string| none | base64 encoded ID block |
++------------+-------+----------+----------------------------------------------+
+| id_auth | string| none | base64 encoded authentication information |
++------------+-------+----------+----------------------------------------------+
+| auth_key_en| bool | 0 | auth block contains author key |
++------------+-------+----------+----------------------------------------------+
+| host_data | string| none | host provided data |
++------------+-------+----------+----------------------------------------------+
+
+To launch a SEV-SNP guest
+
+# ${QEMU} \
+ -machine ...,confidential-guest-support=sev0 \
+ -object sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,snp=on
+
+To launch a SEV-SNP guest with launch configuration
+
+# ${QEMU} \
+ -machine ...,confidential-guest-support=sev0 \
+ -object sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,snp=on,launch-config=<config>
+
Debugging
-----------
Since the memory contents of a SEV guest are encrypted, hypervisor access to
diff --git a/qapi/qom.json b/qapi/qom.json
index 652be317b8..bdf89fda27 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -749,6 +749,10 @@
# @reduced-phys-bits: number of bits in physical addresses that become
# unavailable when SEV is enabled
#
+# @snp: SEV-SNP is enabled (default: 0)
+#
+# @launch-config: launch config file to use
+#
# Since: 2.12
##
{ 'struct': 'SevGuestProperties',
@@ -758,6 +762,8 @@
'*policy': 'uint32',
'*handle': 'uint32',
'*cbitpos': 'uint32',
+ '*snp': 'bool',
+ '*launch-config': 'str',
'reduced-phys-bits': 'uint32' } }

##
diff --git a/target/i386/sev.c b/target/i386/sev.c
index 83df8c09f6..6b238ef969 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -37,6 +37,11 @@
#define TYPE_SEV_GUEST "sev-guest"
OBJECT_DECLARE_SIMPLE_TYPE(SevGuestState, SEV_GUEST)

+struct snp_launch_config {
+ struct kvm_snp_init init;
+ struct kvm_sev_snp_launch_start start;
+ struct kvm_sev_snp_launch_finish finish;
+};

/**
* SevGuestState:
@@ -58,6 +63,8 @@ struct SevGuestState {
char *session_file;
uint32_t cbitpos;
uint32_t reduced_phys_bits;
+ char *launch_config_file;
+ bool snp;

/* runtime state */
uint32_t handle;
@@ -72,10 +79,13 @@ struct SevGuestState {
uint32_t reset_cs;
uint32_t reset_ip;
bool reset_data_valid;
+
+ struct snp_launch_config snp_config;
};

#define DEFAULT_GUEST_POLICY 0x1 /* disable debug */
#define DEFAULT_SEV_DEVICE "/dev/sev"
+#define DEFAULT_SEV_SNP_POLICY 0x30000

#define SEV_INFO_BLOCK_GUID "00f771de-1a7e-4fcb-890e-68c77e2fb44e"
typedef struct __attribute__((__packed__)) SevInfoBlock {
@@ -298,6 +308,212 @@ sev_guest_set_sev_device(Object *obj, const char *value, Error **errp)
sev->sev_device = g_strdup(value);
}

+static void
+sev_guest_set_snp(Object *obj, bool value, Error **errp)
+{
+ SevGuestState *sev = SEV_GUEST(obj);
+
+ sev->snp = value;
+}
+
+static bool
+sev_guest_get_snp(Object *obj, Error **errp)
+{
+ SevGuestState *sev = SEV_GUEST(obj);
+
+ return sev->snp;
+}
+
+
+static char *
+sev_guest_get_launch_config_file(Object *obj, Error **errp)
+{
+ SevGuestState *s = SEV_GUEST(obj);
+
+ return g_strdup(s->launch_config_file);
+}
+
+static int
+config_read_uint64(GKeyFile *f, const char *key, uint64_t *value, Error **errp)
+{
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *str = NULL;
+ uint64_t res;
+
+ str = g_key_file_get_string(f, "SEV-SNP", key, &error);
+ if (!str) {
+ /* key not found */
+ return 0;
+ }
+
+ res = g_ascii_strtoull(str, NULL, 16);
+ if (res == G_MAXUINT64) {
+ error_setg(errp, "Failed to convert %s", str);
+ return 1;
+ }
+
+ *value = res;
+ return 0;
+}
+
+static int
+config_read_bool(GKeyFile *f, const char *key, bool *value, Error **errp)
+{
+ g_autoptr(GError) error = NULL;
+ gboolean val;
+
+ val = g_key_file_get_boolean(f, "SEV-SNP", key, &error);
+ if (!val && g_error_matches(error, G_KEY_FILE_ERROR,
+ G_KEY_FILE_ERROR_INVALID_VALUE)) {
+ error_setg(errp, "%s", error->message);
+ return 1;
+ }
+
+ *value = val;
+ return 0;
+}
+
+static int
+config_read_blob(GKeyFile *f, const char *key, uint8_t *blob, uint32_t len,
+ Error **errp)
+{
+ g_autoptr(GError) error = NULL;
+ g_autofree guchar *data = NULL;
+ g_autofree gchar *base64 = NULL;
+ gsize size;
+
+ base64 = g_key_file_get_string(f, "SEV-SNP", key, &error);
+ if (!base64) {
+ /* key not found */
+ return 0;
+ }
+
+ /* lets decode the value string */
+ data = g_base64_decode(base64, &size);
+ if (!data) {
+ error_setg(errp, "failed to decode '%s'", key);
+ return 1;
+ }
+
+ /* verify the length */
+ if (len != size) {
+ error_setg(errp, "invalid length for key '%s' (expected %d got %ld)",
+ key, len, size);
+ return 1;
+ }
+
+ memcpy(blob, data, size);
+ return 0;
+}
+
+static int
+snp_parse_launch_config(SevGuestState *sev, const char *file, Error **errp)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GKeyFile) key_file = g_key_file_new();
+ struct kvm_sev_snp_launch_start *start = &sev->snp_config.start;
+ struct kvm_snp_init *init = &sev->snp_config.init;
+ struct kvm_sev_snp_launch_finish *finish = &sev->snp_config.finish;
+ uint8_t *id_block = NULL, *id_auth = NULL;
+
+ if (!g_key_file_load_from_file(key_file, file, G_KEY_FILE_NONE, &error)) {
+ error_setg(errp, "Error loading config file: %s", error->message);
+ return 1;
+ }
+
+ /* Check the group first */
+ if (!g_key_file_has_group(key_file, "SEV-SNP")) {
+ error_setg(errp, "Error parsing config file, group SEV-SNP not found");
+ return 1;
+ }
+
+ /* Get the init_flags used in KVM_SNP_INIT */
+ if (config_read_uint64(key_file, "init_flags",
+ (uint64_t *)&init->flags, errp)) {
+ goto err;
+ }
+
+ /* Get the policy used in LAUNCH_START */
+ if (config_read_uint64(key_file, "policy",
+ (uint64_t *)&start->policy, errp)) {
+ goto err;
+ }
+
+ /* Get IMI_EN used in LAUNCH_START */
+ if (config_read_bool(key_file, "imi_en", (bool *)&start->imi_en, errp)) {
+ goto err;
+ }
+
+ /* Get MA_EN used in LAUNCH_START */
+ if (config_read_bool(key_file, "imi_en", (bool *)&start->ma_en, errp)) {
+ goto err;
+ }
+
+ /* Get GOSVW used in LAUNCH_START */
+ if (config_read_blob(key_file, "gosvw", (uint8_t *)&start->gosvw,
+ sizeof(start->gosvw), errp)) {
+ goto err;
+ }
+
+ /* Get ID block used in LAUNCH_FINISH */
+ if (g_key_file_has_key(key_file, "SEV-SNP", "id_block", &error)) {
+
+ id_block = g_malloc(KVM_SEV_SNP_ID_BLOCK_SIZE);
+
+ if (config_read_blob(key_file, "id_block", id_block,
+ KVM_SEV_SNP_ID_BLOCK_SIZE, errp)) {
+ goto err;
+ }
+
+ finish->id_block_uaddr = (unsigned long)id_block;
+ finish->id_block_en = 1;
+ }
+
+ /* Get authentication block used in LAUNCH_FINISH */
+ if (g_key_file_has_key(key_file, "SEV-SNP", "id_auth", &error)) {
+
+ id_auth = g_malloc(KVM_SEV_SNP_ID_AUTH_SIZE);
+
+ if (config_read_blob(key_file, "auth_block", id_auth,
+ KVM_SEV_SNP_ID_AUTH_SIZE, errp)) {
+ goto err;
+ }
+
+ finish->id_auth_uaddr = (unsigned long)id_auth;
+
+ /* Get AUTH_KEY_EN used in LAUNCH_FINISH */
+ if (config_read_bool(key_file, "auth_key_en",
+ (bool *)&finish->auth_key_en, errp)) {
+ goto err;
+ }
+ }
+
+ /* Get host_data used in LAUNCH_FINISH */
+ if (config_read_blob(key_file, "host_data", (uint8_t *)&finish->host_data,
+ sizeof(finish->host_data), errp)) {
+ goto err;
+ }
+
+ return 0;
+
+err:
+ g_free(id_block);
+ g_free(id_auth);
+ return 1;
+}
+
+static void
+sev_guest_set_launch_config_file(Object *obj, const char *value, Error **errp)
+{
+ SevGuestState *s = SEV_GUEST(obj);
+
+ if (snp_parse_launch_config(s, value, errp)) {
+ return;
+ }
+
+ s->launch_config_file = g_strdup(value);
+}
+
static void
sev_guest_class_init(ObjectClass *oc, void *data)
{
@@ -316,6 +532,16 @@ sev_guest_class_init(ObjectClass *oc, void *data)
sev_guest_set_session_file);
object_class_property_set_description(oc, "session-file",
"guest owners session parameters (encoded with base64)");
+ object_class_property_add_bool(oc, "snp",
+ sev_guest_get_snp,
+ sev_guest_set_snp);
+ object_class_property_set_description(oc, "snp",
+ "enable SEV-SNP support");
+ object_class_property_add_str(oc, "launch-config",
+ sev_guest_get_launch_config_file,
+ sev_guest_set_launch_config_file);
+ object_class_property_set_description(oc, "launch-config",
+ "the file provides the SEV-SNP guest launch parameters");
}

static void
@@ -325,6 +551,7 @@ sev_guest_instance_init(Object *obj)

sev->sev_device = g_strdup(DEFAULT_SEV_DEVICE);
sev->policy = DEFAULT_GUEST_POLICY;
+ sev->snp_config.start.policy = DEFAULT_SEV_SNP_POLICY;
object_property_add_uint32_ptr(obj, "policy", &sev->policy,
OBJ_PROP_FLAG_READWRITE);
object_property_add_uint32_ptr(obj, "handle", &sev->handle,
--
2.17.1