diff mbox series

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

Message ID 20210709215550.32496-3-brijesh.singh@amd.com
State New
Headers show
Series Add AMD Secure Nested Paging (SEV-SNP) support | expand

Commit Message

Brijesh Singh July 9, 2021, 9:55 p.m. UTC
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(-)

Comments

Dov Murik July 12, 2021, 6:09 a.m. UTC | #1
On 10/07/2021 0:55, Brijesh Singh 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=="
> 
> 
> 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>
> 

Not directly SNP-related, but in an internal communication Connor told
me he wants to allow the SEV configuration (like dh-cert-file etc.) to
be set using QMP commands when the machine launches instead (or in
addition to) setting them via QEMU command-line parameters.

Whatever the configuration solution decided for the SEV parameters
should also apply to these new SNP settings (policy, id_block, etc.).

-Dov
Dr. David Alan Gilbert July 12, 2021, 2:34 p.m. UTC | #2
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@amd.com) 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@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
>
Daniel P. Berrangé July 12, 2021, 2:43 p.m. UTC | #3
On Fri, Jul 09, 2021 at 04:55:46PM -0500, Brijesh Singh wrote:
> To launch the SEV-SNP guest, a user can specify up to 8 parameters.
> Passing all parameters through command line can be difficult.

This sentence applies to pretty much everything in QEMU and the
SEV-SNP example is nowhere near an extreme example IMHO.

>                                                              To simplify
> the launch parameter passing, introduce a .ini-like config file that can be
> used for passing the parameters to the launch flow.

Inventing a new config file format for usage by just one specific
niche feature in QEMU is something I'd say we do not want.

Our long term goal in QEMU is to move to a world where 100% of
QEMU configuration is provided in JSON format, using the QAPI
schema to define the accepted input set.  

> 
> 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=="

These parameters are really tiny and trivial to provide on the command
line, so I'm not finding this config file compelling.

> 
> 
> 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(-)

Regards,
Daniel
Brijesh Singh July 12, 2021, 3:56 p.m. UTC | #4
On 7/12/21 9:43 AM, Daniel P. Berrangé wrote:
> On Fri, Jul 09, 2021 at 04:55:46PM -0500, Brijesh Singh wrote:
>> To launch the SEV-SNP guest, a user can specify up to 8 parameters.
>> Passing all parameters through command line can be difficult.
> 
> This sentence applies to pretty much everything in QEMU and the
> SEV-SNP example is nowhere near an extreme example IMHO.
> 
>>                                                               To simplify
>> the launch parameter passing, introduce a .ini-like config file that can be
>> used for passing the parameters to the launch flow.
> 
> Inventing a new config file format for usage by just one specific
> niche feature in QEMU is something I'd say we do not want.
> 
> Our long term goal in QEMU is to move to a world where 100% of
> QEMU configuration is provided in JSON format, using the QAPI
> schema to define the accepted input set.
> 

I am open to all suggestions. I was trying to avoid passing all these 
parameters through the command line because some of them can be huge (up 
to a page size)


>>
>> 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=="
> 
> These parameters are really tiny and trivial to provide on the command
> line, so I'm not finding this config file compelling.
> 

I have only included 3 small parameters. Other parameters can be up to a 
page size. The breakdown looks like this:

policy: 8 bytes
flags: 8 bytes
id_block: 96 bytes
id_auth: 4096 bytes
host_data: 32 bytes
gosvw: 16 bytes



>>
>>
>> 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(-)
> 
> Regards,
> Daniel
>
Brijesh Singh July 12, 2021, 3:59 p.m. UTC | #5
On 7/12/21 9:34 AM, Dr. David Alan Gilbert wrote:
>>
>> $ 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?
> 

I did not included all the 8 parameters in the commit messages, mainly 
because some of them are big. I just picked 3 smaller ones.

