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@xxxxxxx> --- 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