* Marc-André Lureau (marcandre.lur...@gmail.com) wrote:
> Hi Stefan
> 
> On Thu, Mar 1, 2018 at 8:59 PM, Stefan Berger
> <stef...@linux.vnet.ibm.com> wrote:
> > Extend the TPM emulator backend device with state migration support.
> >
> > The external TPM emulator 'swtpm' provides a protocol over
> > its control channel to retrieve its state blobs. We implement
> > functions for getting and setting the different state blobs.
> > In case the setting of the state blobs fails, we return a
> > negative errno code to fail the start of the VM.
> >
> > Since we have an external TPM emulator, we need to make sure
> > that we do not migrate the state for as long as it is busy
> > processing a request. We need to wait for notification that
> > the request has completed processing.
> 
> Because qemu will have to deal with an external state, managed by the
> tpm emulator (different from other storage/nvram):
> 
> - the emulator statedir must be different between source and dest? Can
> it be the same?
> - what is the story regarding versionning? Can you migrate from/to any
> version, or only the same version?
> - can the host source and destination be of different endianess?
> - how is suspend and snapshot working?
> 
> There are probably other complications that I can't think of
> immediately, but David or Juan help may help avoid mistakes.

Yes, I think that just about covers it.

> It probably deserves a few explanations in docs/specs/tpm.txt.
> 
> >
> > Signed-off-by: Stefan Berger <stef...@linux.vnet.ibm.com>
> > ---
> >  hw/tpm/tpm_emulator.c | 312 
> > ++++++++++++++++++++++++++++++++++++++++++++++++--
> 
> It would be worth to add some basic tests. Either growing our own
> tests/tpm-emu.c test emulator
> 
> or checking that swtpm is present (and of required version?) to
> run/skip the test a more complete test.
> 
> >  1 file changed, 302 insertions(+), 10 deletions(-)
> >
> > diff --git a/hw/tpm/tpm_emulator.c b/hw/tpm/tpm_emulator.c
> > index b787aee..da877e5 100644
> > --- a/hw/tpm/tpm_emulator.c
> > +++ b/hw/tpm/tpm_emulator.c
> > @@ -55,6 +55,19 @@
> >  #define TPM_EMULATOR_IMPLEMENTS_ALL_CAPS(S, cap) (((S)->caps & (cap)) == 
> > (cap))
> >
> >  /* data structures */
> > +
> > +/* blobs from the TPM; part of VM state when migrating */
> > +typedef struct TPMBlobBuffers {
> > +    uint32_t permanent_flags;
> > +    TPMSizedBuffer permanent;
> > +
> > +    uint32_t volatil_flags;
> > +    TPMSizedBuffer volatil;
> > +
> > +    uint32_t savestate_flags;
> > +    TPMSizedBuffer savestate;
> > +} TPMBlobBuffers;
> > +
> >  typedef struct TPMEmulator {
> >      TPMBackend parent;
> >
> > @@ -70,6 +83,8 @@ typedef struct TPMEmulator {
> >
> >      unsigned int established_flag:1;
> >      unsigned int established_flag_cached:1;
> > +
> > +    TPMBlobBuffers state_blobs;
> 
> Suspicious addition, it shouldn't be necessary in running state. It
> could be allocated on demand? Even better if the field is removed, but
> this may not be possible with VMSTATE macros.
> 
> >  } TPMEmulator;
> >
> >
> > @@ -301,7 +316,8 @@ static int tpm_emulator_set_buffer_size(TPMBackend *tb,
> >      return 0;
> >  }
> >
> > -static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize)
> > +static int _tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize,
> > +                                     bool is_resume)
> 
> Using underscore-prefixed names is discouraged. Call it tpm_emulator_init?
> 
> >  {
> >      TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
> >      ptm_init init = {
> > @@ -309,12 +325,17 @@ static int tpm_emulator_startup_tpm(TPMBackend *tb, 
> > size_t buffersize)
> >      };
> >      ptm_res res;
> >
> > +    DPRINTF("%s   is_resume: %d", __func__, is_resume);
> > +
>
> extra spaces, you may also add buffersize while at it.

Also it would be good to use trace_'s where possible rather than
DPRINTFs.

> 
> >      if (buffersize != 0 &&
> >          tpm_emulator_set_buffer_size(tb, buffersize, NULL) < 0) {
> >          goto err_exit;
> >      }
> >
> > -    DPRINTF("%s", __func__);
> > +    if (is_resume) {
> > +        init.u.req.init_flags = cpu_to_be32(PTM_INIT_FLAG_DELETE_VOLATILE);
> > +    }
> 
> Since it's a flags, and for future extensibility, I would suggest |=.
> 
> > +
> >      if (tpm_emulator_ctrlcmd(tpm_emu, CMD_INIT, &init, sizeof(init),
> >                               sizeof(init)) < 0) {
> >          error_report("tpm-emulator: could not send INIT: %s",
> > @@ -333,6 +354,11 @@ err_exit:
> >      return -1;
> >  }
> >
> > +static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize)
> > +{
> > +    return _tpm_emulator_startup_tpm(tb, buffersize, false);
> > +}
> > +
> >  static bool tpm_emulator_get_tpm_established_flag(TPMBackend *tb)
> >  {
> >      TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
> > @@ -431,16 +457,21 @@ static size_t tpm_emulator_get_buffer_size(TPMBackend 
> > *tb)
> >  static int tpm_emulator_block_migration(TPMEmulator *tpm_emu)
> >  {
> >      Error *err = NULL;
> > +    ptm_cap caps = PTM_CAP_GET_STATEBLOB | PTM_CAP_SET_STATEBLOB |
> > +                   PTM_CAP_STOP;
> >
> > -    error_setg(&tpm_emu->migration_blocker,
> > -               "Migration disabled: TPM emulator not yet migratable");
> > -    migrate_add_blocker(tpm_emu->migration_blocker, &err);
> > -    if (err) {
> > -        error_report_err(err);
> > -        error_free(tpm_emu->migration_blocker);
> > -        tpm_emu->migration_blocker = NULL;
> > +    if (!TPM_EMULATOR_IMPLEMENTS_ALL_CAPS(tpm_emu, caps)) {
> > +        error_setg(&tpm_emu->migration_blocker,
> > +                   "Migration disabled: TPM emulator does not support "
> > +                   "migration");
> > +        migrate_add_blocker(tpm_emu->migration_blocker, &err);
> > +        if (err) {
> > +            error_report_err(err);
> > +            error_free(tpm_emu->migration_blocker);
> > +            tpm_emu->migration_blocker = NULL;
> >
> > -        return -1;
> > +            return -1;
> > +        }
> >      }
> >
> >      return 0;
> > @@ -569,6 +600,263 @@ static const QemuOptDesc tpm_emulator_cmdline_opts[] 
> > = {
> >      { /* end of list */ },
> >  };
> >
> > +/*
> > + * Transfer a TPM state blob from the TPM into a provided buffer.
> > + *
> > + * @tpm_emu: TPMEmulator
> > + * @type: the type of blob to transfer
> > + * @tsb: the TPMSizeBuffer to fill with the blob
> > + * @flags: the flags to return to the caller
> > + */
> > +static int tpm_emulator_get_state_blob(TPMEmulator *tpm_emu,
> > +                                       uint8_t type,
> > +                                       TPMSizedBuffer *tsb,
> > +                                       uint32_t *flags)
> > +{
> > +    ptm_getstate pgs;
> > +    ptm_res res;
> > +    ssize_t n;
> > +    uint32_t totlength, length;
> > +
> > +    tpm_sized_buffer_reset(tsb);
> > +
> > +    pgs.u.req.state_flags = cpu_to_be32(PTM_STATE_FLAG_DECRYPTED);
> > +    pgs.u.req.type = cpu_to_be32(type);
> > +    pgs.u.req.offset = 0;
> > +
> > +    if (tpm_emulator_ctrlcmd(tpm_emu, CMD_GET_STATEBLOB,
> > +                             &pgs, sizeof(pgs.u.req),
> > +                             offsetof(ptm_getstate, u.resp.data)) < 0) {
> > +        error_report("tpm-emulator: could not get state blob type %d : %s",
> > +                     type, strerror(errno));
> > +        return -1;
> 
> (I guess some day vmstate functions should have an Error ** argument)
> 
> > +    }
> > +
> > +    res = be32_to_cpu(pgs.u.resp.tpm_result);
> > +    if (res != 0 && (res & 0x800) == 0) {
> > +        error_report("tpm-emulator: Getting the stateblob (type %d) failed 
> > "
> > +                     "with a TPM error 0x%x", type, res);
> > +        return -1;
> > +    }
> > +
> > +    totlength = be32_to_cpu(pgs.u.resp.totlength);
> > +    length = be32_to_cpu(pgs.u.resp.length);
> > +    if (totlength != length) {
> > +        error_report("tpm-emulator: Expecting to read %u bytes "
> > +                     "but would get %u", totlength, length);
> > +        return -1;
> > +    }
> > +
> > +    *flags = be32_to_cpu(pgs.u.resp.state_flags);
> > +
> > +    tsb->buffer = g_malloc(totlength);
> 
> That TPMSizedBuffer is not really useful. Use GArray or qemu Buffer ?
> (and we could drop TPMSizedBuffer).

Also, given this is an arbitrary sized chunk, this should probably use
g_try_malloc and check the result rather than letting g_malloc assert on
failure (especially true on the source side).

> > +    n = qemu_chr_fe_read_all(&tpm_emu->ctrl_chr, tsb->buffer, totlength);
> > +    if (n != totlength) {
> > +        error_report("tpm-emulator: Could not read stateblob (type %d) : 
> > %s",
> > +                     type, strerror(errno));
> > +        return -1;
> > +    }
> > +    tsb->size = totlength;
> > +
> > +    DPRINTF("got state blob type %d, %d bytes, flags 0x%08x\n",
> > +            type, tsb->size, *flags);
> > +
> > +    return 0;
> > +}
> > +
> > +static int tpm_emulator_get_state_blobs(TPMEmulator *tpm_emu)
> > +{
> > +    TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs;
> > +
> > +    if (tpm_emulator_get_state_blob(tpm_emu, PTM_BLOB_TYPE_PERMANENT,
> > +                                    &state_blobs->permanent,
> > +                                    &state_blobs->permanent_flags) < 0 ||
> > +        tpm_emulator_get_state_blob(tpm_emu, PTM_BLOB_TYPE_VOLATILE,
> > +                                    &state_blobs->volatil,
> > +                                    &state_blobs->volatil_flags) < 0 ||
> > +        tpm_emulator_get_state_blob(tpm_emu, PTM_BLOB_TYPE_SAVESTATE,
> > +                                    &state_blobs->savestate,
> > +                                    &state_blobs->savestate_flags) < 0) {
> > +        goto err_exit;
> > +    }
> > +
> > +    return 0;
> > +
> > + err_exit:
> > +    tpm_sized_buffer_reset(&state_blobs->volatil);
> > +    tpm_sized_buffer_reset(&state_blobs->permanent);
> > +    tpm_sized_buffer_reset(&state_blobs->savestate);
> > +
> > +    return -1;
> > +}
> > +
> > +/*
> > + * Transfer a TPM state blob to the TPM emulator.
> > + *
> > + * @tpm_emu: TPMEmulator
> > + * @type: the type of TPM state blob to transfer
> > + * @tsb: TPMSizeBuffer containing the TPM state blob
> > + * @flags: Flags describing the (encryption) state of the TPM state blob
> > + */
> > +static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
> > +                                       uint32_t type,
> > +                                       TPMSizedBuffer *tsb,
> > +                                       uint32_t flags)
> > +{
> > +    uint32_t offset = 0;
> > +    ssize_t n;
> > +    ptm_setstate pss;
> > +    ptm_res tpm_result;
> > +
> > +    if (tsb->size == 0) {
> > +        return 0;
> > +    }
> > +
> > +    pss = (ptm_setstate) {
> > +        .u.req.state_flags = cpu_to_be32(flags),
> > +        .u.req.type = cpu_to_be32(type),
> > +        .u.req.length = cpu_to_be32(tsb->size),
> > +    };
> > +
> > +    /* write the header only */
> > +    if (tpm_emulator_ctrlcmd(tpm_emu, CMD_SET_STATEBLOB, &pss,
> > +                             offsetof(ptm_setstate, u.req.data), 0) < 0) {
> > +        error_report("tpm-emulator: could not set state blob type %d : %s",
> > +                     type, strerror(errno));
> > +        return -1;
> > +    }
> > +
> > +    /* now the body */
> > +    n = qemu_chr_fe_write_all(&tpm_emu->ctrl_chr,
> > +                              &tsb->buffer[offset], tsb->size);
> > +    if (n != tsb->size) {
> > +        error_report("tpm-emulator: Writing the stateblob (type %d) "
> > +                     "failed: %s", type, strerror(errno));
> > +        return -1;
> > +    }
> > +
> > +    /* now get the result */
> > +    n = qemu_chr_fe_read_all(&tpm_emu->ctrl_chr,
> > +                             (uint8_t *)&pss, sizeof(pss.u.resp));
> > +    if (n != sizeof(pss.u.resp)) {
> > +        error_report("tpm-emulator: Reading response from writing 
> > stateblob "
> > +                     "(type %d) failed: %s", type, strerror(errno));
> > +        return -1;
> > +    }
> > +
> > +    tpm_result = be32_to_cpu(pss.u.resp.tpm_result);
> > +    if (tpm_result != 0) {
> > +        error_report("tpm-emulator: Setting the stateblob (type %d) failed 
> > "
> > +                     "with a TPM error 0x%x", type, tpm_result);
> > +        return -1;
> > +    }
> > +
> > +    DPRINTF("set the state blob type %d, %d bytes, flags 0x%08x\n",
> > +            type, tsb->size, flags);
> > +
> > +    return 0;
> > +}
> > +
> > +/*
> > + * Set all the TPM state blobs.
> > + *
> > + * Returns a negative errno code in case of error.
> > + */
> > +static int tpm_emulator_set_state_blobs(TPMBackend *tb)
> > +{
> > +    TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
> > +    TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs;
> > +
> > +    DPRINTF("%s: %d", __func__, __LINE__);
> > +
> > +    if (tpm_emulator_stop_tpm(tb) < 0) {
> > +        return -EIO;
> > +    }
> > +
> > +    if (tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_PERMANENT,
> > +                                    &state_blobs->permanent,
> > +                                    state_blobs->permanent_flags) < 0 ||
> > +        tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_VOLATILE,
> > +                                    &state_blobs->volatil,
> > +                                    state_blobs->volatil_flags) < 0 ||
> > +        tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_SAVESTATE,
> > +                                    &state_blobs->savestate,
> > +                                    state_blobs->savestate_flags) < 0) {
> > +        return -EBADMSG;
> > +    }
> > +
> > +    DPRINTF("DONE SETTING STATEBLOBS");
> 
> UPPERCASES!
> 
> > +
> > +    return 0;
> > +}
> > +
> > +static int tpm_emulator_pre_save(void *opaque)
> > +{
> > +    TPMBackend *tb = opaque;
> > +    TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
> > +
> > +    DPRINTF("%s", __func__);
> > +
> > +    tpm_backend_finish_sync(tb);
> > +
> > +    /* get the state blobs from the TPM */
> > +    return tpm_emulator_get_state_blobs(tpm_emu);
> > +}
> > +
> > +/*
> > + * Load the TPM state blobs into the TPM.
> > + *
> > + * Returns negative errno codes in case of error.
> > + */
> > +static int tpm_emulator_post_load(void *opaque,
> > +                                  int version_id __attribute__((unused)))
> 
> unnecessary attribute
> 
> > +{
> > +    TPMBackend *tb = opaque;
> > +    int ret;
> > +
> > +    ret = tpm_emulator_set_state_blobs(tb);
> > +    if (ret < 0) {
> > +        return ret;
> > +    }
> > +
> > +    if (_tpm_emulator_startup_tpm(tb, 0, true) < 0) {
> > +        return -EIO;
> > +    }
> > +
> 
> I dislike the mix of functions returning errno and others, and the
> lack of Error **, but it's not specific to this patch. Ok.  :)
> 
> > +    return 0;
> > +}
> > +
> > +static const VMStateDescription vmstate_tpm_emulator = {
> > +    .name = "tpm-emulator",
> > +    .version_id = 1,
> > +    .minimum_version_id = 0,
> 
> version_id = 1 & minimum_version_id = 0 ?
> 
> It's the first version, let's have version_id = 0 (and you could
> remove minimum_version).
> 
> > +    .minimum_version_id_old = 0,
> 
> No need to specify that, no load_state_old() either
> 
> > +    .pre_save = tpm_emulator_pre_save,
> > +    .post_load = tpm_emulator_post_load,
> > +    .fields = (VMStateField[]) {
> > +        VMSTATE_UINT32(state_blobs.permanent_flags, TPMEmulator),
> > +        VMSTATE_UINT32(state_blobs.permanent.size, TPMEmulator),
> > +        VMSTATE_VBUFFER_ALLOC_UINT32(state_blobs.permanent.buffer,
> > +                                     TPMEmulator, 1, 0,
> > +                                     state_blobs.permanent.size),
> > +
> > +        VMSTATE_UINT32(state_blobs.volatil_flags, TPMEmulator),
> > +        VMSTATE_UINT32(state_blobs.volatil.size, TPMEmulator),
> > +        VMSTATE_VBUFFER_ALLOC_UINT32(state_blobs.volatil.buffer,
> > +                                     TPMEmulator, 1, 0,
> > +                                     state_blobs.volatil.size),
> > +
> > +        VMSTATE_UINT32(state_blobs.savestate_flags, TPMEmulator),
> > +        VMSTATE_UINT32(state_blobs.savestate.size, TPMEmulator),
> > +        VMSTATE_VBUFFER_ALLOC_UINT32(state_blobs.savestate.buffer,
> > +                                     TPMEmulator, 1, 0,
> > +                                     state_blobs.savestate.size),
> > +
> > +        VMSTATE_END_OF_LIST()
> > +    }
> > +};
> > +
> >  static void tpm_emulator_inst_init(Object *obj)
> >  {
> >      TPMEmulator *tpm_emu = TPM_EMULATOR(obj);
> > @@ -577,6 +865,8 @@ static void tpm_emulator_inst_init(Object *obj)
> >      tpm_emu->options = g_new0(TPMEmulatorOptions, 1);
> >      tpm_emu->cur_locty_number = ~0;
> >      qemu_mutex_init(&tpm_emu->mutex);
> > +
> > +    vmstate_register(NULL, -1, &vmstate_tpm_emulator, obj);
> >  }
> >
> >  /*
> > @@ -613,6 +903,8 @@ static void tpm_emulator_inst_finalize(Object *obj)
> >      }
> >
> >      qemu_mutex_destroy(&tpm_emu->mutex);
> > +
> > +    vmstate_unregister(NULL, &vmstate_tpm_emulator, obj);

It's best to avoid the vmstate_register/unregister and just set the
dc->vmsd pointer during class_init; see hw/virtio/virtio-balloon.c
virtio_balloon_class_init for an example.

Dave
> >  }
> >
> >  static void tpm_emulator_class_init(ObjectClass *klass, void *data)
> > --
> > 2.5.5
> >
> >
> 
> looks good to me overall
> 
> -- 
> Marc-André Lureau
--
Dr. David Alan Gilbert / dgilb...@redhat.com / Manchester, UK

Reply via email to