-Brijesh
Dr. David Alan Gilbert July 12, 2021, 4:16 p.m. UTC | #6
* Brijesh Singh (brijesh.singh@amd.com) wrote:
> 
> 
> On 7/12/21 9:34 AM, Dr. David Alan Gilbert wrote:
> > > 
> > > $ 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?
> > 
> 
> I did not included all the 8 parameters in the commit messages, mainly
> because some of them are big. I just picked 3 smaller ones.

It would be good to have a full example, even if one of them was
something like:

  stuff = ".....<upto 4096 bytes>...."

so we'd just be able to get more of an idea.

Dave

> -Brijesh
>
Daniel P. Berrangé July 12, 2021, 4:24 p.m. UTC | #7
On Mon, Jul 12, 2021 at 10:56:40AM -0500, Brijesh Singh wrote:
> 
> 
> On 7/12/21 9:43 AM, Daniel P. Berrangé wrote:
> > On Fri, Jul 09, 2021 at 04:55:46PM -0500, Brijesh Singh wrote:
> > > To launch the SEV-SNP guest, a user can specify up to 8 parameters.
> > > Passing all parameters through command line can be difficult.
> > 
> > This sentence applies to pretty much everything in QEMU and the
> > SEV-SNP example is nowhere near an extreme example IMHO.
> > 
> > >                                                               To simplify
> > > the launch parameter passing, introduce a .ini-like config file that can be
> > > used for passing the parameters to the launch flow.
> > 
> > Inventing a new config file format for usage by just one specific
> > niche feature in QEMU is something I'd say we do not want.
> > 
> > Our long term goal in QEMU is to move to a world where 100% of
> > QEMU configuration is provided in JSON format, using the QAPI
> > schema to define the accepted input set.
> > 
> 
> I am open to all suggestions. I was trying to avoid passing all these
> parameters through the command line because some of them can be huge (up to
> a page size)
> 
> 
> > > 
> > > 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=="
> > 
> > These parameters are really tiny and trivial to provide on the command
> > line, so I'm not finding this config file compelling.
> > 
> 
> I have only included 3 small parameters. Other parameters can be up to a
> page size. The breakdown looks like this:
> 
> policy: 8 bytes
> flags: 8 bytes
> id_block: 96 bytes
> id_auth: 4096 bytes
> host_data: 32 bytes
> gosvw: 16 bytes

Only the id_auth parameter is really considered large here.

When you say "up to a page size", that implies that the size is
actually variable.  Is that correct, and if so, what is a real
world common size going to be ? Is the common size much smaller
than this upper limit ? If so I'd just ignore the issue entirely.

If not, then, 4k on the command line is certainly ugly, but isn't
technically impossible. It would be similarly ugly to have this
value stuffed into a libvirt XML configuration for that matter.

One option is to supply only that one parameter via an external
file, with the file being an opaque blob whose context is the
parameter value thus avoiding inventing a custom file format
parser.

When "id_auth" is described as "authentication data", are there
any secrecy requirements around this ?

QEMU does have the '-object secret' framework for passing blobs
of sensitive data to QEMU and can allow passing via a file:

  https://qemu-project.gitlab.io/qemu/system/secrets.html

Even if this doesn't actually need to be kept private, we
could decide to simply (ab)use the 'secret' object anyway
as a way to let it be passed in out of band via a file.

Libvirt also has a 'secret' concept for passing blobs of
sensitive data out of band from the main XML config, which
could again be leveraged.

It does feel a little dirty to be using 'secrets' in libvirt
and QEMU for data that doesn't actually need to be secret,
just as a way to avoid huge config files. So we could just
ignore the secrets and directly have 'id_auth_file' as a
parameter and directly reference a file.

Regards,
Daniel
Markus Armbruster July 13, 2021, 1:46 p.m. UTC | #8
Brijesh Singh <brijesh.singh@amd.com> writes:

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

I acknowledge doing complex configuration on the command line can be
awkward.  But if we added a separate configuration file for every
configurable thing where that's the case, we'd have too many already,
and we'd constantly grow more.  I don't think this is a viable solution.

In my opinion, much of what we do on the command line should be done in
configuration files instead.  Not in several different configuration
languages, mind, but using one common language for all our configuration
needs.

