cc'ing in armbru, since he knows about our command line - have we got a neater way of doing this, or something else that reads config file? Could the existing -readconfig work? Although this is a fairly large chunk of data, I don't think it's any larger than our block device configs on a bad day. Dave * Brijesh Singh (brijesh.singh@xxxxxxx) wrote: > 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==" Wouldn't the 'gosvw' and 'hostdata' also be in there? Dave > > 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 > -- Dr. David Alan Gilbert / dgilbert@xxxxxxxxxx / Manchester, UK