Some of us argue this language already exists: QMP.  It can't do
everything the command line can do, but that's a matter of putting in
the work.  However, JSON isn't a good configuration language[1].  To get
a decent one, we'd have to to extend JSON[2], or wrap another concrete
syntax around QMP's abstract syntax.

But this doesn't help you at all *now*.

I recommend to do exactly what we've done before for complex
configuration: define it in the QAPI schema, so we can use both dotted
keys and JSON on the command line, and can have QMP, too.  Examples:
-blockdev, -display, -compat.

Questions?


[1] https://www.lucidchart.com/techblog/2018/07/16/why-json-isnt-a-good-configuration-language/

[2] Thanks, but no thanks.  Let's make new and interesting mistakes
instead of repeating old and tired ones.
Brijesh Singh July 13, 2021, 1:54 p.m. UTC | #9
On 7/12/21 11:24 AM, Daniel P. Berrangé wrote:>>
>> policy: 8 bytes
>> flags: 8 bytes
>> id_block: 96 bytes
>> id_auth: 4096 bytes
>> host_data: 32 bytes
>> gosvw: 16 bytes
> 
> Only the id_auth parameter is really considered large here.
> 
> When you say "up to a page size", that implies that the size is
> actually variable.  Is that correct, and if so, what is a real
> world common size going to be ? Is the common size much smaller
> than this upper limit ? If so I'd just ignore the issue entirely.

Looking at the recent spec, it appears that id_auth is fixed to 4K.

> 
> If not, then, 4k on the command line is certainly ugly, but isn't
> technically impossible. It would be similarly ugly to have this
> value stuffed into a libvirt XML configuration for that matter.
> 
> One option is to supply only that one parameter via an external
> file, with the file being an opaque blob whose context is the
> parameter value thus avoiding inventing a custom file format
> parser.
> 
> When "id_auth" is described as "authentication data", are there
> any secrecy requirements around this ?
> 

Yes this sounds much better, we have been using the similar approach for 
the SEV in which we pass the PDH and session blob through the file.


> QEMU does have the '-object secret' framework for passing blobs
> of sensitive data to QEMU and can allow passing via a file:
> 
>    https://nam11.safelinks.protection.outlook.com/?url=https%3A%2F%2Fqemu-project.gitlab.io%2Fqemu%2Fsystem%2Fsecrets.html&amp;data=04%7C01%7Cbrijesh.singh%40amd.com%7C891fdc1ab0d8483aecb808d945519054%7C3dd8961fe4884e608e11a82d994e183d%7C0%7C0%7C637617038899405482%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=8AHUC3DeyauxT4Pd2ZkUJkDyu9XHtexybM0BgdRlego%3D&amp;reserved=0
> 
> Even if this doesn't actually need to be kept private, we
> could decide to simply (ab)use the 'secret' object anyway
> as a way to let it be passed in out of band via a file.
> 

The content of the field does not need to be protected. It's a public 
information, so I am not sure the secrets object fits here.

thanks
Eric Blake July 13, 2021, 6:21 p.m. UTC | #10
On Fri, Jul 09, 2021 at 04:55:46PM -0500, Brijesh Singh 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.

I agree with Markus' assessment that we are probably going to be
better off reusing what we already have for other complex options
rather than inventing yet another ini file.

Additional things I noted:

> +++ 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)

Here you list 0...

> +#
> +# @launch-config: launch config file to use

Both additions (if we keep the launch-config addition) are missing
'(since 6.1)' notations.

> +#
>  # Since: 2.12
>  ##
>  { 'struct': 'SevGuestProperties',
> @@ -758,6 +762,8 @@
>              '*policy': 'uint32',
>              '*handle': 'uint32',
>              '*cbitpos': 'uint32',
> +            '*snp': 'bool',

...but here you state snp is bool. That means the default is 'false', not '0'.

> +            '*launch-config': 'str',
>              'reduced-phys-bits': 'uint32' } }
>
Brijesh Singh July 14, 2021, 2:18 p.m. UTC | #11
On 7/13/21 8:46 AM, Markus Armbruster wrote:
> Brijesh Singh <brijesh.singh@amd.com> writes:
>
>> 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>
> I acknowledge doing complex configuration on the command line can be
> awkward.  But if we added a separate configuration file for every
> configurable thing where that's the case, we'd have too many already,
> and we'd constantly grow more.  I don't think this is a viable solution.
>
> In my opinion, much of what we do on the command line should be done in
> configuration files instead.  Not in several different configuration
> languages, mind, but using one common language for all our configuration
> needs.
>
> Some of us argue this language already exists: QMP.  It can't do
> everything the command line can do, but that's a matter of putting in
> the work.  However, JSON isn't a good configuration language[1].  To get
> a decent one, we'd have to to extend JSON[2], or wrap another concrete
> syntax around QMP's abstract syntax.
>
> But this doesn't help you at all *now*.
>
> I recommend to do exactly what we've done before for complex
> configuration: define it in the QAPI schema, so we can use both dotted
> keys and JSON on the command line, and can have QMP, too.  Examples:
> -blockdev, -display, -compat.
>
> Questions?


I will take a look at the blockdev and try modeling after that. if I run
into any questions then I will ask. thanks for the pointer Markus.

-Brijesh
Michael Roth July 20, 2021, 7:42 p.m. UTC | #12
On Tue, Jul 13, 2021 at 03:46:19PM +0200, Markus Armbruster wrote:
> Brijesh Singh <brijesh.singh@amd.com> writes:
> 
> > 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>
> 
> I acknowledge doing complex configuration on the command line can be
> awkward.  But if we added a separate configuration file for every
> configurable thing where that's the case, we'd have too many already,
> and we'd constantly grow more.  I don't think this is a viable solution.
> 
> In my opinion, much of what we do on the command line should be done in
> configuration files instead.  Not in several different configuration
> languages, mind, but using one common language for all our configuration
> needs.
> 
> Some of us argue this language already exists: QMP.  It can't do
> everything the command line can do, but that's a matter of putting in
> the work.  However, JSON isn't a good configuration language[1].  To get
> a decent one, we'd have to to extend JSON[2], or wrap another concrete
> syntax around QMP's abstract syntax.
> 
> But this doesn't help you at all *now*.
> 
> I recommend to do exactly what we've done before for complex
> configuration: define it in the QAPI schema, so we can use both dotted
> keys and JSON on the command line, and can have QMP, too.  Examples:
> -blockdev, -display, -compat.
> 
> Questions?

Hi Markus, Daniel,

I'm dealing with similar considerations with some SNP config options
relating to CPUID enforcement, so I've started looking into this as
well, but am still a little confused on the best way to proceed.

I see that -blockdev supports both JSON command-line arguments (via
qobject_input_visitor_new) and dotted keys
(via qobject_input_vistior_new_keyval).

We could introduce a new config group do the same, maybe something specific
to ConfidentialGuestSupport objects, e.g.:

  -confidential-guest-support sev-guest,id=sev0,key_a.subkey_b=...

and use the same mechanisms to parse the options, but this seems to
either involve adding a layer of option translations between command-line
and the underlying object properties, or, if we keep the 1:1 mapping
between QAPI-defined keys and object properties, it basically becomes a
way to pass a different Visitor implementation into object_property_set(),
in this case one created by object_input_visitor_new_keyval() instead of
opts_visitor_new().

In either case, genericizing it beyond CGS/SEV would basically be
introducing:

  -object2 sev-guest,id=sev0,key_a.subkey_b=...

Which one seems preferable? Or is the answer neither?

I've also been looking at whether this could all be handled via -object,
and it seems -object already supports JSON command-line arguments, and that
switching it from using OptsVisitor to QObjectVisitor for non-JSON case
would be enough to have it handle dotted keys, but I'm not sure what the
fall-out would be compatibility-wise.

All lot of that falls under making sure the QObject/keyval parser is
compatible with existing command-lines parsed via OptsVisitor. One example
where there still seems to be a difference is lack of support for ranges
such as "cpus=1-4" in keyval parser. Daniel had a series that addressed
this:

  https://lists.gnu.org/archive/html/qemu-devel/2016-09/msg08248.html

but it doesn't seem to have made it into the tree, which is why I feel like
maybe there are complications with this approach I haven't considered?

Thanks!

-Mike

> 
> 
> [1] https://www.lucidchart.com/techblog/2018/07/16/why-json-isnt-a-good-configuration-language/
> 
> [2] Thanks, but no thanks.  Let's make new and interesting mistakes
> instead of repeating old and tired ones.
>
Daniel P. Berrangé July 20, 2021, 9:54 p.m. UTC | #13
On Tue, Jul 20, 2021 at 02:42:12PM -0500, Michael Roth wrote:
> On Tue, Jul 13, 2021 at 03:46:19PM +0200, Markus Armbruster wrote:
> > Brijesh Singh <brijesh.singh@amd.com> writes:
> > 
> > > 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>
> > 
> > I acknowledge doing complex configuration on the command line can be
> > awkward.  But if we added a separate configuration file for every
> > configurable thing where that's the case, we'd have too many already,
> > and we'd constantly grow more.  I don't think this is a viable solution.
> > 
> > In my opinion, much of what we do on the command line should be done in
> > configuration files instead.  Not in several different configuration
> > languages, mind, but using one common language for all our configuration
> > needs.
> > 
> > Some of us argue this language already exists: QMP.  It can't do
> > everything the command line can do, but that's a matter of putting in
> > the work.  However, JSON isn't a good configuration language[1].  To get
> > a decent one, we'd have to to extend JSON[2], or wrap another concrete
> > syntax around QMP's abstract syntax.
> > 
> > But this doesn't help you at all *now*.
> > 
> > I recommend to do exactly what we've done before for complex
> > configuration: define it in the QAPI schema, so we can use both dotted
> > keys and JSON on the command line, and can have QMP, too.  Examples:
> > -blockdev, -display, -compat.
> > 
> > Questions?
> 
> Hi Markus, Daniel,
> 
> I'm dealing with similar considerations with some SNP config options
> relating to CPUID enforcement, so I've started looking into this as
> well, but am still a little confused on the best way to proceed.
> 
> I see that -blockdev supports both JSON command-line arguments (via
> qobject_input_visitor_new) and dotted keys
> (via qobject_input_vistior_new_keyval).
> 
> We could introduce a new config group do the same, maybe something specific
> to ConfidentialGuestSupport objects, e.g.:
> 
>   -confidential-guest-support sev-guest,id=sev0,key_a.subkey_b=...

We don't wnt to be adding new CLI options anymore. -object with json
syntx should ultimately be able to cover everything we'll ever need
to do.

> 
> and use the same mechanisms to parse the options, but this seems to
> either involve adding a layer of option translations between command-line
> and the underlying object properties, or, if we keep the 1:1 mapping
> between QAPI-defined keys and object properties, it basically becomes a
> way to pass a different Visitor implementation into object_property_set(),
> in this case one created by object_input_visitor_new_keyval() instead of
> opts_visitor_new().
> 
> In either case, genericizing it beyond CGS/SEV would basically be
> introducing:
> 
>   -object2 sev-guest,id=sev0,key_a.subkey_b=...
>
> Which one seems preferable? Or is the answer neither?

Yep, neither is the answer.

> 
> I've also been looking at whether this could all be handled via -object,
> and it seems -object already supports JSON command-line arguments, and that
> switching it from using OptsVisitor to QObjectVisitor for non-JSON case
> would be enough to have it handle dotted keys, but I'm not sure what the
> fall-out would be compatibility-wise.
> 
> All lot of that falls under making sure the QObject/keyval parser is
> compatible with existing command-lines parsed via OptsVisitor. One example
> where there still seems to be a difference is lack of support for ranges
> such as "cpus=1-4" in keyval parser. Daniel had a series that addressed
> this:
> 
>   https://lists.gnu.org/archive/html/qemu-devel/2016-09/msg08248.html
> 
> but it doesn't seem to have made it into the tree, which is why I feel like
> maybe there are complications with this approach I haven't considered?

The core problem with QemuOpts is that it has hacked various hacks
grafted onto it to cope with non-scalar data. My patch was adding
yet another hack. It very hard to introduce new hacks without risk
of causing incompatibility with other existing hacks, or introducing
unexpected edge cases that we don't anticipate.

Ultimately I think we've come to the conclusion that QemuOpts is an
unfixable dead end that should be left alone. Our future is trending
towards being entirely JSON, configured via the QMP monitor not the
CLI. As such the json support for -object is a step towards the pure
JSON world.

IOW, if you have things that work today with QemuOpts that's fine.

If, however, you're coming across situations that need non-scalar
data and it doesn't work with QemuOpts, then just declare that
-object json syntax is mandatory for that scenario. Don't invest
time trying to improve QemuOpts for non-scalar data, nor inventing
new CLI args.


FWIW, specificallly in the case of parameters that take an integer
range, like cores=1-4, in JSON we'd end up representing that as a
array of integers and having to specify all values explicitly.
This is quite verbose, but functionally works.

I think it would have been nice if we defined an explicit 'bitmap'
scalar data type in QAPI for these kind of things, but at this point
in time I think that ship has sailed, and trying to add that now
would create an inconsistent representation across different places.

Regards,
Daniel
Markus Armbruster July 21, 2021, 1:08 p.m. UTC | #14
Daniel P. Berrangé <berrange@redhat.com> writes:

> On Tue, Jul 20, 2021 at 02:42:12PM -0500, Michael Roth wrote:
>> On Tue, Jul 13, 2021 at 03:46:19PM +0200, Markus Armbruster wrote:

[...]

>> > I recommend to do exactly what we've done before for complex
>> > configuration: define it in the QAPI schema, so we can use both dotted
>> > keys and JSON on the command line, and can have QMP, too.  Examples:
>> > -blockdev, -display, -compat.
>> > 
>> > Questions?
>> 
>> Hi Markus, Daniel,
>> 
>> I'm dealing with similar considerations with some SNP config options
>> relating to CPUID enforcement, so I've started looking into this as
>> well, but am still a little confused on the best way to proceed.
>> 
>> I see that -blockdev supports both JSON command-line arguments (via
>> qobject_input_visitor_new) and dotted keys
>> (via qobject_input_vistior_new_keyval).

Yes.  Convenience function qobject_input_visitor_new_str() provides
this.

>> We could introduce a new config group do the same, maybe something specific
>> to ConfidentialGuestSupport objects, e.g.:
>> 
>>   -confidential-guest-support sev-guest,id=sev0,key_a.subkey_b=...
>
> We don't wnt to be adding new CLI options anymore. -object with json
> syntx should ultimately be able to cover everything we'll ever need
> to do.

Depends.  When you want a CLI option to create a single QOM object, then
-object is commonly the way to go.

>> and use the same mechanisms to parse the options, but this seems to
>> either involve adding a layer of option translations between command-line
>> and the underlying object properties, or, if we keep the 1:1 mapping
>> between QAPI-defined keys and object properties, it basically becomes a
>> way to pass a different Visitor implementation into object_property_set(),
>> in this case one created by object_input_visitor_new_keyval() instead of
>> opts_visitor_new().

qobject_input_visitor_new_str() provides 1:1, i.e. common abstract
syntax, and concrete syntax either JSON or dotted keys.  Note that the
latter is slightly less expressive (can't do empty arrays and objects,
may fall apart for type 'any').  If you run into these limitations, use
JSON.  Machines should always use JSON.

qobject_input_visitor_new_str() works by wrapping the "right" visitor
around the option argument.  Another way to provide a human-friendly
interface in addition to a machine-friendly one is to translate from
human to the machine interface.  HMP works that way: HMP commands wrap
around QMP commands.  The QMP commands are generated from the QAPI
schema.  The HMP commands are written by hand, which is maximally
flexible, but also laborious.

>> In either case, genericizing it beyond CGS/SEV would basically be
>> introducing:
>> 
>>   -object2 sev-guest,id=sev0,key_a.subkey_b=...

That's because existing -object doesn't use keyval_parse() + the keyval
QObjct input visitor, it uses QemuOpts and the options visitor, for
backward compatibility with all their (barely understood) features and
warts.

Unfortunate, because even new user-creatable objects can't escape
QemuOpts.

QemuOpts needs to go.  But replacing it is difficult and scary in
places.  -object is such a place.

>> Which one seems preferable? Or is the answer neither?
>
> Yep, neither is the answer.

Welcome to the QemuOpts swamp, here's your waders and a leaky bucket.

>> I've also been looking at whether this could all be handled via -object,
>> and it seems -object already supports JSON command-line arguments, and that
>> switching it from using OptsVisitor to QObjectVisitor for non-JSON case
>> would be enough to have it handle dotted keys, but I'm not sure what the
>> fall-out would be compatibility-wise.

It's complicated, and nobody knows for sure.  That's why we didn't dare
to take the jump, and instead grafted on JSON syntax without changing
the old syntax.  Excuse me while I sacrifice another rubber chicken to
the backward compatibility idol.

>> All lot of that falls under making sure the QObject/keyval parser is
>> compatible with existing command-lines parsed via OptsVisitor. One example
>> where there still seems to be a difference is lack of support for ranges
>> such as "cpus=1-4" in keyval parser. Daniel had a series that addressed
>> this:
>> 
>>   https://lists.gnu.org/archive/html/qemu-devel/2016-09/msg08248.html
>> 
>> but it doesn't seem to have made it into the tree, which is why I feel like
>> maybe there are complications with this approach I haven't considered?
>
> The core problem with QemuOpts is that it has hacked various hacks
> grafted onto it to cope with non-scalar data. My patch was adding
> yet another hack. It very hard to introduce new hacks without risk
> of causing incompatibility with other existing hacks, or introducing
> unexpected edge cases that we don't anticipate.

Some of the thornier compatibility issues with QemuOpts are due to
unforeseen edge cases of, and interactions between features.

> Ultimately I think we've come to the conclusion that QemuOpts is an
> unfixable dead end that should be left alone. Our future is trending
> towards being entirely JSON, configured via the QMP monitor not the
> CLI. As such the json support for -object is a step towards the pure
> JSON world.

QemuOpts served us well for a while, but we've long grown out of its
limitations.  It needs to go.

QemuOpts not providing an adequate CLI language does not imply we can't
have an adequate CLI language.  The "everything QMP" movement is due to
other factors.  I have serious reservations about the idea, actually.

> IOW, if you have things that work today with QemuOpts that's fine.
>
> If, however, you're coming across situations that need non-scalar
> data and it doesn't work with QemuOpts, then just declare that
> -object json syntax is mandatory for that scenario. Don't invest
> time trying to improve QemuOpts for non-scalar data, nor inventing
> new CLI args.

I agree 100% with "don't mess with QemuOpts".  That mess is complete.

Whether a new CLI option makes sense should be decided case by case.

"Must use JSON" feels acceptable for things basically only management
applications use.

> FWIW, specificallly in the case of parameters that take an integer
> range, like cores=1-4, in JSON we'd end up representing that as a
> array of integers and having to specify all values explicitly.
> This is quite verbose, but functionally works.
>
> I think it would have been nice if we defined an explicit 'bitmap'
> scalar data type in QAPI for these kind of things, but at this point
> in time I think that ship has sailed, and trying to add that now
> would create an inconsistent representation across different places.

External representation (i.e. JSON) should be as consistent as we can
make it.  Multiple different internal representations can be okay.  So
far, we represent JSON arrays as linked lists.  Optionally representing
certain arrays of small integers as bit vectors feels feasible.  Whether
it's worth the effort is another question.
diff mbox series

Patch

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,