diff options
-rw-r--r-- | docs/specs/tpm.txt | 106 | ||||
-rw-r--r-- | hw/i386/pc_piix.c | 8 | ||||
-rw-r--r-- | hw/i386/pc_q35.c | 8 | ||||
-rw-r--r-- | hw/ppc/spapr.c | 14 | ||||
-rw-r--r-- | hw/s390x/s390-virtio-ccw.c | 10 | ||||
-rw-r--r-- | hw/tpm/tpm_emulator.c | 323 | ||||
-rw-r--r-- | hw/tpm/tpm_tis.c | 52 | ||||
-rw-r--r-- | hw/tpm/trace-events | 9 | ||||
-rw-r--r-- | migration/exec.c | 4 | ||||
-rw-r--r-- | migration/fd.c | 4 | ||||
-rw-r--r-- | migration/ram.c | 4 | ||||
-rw-r--r-- | qapi/block-core.json | 4 | ||||
-rw-r--r-- | qapi/common.json | 2 | ||||
-rw-r--r-- | qapi/migration.json | 16 | ||||
-rw-r--r-- | qapi/misc.json | 4 | ||||
-rw-r--r-- | qapi/net.json | 2 | ||||
-rw-r--r-- | qapi/ui.json | 2 | ||||
-rw-r--r-- | qemu-doc.texi | 4 | ||||
-rw-r--r-- | target/ppc/cpu.h | 2 | ||||
-rw-r--r-- | target/ppc/machine.c | 8 | ||||
-rw-r--r-- | target/ppc/translate_init.inc.c | 2 | ||||
-rw-r--r-- | tests/Makefile.include | 3 | ||||
-rw-r--r-- | tests/tpm-crb-swtpm-test.c | 247 | ||||
-rw-r--r-- | tests/tpm-util.c | 186 | ||||
-rw-r--r-- | tests/tpm-util.h | 36 |
25 files changed, 1001 insertions, 59 deletions
diff --git a/docs/specs/tpm.txt b/docs/specs/tpm.txt index d1d71571e9..c230c4c93e 100644 --- a/docs/specs/tpm.txt +++ b/docs/specs/tpm.txt @@ -200,3 +200,109 @@ crw-------. 1 root root 10, 224 Jul 11 10:11 /dev/tpm0 PCR-00: 35 4E 3B CE 23 9F 38 59 ... ... PCR-23: 00 00 00 00 00 00 00 00 ... + + +=== Migration with the TPM emulator === + +The TPM emulator supports the following types of virtual machine migration: + +- VM save / restore (migration into a file) +- Network migration +- Snapshotting (migration into storage like QoW2 or QED) + +The following command sequences can be used to test VM save / restore. + + +In a 1st terminal start an instance of a swtpm using the following command: + +mkdir /tmp/mytpm1 +swtpm socket --tpmstate dir=/tmp/mytpm1 \ + --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \ + --log level=20 --tpm2 + +In a 2nd terminal start the VM: + +qemu-system-x86_64 -display sdl -enable-kvm \ + -m 1024 -boot d -bios bios-256k.bin -boot menu=on \ + -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ + -tpmdev emulator,id=tpm0,chardev=chrtpm \ + -device tpm-tis,tpmdev=tpm0 \ + -monitor stdio \ + test.img + +Verify that the attached TPM is working as expected using applications inside +the VM. + +To store the state of the VM use the following command in the QEMU monitor in +the 2nd terminal: + +(qemu) migrate "exec:cat > testvm.bin" +(qemu) quit + +At this point a file called 'testvm.bin' should exists and the swtpm and QEMU +processes should have ended. + +To test 'VM restore' you have to start the swtpm with the same parameters +as before. If previously a TPM 2 [--tpm2] was saved, --tpm2 must now be +passed again on the command line. + +In the 1st terminal restart the swtpm with the same command line as before: + +swtpm socket --tpmstate dir=/tmp/mytpm1 \ + --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \ + --log level=20 --tpm2 + +In the 2nd terminal restore the state of the VM using the additonal +'-incoming' option. + +qemu-system-x86_64 -display sdl -enable-kvm \ + -m 1024 -boot d -bios bios-256k.bin -boot menu=on \ + -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ + -tpmdev emulator,id=tpm0,chardev=chrtpm \ + -device tpm-tis,tpmdev=tpm0 \ + -incoming "exec:cat < testvm.bin" \ + test.img + + +Troubleshooting migration: + +There are several reasons why migration may fail. In case of problems, +please ensure that the command lines adhere to the following rules and, +if possible, that identical versions of QEMU and swtpm are used at all +times. + +VM save and restore: + - QEMU command line parameters should be identical apart from the + '-incoming' option on VM restore + - swtpm command line parameters should be identical + +VM migration to 'localhost': + - QEMU command line parameters should be identical apart from the + '-incoming' option on the destination side + - swtpm command line parameters should point to two different + directories on the source and destination swtpm (--tpmstate dir=...) + (especially if different versions of libtpms were to be used on the + same machine). + +VM migration across the network: + - QEMU command line parameters should be identical apart from the + '-incoming' option on the destination side + - swtpm command line parameters should be identical + +VM Snapshotting: + - QEMU command line parameters should be identical + - swtpm command line parameters should be identical + + +Besides that, migration failure reasons on the swtpm level may include +the following: + + - the versions of the swtpm on the source and destination sides are + incompatible + - downgrading of TPM state may not be supported + - the source and destination libtpms were compiled with different + compile-time options and the destination side refuses to accept the + state + - different migration keys are used on the source and destination side + and the destination side cannot decrypt the migrated state + (swtpm ... --migration-key ... ) diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index e36c7bbb40..b4c5b03274 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -425,19 +425,19 @@ static void pc_i440fx_machine_options(MachineClass *m) m->default_display = "std"; } -static void pc_i440fx_2_13_machine_options(MachineClass *m) +static void pc_i440fx_3_0_machine_options(MachineClass *m) { pc_i440fx_machine_options(m); m->alias = "pc"; m->is_default = 1; } -DEFINE_I440FX_MACHINE(v2_13, "pc-i440fx-2.13", NULL, - pc_i440fx_2_13_machine_options); +DEFINE_I440FX_MACHINE(v3_0, "pc-i440fx-3.0", NULL, + pc_i440fx_3_0_machine_options); static void pc_i440fx_2_12_machine_options(MachineClass *m) { - pc_i440fx_2_13_machine_options(m); + pc_i440fx_3_0_machine_options(m); m->is_default = 0; m->alias = NULL; SET_MACHINE_COMPAT(m, PC_COMPAT_2_12); diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 2372457c6a..83d6d75efa 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -308,18 +308,18 @@ static void pc_q35_machine_options(MachineClass *m) m->max_cpus = 288; } -static void pc_q35_2_13_machine_options(MachineClass *m) +static void pc_q35_3_0_machine_options(MachineClass *m) { pc_q35_machine_options(m); m->alias = "q35"; } -DEFINE_Q35_MACHINE(v2_13, "pc-q35-2.13", NULL, - pc_q35_2_13_machine_options); +DEFINE_Q35_MACHINE(v3_0, "pc-q35-3.0", NULL, + pc_q35_3_0_machine_options); static void pc_q35_2_12_machine_options(MachineClass *m) { - pc_q35_2_13_machine_options(m); + pc_q35_3_0_machine_options(m); m->alias = NULL; SET_MACHINE_COMPAT(m, PC_COMPAT_2_12); } diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index ebf30dd60b..213f6f9599 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -4071,18 +4071,18 @@ static const TypeInfo spapr_machine_info = { type_init(spapr_machine_register_##suffix) /* - * pseries-2.13 + * pseries-3.0 */ -static void spapr_machine_2_13_instance_options(MachineState *machine) +static void spapr_machine_3_0_instance_options(MachineState *machine) { } -static void spapr_machine_2_13_class_options(MachineClass *mc) +static void spapr_machine_3_0_class_options(MachineClass *mc) { /* Defaults for the latest behaviour inherited from the base class */ } -DEFINE_SPAPR_MACHINE(2_13, "2.13", true); +DEFINE_SPAPR_MACHINE(3_0, "3.0", true); /* * pseries-2.12 @@ -4091,18 +4091,18 @@ DEFINE_SPAPR_MACHINE(2_13, "2.13", true); HW_COMPAT_2_12 \ { \ .driver = TYPE_POWERPC_CPU, \ - .property = "pre-2.13-migration", \ + .property = "pre-3.0-migration", \ .value = "on", \ }, static void spapr_machine_2_12_instance_options(MachineState *machine) { - spapr_machine_2_13_instance_options(machine); + spapr_machine_3_0_instance_options(machine); } static void spapr_machine_2_12_class_options(MachineClass *mc) { - spapr_machine_2_13_class_options(mc); + spapr_machine_3_0_class_options(mc); SET_MACHINE_COMPAT(mc, SPAPR_COMPAT_2_12); } diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index e548d341a0..7ae5fb38dd 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c @@ -812,23 +812,23 @@ bool css_migration_enabled(void) .value = "0",\ }, -static void ccw_machine_2_13_instance_options(MachineState *machine) +static void ccw_machine_3_0_instance_options(MachineState *machine) { } -static void ccw_machine_2_13_class_options(MachineClass *mc) +static void ccw_machine_3_0_class_options(MachineClass *mc) { } -DEFINE_CCW_MACHINE(2_13, "2.13", true); +DEFINE_CCW_MACHINE(3_0, "3.0", true); static void ccw_machine_2_12_instance_options(MachineState *machine) { - ccw_machine_2_13_instance_options(machine); + ccw_machine_3_0_instance_options(machine); } static void ccw_machine_2_12_class_options(MachineClass *mc) { - ccw_machine_2_13_class_options(mc); + ccw_machine_3_0_class_options(mc); SET_MACHINE_COMPAT(mc, CCW_COMPAT_2_12); } DEFINE_CCW_MACHINE(2_12, "2.12", false); diff --git a/hw/tpm/tpm_emulator.c b/hw/tpm/tpm_emulator.c index 6418ef0831..10bc20dbec 100644 --- a/hw/tpm/tpm_emulator.c +++ b/hw/tpm/tpm_emulator.c @@ -4,7 +4,7 @@ * Copyright (c) 2017 Intel Corporation * Author: Amarnath Valluri <amarnath.valluri@intel.com> * - * Copyright (c) 2010 - 2013 IBM Corporation + * Copyright (c) 2010 - 2013, 2018 IBM Corporation * Authors: * Stefan Berger <stefanb@us.ibm.com> * @@ -49,6 +49,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; @@ -64,6 +77,8 @@ typedef struct TPMEmulator { unsigned int established_flag:1; unsigned int established_flag_cached:1; + + TPMBlobBuffers state_blobs; } TPMEmulator; @@ -293,7 +308,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_resume(TPMBackend *tb, size_t buffersize, + bool is_resume) { TPMEmulator *tpm_emu = TPM_EMULATOR(tb); ptm_init init = { @@ -301,12 +317,17 @@ static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize) }; ptm_res res; + trace_tpm_emulator_startup_tpm_resume(is_resume, buffersize); + if (buffersize != 0 && tpm_emulator_set_buffer_size(tb, buffersize, NULL) < 0) { goto err_exit; } - trace_tpm_emulator_startup_tpm(); + if (is_resume) { + init.u.req.init_flags |= cpu_to_be32(PTM_INIT_FLAG_DELETE_VOLATILE); + } + if (tpm_emulator_ctrlcmd(tpm_emu, CMD_INIT, &init, sizeof(init), sizeof(init)) < 0) { error_report("tpm-emulator: could not send INIT: %s", @@ -325,6 +346,11 @@ err_exit: return -1; } +static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize) +{ + return tpm_emulator_startup_tpm_resume(tb, buffersize, false); +} + static bool tpm_emulator_get_tpm_established_flag(TPMBackend *tb) { TPMEmulator *tpm_emu = TPM_EMULATOR(tb); @@ -423,16 +449,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; @@ -570,6 +601,267 @@ 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; + } + + 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); + + if (totlength > 0) { + tsb->buffer = g_try_malloc(totlength); + if (!tsb->buffer) { + error_report("tpm-emulator: Out of memory allocating %u bytes", + totlength); + return -1; + } + + 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); " + "expected %u bytes, got %zd", + type, totlength, n); + return -1; + } + } + tsb->size = totlength; + + trace_tpm_emulator_get_state_blob(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: TPMSizedBuffer 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) +{ + 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, tsb->size); + if (n != tsb->size) { + error_report("tpm-emulator: Writing the stateblob (type %d) " + "failed; could not write %u bytes, but only %zd", + type, tsb->size, n); + 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; expected %zu bytes, got %zd", type, + sizeof(pss.u.resp), n); + 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; + } + + trace_tpm_emulator_set_state_blob(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; + + trace_tpm_emulator_set_state_blobs(); + + if (tpm_emulator_stop_tpm(tb) < 0) { + trace_tpm_emulator_set_state_blobs_error("Could not stop TPM"); + 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 -EIO; + } + + trace_tpm_emulator_set_state_blobs_done(); + + return 0; +} + +static int tpm_emulator_pre_save(void *opaque) +{ + TPMBackend *tb = opaque; + TPMEmulator *tpm_emu = TPM_EMULATOR(tb); + + trace_tpm_emulator_pre_save(); + + 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) +{ + TPMBackend *tb = opaque; + int ret; + + ret = tpm_emulator_set_state_blobs(tb); + if (ret < 0) { + return ret; + } + + if (tpm_emulator_startup_tpm_resume(tb, 0, true) < 0) { + return -EIO; + } + + return 0; +} + +static const VMStateDescription vmstate_tpm_emulator = { + .name = "tpm-emulator", + .version_id = 0, + .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, 0, 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, 0, 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, 0, 0, + state_blobs.savestate.size), + + VMSTATE_END_OF_LIST() + } +}; + static void tpm_emulator_inst_init(Object *obj) { TPMEmulator *tpm_emu = TPM_EMULATOR(obj); @@ -579,6 +871,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); } /* @@ -600,6 +894,7 @@ static void tpm_emulator_shutdown(TPMEmulator *tpm_emu) static void tpm_emulator_inst_finalize(Object *obj) { TPMEmulator *tpm_emu = TPM_EMULATOR(obj); + TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs; tpm_emulator_shutdown(tpm_emu); @@ -614,7 +909,13 @@ static void tpm_emulator_inst_finalize(Object *obj) error_free(tpm_emu->migration_blocker); } + tpm_sized_buffer_reset(&state_blobs->volatil); + tpm_sized_buffer_reset(&state_blobs->permanent); + tpm_sized_buffer_reset(&state_blobs->savestate); + qemu_mutex_destroy(&tpm_emu->mutex); + + vmstate_unregister(NULL, &vmstate_tpm_emulator, obj); } static void tpm_emulator_class_init(ObjectClass *klass, void *data) diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c index 2ac7e74307..12f5c9a759 100644 --- a/hw/tpm/tpm_tis.c +++ b/hw/tpm/tpm_tis.c @@ -894,9 +894,57 @@ static void tpm_tis_reset(DeviceState *dev) tpm_backend_startup_tpm(s->be_driver, s->be_buffer_size); } +/* persistent state handling */ + +static int tpm_tis_pre_save(void *opaque) +{ + TPMState *s = opaque; + uint8_t locty = s->active_locty; + + trace_tpm_tis_pre_save(locty, s->rw_offset); + + if (DEBUG_TIS) { + tpm_tis_dump_state(opaque, 0); + } + + /* + * Synchronize with backend completion. + */ + tpm_backend_finish_sync(s->be_driver); + + return 0; +} + +static const VMStateDescription vmstate_locty = { + .name = "tpm-tis/locty", + .version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(state, TPMLocality), + VMSTATE_UINT32(inte, TPMLocality), + VMSTATE_UINT32(ints, TPMLocality), + VMSTATE_UINT8(access, TPMLocality), + VMSTATE_UINT32(sts, TPMLocality), + VMSTATE_UINT32(iface_id, TPMLocality), + VMSTATE_END_OF_LIST(), + } +}; + static const VMStateDescription vmstate_tpm_tis = { - .name = "tpm", - .unmigratable = 1, + .name = "tpm-tis", + .version_id = 0, + .pre_save = tpm_tis_pre_save, + .fields = (VMStateField[]) { + VMSTATE_BUFFER(buffer, TPMState), + VMSTATE_UINT16(rw_offset, TPMState), + VMSTATE_UINT8(active_locty, TPMState), + VMSTATE_UINT8(aborting_locty, TPMState), + VMSTATE_UINT8(next_locty, TPMState), + + VMSTATE_STRUCT_ARRAY(loc, TPMState, TPM_TIS_NUM_LOCALITIES, 0, + vmstate_locty, TPMLocality), + + VMSTATE_END_OF_LIST() + } }; static Property tpm_tis_properties[] = { diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events index 9a65384088..25bee0cecf 100644 --- a/hw/tpm/trace-events +++ b/hw/tpm/trace-events @@ -20,13 +20,19 @@ tpm_emulator_set_locality(uint8_t locty) "setting locality to %d" tpm_emulator_handle_request(void) "processing TPM command" tpm_emulator_probe_caps(uint64_t caps) "capabilities: 0x%"PRIx64 tpm_emulator_set_buffer_size(uint32_t buffersize, uint32_t minsize, uint32_t maxsize) "buffer size: %u, min: %u, max: %u" -tpm_emulator_startup_tpm(void) "startup" +tpm_emulator_startup_tpm_resume(bool is_resume, size_t buffersize) "is_resume: %d, buffer size: %zu" tpm_emulator_get_tpm_established_flag(uint8_t flag) "got established flag: %d" tpm_emulator_cancel_cmd_not_supt(void) "Backend does not support CANCEL_TPM_CMD" tpm_emulator_handle_device_opts_tpm12(void) "TPM Version 1.2" tpm_emulator_handle_device_opts_tpm2(void) "TPM Version 2" tpm_emulator_handle_device_opts_unspec(void) "TPM Version Unspecified" tpm_emulator_handle_device_opts_startup_error(void) "Startup error" +tpm_emulator_get_state_blob(uint8_t type, uint32_t size, uint32_t flags) "got state blob type %d, %u bytes, flags 0x%08x" +tpm_emulator_set_state_blob(uint8_t type, uint32_t size, uint32_t flags) "set state blob type %d, %u bytes, flags 0x%08x" +tpm_emulator_set_state_blobs(void) "setting state blobs" +tpm_emulator_set_state_blobs_error(const char *msg) "error while setting state blobs: %s" +tpm_emulator_set_state_blobs_done(void) "Done setting state blobs" +tpm_emulator_pre_save(void) "" tpm_emulator_inst_init(void) "" # hw/tpm/tpm_tis.c @@ -44,3 +50,4 @@ tpm_tis_mmio_write_locty_seized(uint8_t locty, uint8_t active) "Locality %d seiz tpm_tis_mmio_write_init_abort(void) "Initiating abort" tpm_tis_mmio_write_lowering_irq(void) "Lowering IRQ" tpm_tis_mmio_write_data2send(uint32_t value, unsigned size) "Data to send to TPM: 0x%08x (size=%d)" +tpm_tis_pre_save(uint8_t locty, uint32_t rw_offset) "locty: %d, rw_offset = %u" diff --git a/migration/exec.c b/migration/exec.c index 9d0f82f1f0..0bbeb63c97 100644 --- a/migration/exec.c +++ b/migration/exec.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "channel.h" #include "exec.h" +#include "migration.h" #include "io/channel-command.h" #include "trace.h" @@ -48,6 +49,9 @@ static gboolean exec_accept_incoming_migration(QIOChannel *ioc, { migration_channel_process_incoming(ioc); object_unref(OBJECT(ioc)); + if (!migrate_use_multifd()) { + migration_incoming_process(); + } return G_SOURCE_REMOVE; } diff --git a/migration/fd.c b/migration/fd.c index 9a380bbbc4..fee34ffdc0 100644 --- a/migration/fd.c +++ b/migration/fd.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" #include "channel.h" #include "fd.h" +#include "migration.h" #include "monitor/monitor.h" #include "io/channel-util.h" #include "trace.h" @@ -48,6 +49,9 @@ static gboolean fd_accept_incoming_migration(QIOChannel *ioc, { migration_channel_process_incoming(ioc); object_unref(OBJECT(ioc)); + if (!migrate_use_multifd()) { + migration_incoming_process(); + } return G_SOURCE_REMOVE; } diff --git a/migration/ram.c b/migration/ram.c index 5bcbf7a9f9..c53e8369a3 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -246,7 +246,7 @@ int64_t ramblock_recv_bitmap_send(QEMUFile *file, qemu_put_be64(file, RAMBLOCK_RECV_BITMAP_ENDING); qemu_fflush(file); - free(le_bitmap); + g_free(le_bitmap); if (qemu_file_get_error(file)) { return qemu_file_get_error(file); @@ -3514,7 +3514,7 @@ int ram_dirty_bitmap_reload(MigrationState *s, RAMBlock *block) ret = 0; out: - free(le_bitmap); + g_free(le_bitmap); return ret; } diff --git a/qapi/block-core.json b/qapi/block-core.json index 7dfa77a05c..ad66ad6f80 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -2424,7 +2424,7 @@ # @vxhs: Since 2.10 # @throttle: Since 2.11 # @nvme: Since 2.12 -# @copy-on-read: Since 2.13 +# @copy-on-read: Since 3.0 # # Since: 2.9 ## @@ -2452,7 +2452,7 @@ # @x-check-cache-dropped: whether to check that page cache was dropped on live # migration. May cause noticeable delays if the image # file is large, do not use in production. -# (default: off) (since: 2.13) +# (default: off) (since: 3.0) # # Since: 2.9 ## diff --git a/qapi/common.json b/qapi/common.json index c811d04984..c367adc4b6 100644 --- a/qapi/common.json +++ b/qapi/common.json @@ -140,7 +140,7 @@ # prefix to produce the corresponding QEMU executable name. This # is true even for "qemu-system-x86_64". # -# Since: 2.13 +# Since: 3.0 ## { 'enum' : 'SysEmuTarget', 'data' : [ 'aarch64', 'alpha', 'arm', 'cris', 'hppa', 'i386', 'lm32', diff --git a/qapi/migration.json b/qapi/migration.json index 3ec418dabf..dc9cc85545 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -89,9 +89,9 @@ # # @postcopy-active: like active, but now in postcopy mode. (since 2.5) # -# @postcopy-paused: during postcopy but paused. (since 2.13) +# @postcopy-paused: during postcopy but paused. (since 3.0) # -# @postcopy-recover: trying to recover from a paused postcopy. (since 2.13) +# @postcopy-recover: trying to recover from a paused postcopy. (since 3.0) # # @completed: migration is finished. # @@ -163,11 +163,11 @@ # # @postcopy-blocktime: total time when all vCPU were blocked during postcopy # live migration. This is only present when the postcopy-blocktime -# migration capability is enabled. (Since 2.13) +# migration capability is enabled. (Since 3.0) # # @postcopy-vcpu-blocktime: list of the postcopy blocktime per vCPU. This is # only present when the postcopy-blocktime migration capability -# is enabled. (Since 2.13) +# is enabled. (Since 3.0) # # # Since: 0.14.0 @@ -374,7 +374,7 @@ # (since 2.12) # # @postcopy-blocktime: Calculate downtime for postcopy live migration -# (since 2.13) +# (since 3.0) # # Since: 1.2 ## @@ -1034,7 +1034,7 @@ # @detach: this argument exists only for compatibility reasons and # is ignored by QEMU # -# @resume: resume one paused migration, default "off". (since 2.13) +# @resume: resume one paused migration, default "off". (since 3.0) # # Returns: nothing on success # @@ -1208,7 +1208,7 @@ # "arguments": { "uri": "tcp:192.168.1.200:12345" } } # <- { "return": {} } # -# Since: 2.13 +# Since: 3.0 ## { 'command': 'migrate-recover', 'data': { 'uri': 'str' }, 'allow-oob': true } @@ -1225,6 +1225,6 @@ # -> { "execute": "migrate-pause" } # <- { "return": {} } # -# Since: 2.13 +# Since: 3.0 ## { 'command': 'migrate-pause', 'allow-oob': true } diff --git a/qapi/misc.json b/qapi/misc.json index f5988cc0b5..99bcaacd62 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -558,11 +558,11 @@ # @props: properties describing to which node/socket/core/thread # virtual CPU belongs to, provided if supported by board # -# @arch: base architecture of the cpu; deprecated since 2.13.0 in favor +# @arch: base architecture of the cpu; deprecated since 3.0.0 in favor # of @target # # @target: the QEMU system emulation target, which determines which -# additional fields will be listed (since 2.13) +# additional fields will be listed (since 3.0) # # Since: 2.12 # diff --git a/qapi/net.json b/qapi/net.json index b8adf1f03f..5c1dc48890 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -498,7 +498,7 @@ # # Since: 1.2 # -# 'vlan': dropped in 2.13 +# 'vlan': dropped in 3.0 ## { 'struct': 'NetLegacy', 'data': { diff --git a/qapi/ui.json b/qapi/ui.json index 3ad7835992..fc18a05f0f 100644 --- a/qapi/ui.json +++ b/qapi/ui.json @@ -1031,7 +1031,7 @@ # @core: Use OpenGL with Core (desktop) Context. # @es: Use OpenGL with ES (embedded systems) Context. # - # Since: 2.13 + # Since: 3.0 # ## { 'enum' : 'DisplayGLMode', diff --git a/qemu-doc.texi b/qemu-doc.texi index 0e0e0ae72b..cac1c3b39e 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -2917,7 +2917,7 @@ The @code{-localtime} option has been replaced by @code{-rtc base=localtime}. The @code{-startdate} option has been replaced by @code{-rtc base=@var{date}}. -@subsection -virtioconsole (since 2.13.0) +@subsection -virtioconsole (since 3.0.0) Option @option{-virtioconsole} has been replaced by @option{-device virtconsole}. @@ -2940,7 +2940,7 @@ from qcow2 images. The ``query-cpus'' command is replaced by the ``query-cpus-fast'' command. -@subsection query-cpus-fast "arch" output member (since 2.13.0) +@subsection query-cpus-fast "arch" output member (since 3.0.0) The ``arch'' output member of the ``query-cpus-fast'' command is replaced by the ``target'' output member. diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 7ccd2f460e..0247c1f04c 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -1215,7 +1215,7 @@ struct PowerPCCPU { uint64_t mig_insns_flags2; uint32_t mig_nb_BATs; bool pre_2_10_migration; - bool pre_2_13_migration; + bool pre_3_0_migration; int32_t mig_slb_nr; }; diff --git a/target/ppc/machine.c b/target/ppc/machine.c index ba1b9e531f..b2745ec4e5 100644 --- a/target/ppc/machine.c +++ b/target/ppc/machine.c @@ -150,11 +150,11 @@ static bool cpu_pre_2_8_migration(void *opaque, int version_id) } #if defined(TARGET_PPC64) -static bool cpu_pre_2_13_migration(void *opaque, int version_id) +static bool cpu_pre_3_0_migration(void *opaque, int version_id) { PowerPCCPU *cpu = opaque; - return cpu->pre_2_13_migration; + return cpu->pre_3_0_migration; } #endif @@ -220,7 +220,7 @@ static int cpu_pre_save(void *opaque) cpu->mig_insns_flags2 = env->insns_flags2 & insns_compat_mask2; cpu->mig_nb_BATs = env->nb_BATs; } - if (cpu->pre_2_13_migration) { + if (cpu->pre_3_0_migration) { if (cpu->hash64_opts) { cpu->mig_slb_nr = cpu->hash64_opts->slb_size; } @@ -517,7 +517,7 @@ static const VMStateDescription vmstate_slb = { .needed = slb_needed, .post_load = slb_post_load, .fields = (VMStateField[]) { - VMSTATE_INT32_TEST(mig_slb_nr, PowerPCCPU, cpu_pre_2_13_migration), + VMSTATE_INT32_TEST(mig_slb_nr, PowerPCCPU, cpu_pre_3_0_migration), VMSTATE_SLB_ARRAY(env.slb, PowerPCCPU, MAX_SLB_ENTRIES), VMSTATE_END_OF_LIST() } diff --git a/target/ppc/translate_init.inc.c b/target/ppc/translate_init.inc.c index a0b3f184b2..ab782cb32a 100644 --- a/target/ppc/translate_init.inc.c +++ b/target/ppc/translate_init.inc.c @@ -10427,7 +10427,7 @@ static Property ppc_cpu_properties[] = { DEFINE_PROP_BOOL("pre-2.8-migration", PowerPCCPU, pre_2_8_migration, false), DEFINE_PROP_BOOL("pre-2.10-migration", PowerPCCPU, pre_2_10_migration, false), - DEFINE_PROP_BOOL("pre-2.13-migration", PowerPCCPU, pre_2_13_migration, + DEFINE_PROP_BOOL("pre-3.0-migration", PowerPCCPU, pre_3_0_migration, false), DEFINE_PROP_END_OF_LIST(), }; diff --git a/tests/Makefile.include b/tests/Makefile.include index 3b9a5e31a2..b499ba1813 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -297,6 +297,7 @@ check-qtest-i386-$(CONFIG_VHOST_USER_NET_TEST_i386) += tests/vhost-user-test$(EX ifeq ($(CONFIG_VHOST_USER_NET_TEST_i386),) check-qtest-x86_64-$(CONFIG_VHOST_USER_NET_TEST_x86_64) += tests/vhost-user-test$(EXESUF) endif +check-qtest-i386-$(CONFIG_TPM) += tests/tpm-crb-swtpm-test$(EXESUF) check-qtest-i386-$(CONFIG_TPM) += tests/tpm-crb-test$(EXESUF) check-qtest-i386-$(CONFIG_TPM) += tests/tpm-tis-test$(EXESUF) check-qtest-i386-$(CONFIG_SLIRP) += tests/test-netfilter$(EXESUF) @@ -721,6 +722,8 @@ tests/test-util-sockets$(EXESUF): tests/test-util-sockets.o \ tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y) tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \ tests/io-channel-helpers.o tests/socket-helpers.o $(test-io-obj-y) +tests/tpm-crb-swtpm-test$(EXESUF): tests/tpm-crb-swtpm-test.o tests/tpm-emu.o \ + tests/tpm-util.o $(test-io-obj-y) tests/tpm-crb-test$(EXESUF): tests/tpm-crb-test.o tests/tpm-emu.o $(test-io-obj-y) tests/tpm-tis-test$(EXESUF): tests/tpm-tis-test.o tests/tpm-emu.o $(test-io-obj-y) tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \ diff --git a/tests/tpm-crb-swtpm-test.c b/tests/tpm-crb-swtpm-test.c new file mode 100644 index 0000000000..c2bde0cbaa --- /dev/null +++ b/tests/tpm-crb-swtpm-test.c @@ -0,0 +1,247 @@ +/* + * QTest testcase for TPM CRB talking to external swtpm and swtpm migration + * + * Copyright (c) 2018 IBM Corporation + * with parts borrowed from migration-test.c that is: + * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates + * + * Authors: + * Stefan Berger <stefanb@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include <glib/gstdio.h> + +#include "hw/acpi/tpm.h" +#include "io/channel-socket.h" +#include "libqtest.h" +#include "tpm-util.h" +#include "sysemu/tpm.h" +#include "qapi/qmp/qdict.h" + +typedef struct TestState { + char *src_tpm_path; + char *dst_tpm_path; + char *uri; +} TestState; + +bool got_stop; + +static void migrate(QTestState *who, const char *uri) +{ + QDict *rsp; + gchar *cmd; + + cmd = g_strdup_printf("{ 'execute': 'migrate'," + "'arguments': { 'uri': '%s' } }", + uri); + rsp = qtest_qmp(who, cmd); + g_free(cmd); + g_assert(qdict_haskey(rsp, "return")); + qobject_unref(rsp); +} + +/* + * Events can get in the way of responses we are actually waiting for. + */ +static QDict *wait_command(QTestState *who, const char *command) +{ + const char *event_string; + QDict *response; + + response = qtest_qmp(who, command); + + while (qdict_haskey(response, "event")) { + /* OK, it was an event */ + event_string = qdict_get_str(response, "event"); + if (!strcmp(event_string, "STOP")) { + got_stop = true; + } + qobject_unref(response); + response = qtest_qmp_receive(who); + } + return response; +} + +static void wait_for_migration_complete(QTestState *who) +{ + while (true) { + QDict *rsp, *rsp_return; + bool completed; + const char *status; + + rsp = wait_command(who, "{ 'execute': 'query-migrate' }"); + rsp_return = qdict_get_qdict(rsp, "return"); + status = qdict_get_str(rsp_return, "status"); + completed = strcmp(status, "completed") == 0; + g_assert_cmpstr(status, !=, "failed"); + qobject_unref(rsp); + if (completed) { + return; + } + usleep(1000); + } +} + +static void migration_start_qemu(QTestState **src_qemu, QTestState **dst_qemu, + SocketAddress *src_tpm_addr, + SocketAddress *dst_tpm_addr, + const char *miguri) +{ + char *src_qemu_args, *dst_qemu_args; + + src_qemu_args = g_strdup_printf( + "-chardev socket,id=chr,path=%s " + "-tpmdev emulator,id=dev,chardev=chr " + "-device tpm-crb,tpmdev=dev ", + src_tpm_addr->u.q_unix.path); + + *src_qemu = qtest_init(src_qemu_args); + + dst_qemu_args = g_strdup_printf( + "-chardev socket,id=chr,path=%s " + "-tpmdev emulator,id=dev,chardev=chr " + "-device tpm-crb,tpmdev=dev " + "-incoming %s", + dst_tpm_addr->u.q_unix.path, + miguri); + + *dst_qemu = qtest_init(dst_qemu_args); + + free(src_qemu_args); + free(dst_qemu_args); +} + +static void tpm_crb_swtpm_test(const void *data) +{ + char *args = NULL; + QTestState *s; + SocketAddress *addr = NULL; + gboolean succ; + GPid swtpm_pid; + GError *error = NULL; + const TestState *ts = data; + + succ = tpm_util_swtpm_start(ts->src_tpm_path, &swtpm_pid, &addr, &error); + /* succ may be false if swtpm is not available */ + if (!succ) { + return; + } + + args = g_strdup_printf( + "-chardev socket,id=chr,path=%s " + "-tpmdev emulator,id=dev,chardev=chr " + "-device tpm-crb,tpmdev=dev", + addr->u.q_unix.path); + + s = qtest_start(args); + g_free(args); + + tpm_util_startup(s, tpm_util_crb_transfer); + tpm_util_pcrextend(s, tpm_util_crb_transfer); + + unsigned char tpm_pcrread_resp[] = + "\x80\x01\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00" + "\x00\x01\x00\x0b\x03\x00\x04\x00\x00\x00\x00\x01\x00\x20\xf6\x85" + "\x98\xe5\x86\x8d\xe6\x8b\x97\x29\x99\x60\xf2\x71\x7d\x17\x67\x89" + "\xa4\x2f\x9a\xae\xa8\xc7\xb7\xaa\x79\xa8\x62\x56\xc1\xde"; + tpm_util_pcrread(s, tpm_util_crb_transfer, tpm_pcrread_resp, + sizeof(tpm_pcrread_resp)); + + qtest_end(); + tpm_util_swtpm_kill(swtpm_pid); + + if (addr) { + g_unlink(addr->u.q_unix.path); + qapi_free_SocketAddress(addr); + } +} + +static void tpm_crb_swtpm_migration_test(const void *data) +{ + const TestState *ts = data; + gboolean succ; + GPid src_tpm_pid, dst_tpm_pid; + SocketAddress *src_tpm_addr = NULL, *dst_tpm_addr = NULL; + GError *error = NULL; + QTestState *src_qemu, *dst_qemu; + + succ = tpm_util_swtpm_start(ts->src_tpm_path, &src_tpm_pid, + &src_tpm_addr, &error); + /* succ may be false if swtpm is not available */ + if (!succ) { + return; + } + + succ = tpm_util_swtpm_start(ts->dst_tpm_path, &dst_tpm_pid, + &dst_tpm_addr, &error); + /* succ may be false if swtpm is not available */ + if (!succ) { + goto err_src_tpm_kill; + } + + migration_start_qemu(&src_qemu, &dst_qemu, src_tpm_addr, dst_tpm_addr, + ts->uri); + + tpm_util_startup(src_qemu, tpm_util_crb_transfer); + tpm_util_pcrextend(src_qemu, tpm_util_crb_transfer); + + unsigned char tpm_pcrread_resp[] = + "\x80\x01\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00" + "\x00\x01\x00\x0b\x03\x00\x04\x00\x00\x00\x00\x01\x00\x20\xf6\x85" + "\x98\xe5\x86\x8d\xe6\x8b\x97\x29\x99\x60\xf2\x71\x7d\x17\x67\x89" + "\xa4\x2f\x9a\xae\xa8\xc7\xb7\xaa\x79\xa8\x62\x56\xc1\xde"; + tpm_util_pcrread(src_qemu, tpm_util_crb_transfer, tpm_pcrread_resp, + sizeof(tpm_pcrread_resp)); + + migrate(src_qemu, ts->uri); + wait_for_migration_complete(src_qemu); + + tpm_util_pcrread(dst_qemu, tpm_util_crb_transfer, tpm_pcrread_resp, + sizeof(tpm_pcrread_resp)); + + qtest_quit(dst_qemu); + qtest_quit(src_qemu); + + tpm_util_swtpm_kill(dst_tpm_pid); + if (dst_tpm_addr) { + g_unlink(dst_tpm_addr->u.q_unix.path); + qapi_free_SocketAddress(dst_tpm_addr); + } + +err_src_tpm_kill: + tpm_util_swtpm_kill(src_tpm_pid); + if (src_tpm_addr) { + g_unlink(src_tpm_addr->u.q_unix.path); + qapi_free_SocketAddress(src_tpm_addr); + } +} + +int main(int argc, char **argv) +{ + int ret; + TestState ts = { 0 }; + + ts.src_tpm_path = g_dir_make_tmp("qemu-tpm-crb-swtpm-test.XXXXXX", NULL); + ts.dst_tpm_path = g_dir_make_tmp("qemu-tpm-crb-swtpm-test.XXXXXX", NULL); + ts.uri = g_strdup_printf("unix:%s/migsocket", ts.src_tpm_path); + + module_call_init(MODULE_INIT_QOM); + g_test_init(&argc, &argv, NULL); + + qtest_add_data_func("/tpm/crb-swtpm/test", &ts, tpm_crb_swtpm_test); + qtest_add_data_func("/tpm/crb-swtpm-migration/test", &ts, + tpm_crb_swtpm_migration_test); + ret = g_test_run(); + + g_rmdir(ts.dst_tpm_path); + g_free(ts.dst_tpm_path); + g_rmdir(ts.src_tpm_path); + g_free(ts.src_tpm_path); + g_free(ts.uri); + + return ret; +} diff --git a/tests/tpm-util.c b/tests/tpm-util.c new file mode 100644 index 0000000000..c9b3947c1c --- /dev/null +++ b/tests/tpm-util.c @@ -0,0 +1,186 @@ +/* + * QTest TPM utilities + * + * Copyright (c) 2018 IBM Corporation + * Copyright (c) 2018 Red Hat, Inc. + * + * Authors: + * Stefan Berger <stefanb@linux.vnet.ibm.com> + * Marc-André Lureau <marcandre.lureau@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" + +#include "hw/acpi/tpm.h" +#include "libqtest.h" +#include "tpm-util.h" + +void tpm_util_crb_transfer(QTestState *s, + const unsigned char *req, size_t req_size, + unsigned char *rsp, size_t rsp_size) +{ + uint64_t caddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR); + uint64_t raddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR); + + qtest_writeb(s, TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL, 1); + + qtest_memwrite(s, caddr, req, req_size); + + uint32_t sts, start = 1; + uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; + qtest_writel(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start); + while (true) { + start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START); + if ((start & 1) == 0) { + break; + } + if (g_get_monotonic_time() >= end_time) { + break; + } + }; + start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START); + g_assert_cmpint(start & 1, ==, 0); + sts = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS); + g_assert_cmpint(sts & 1, ==, 0); + + qtest_memread(s, raddr, rsp, rsp_size); +} + +void tpm_util_startup(QTestState *s, tx_func *tx) +{ + unsigned char buffer[1024]; + unsigned char tpm_startup[] = + "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00"; + unsigned char tpm_startup_resp[] = + "\x80\x01\x00\x00\x00\x0a\x00\x00\x00\x00"; + + tx(s, tpm_startup, sizeof(tpm_startup), buffer, sizeof(buffer)); + + g_assert_cmpmem(buffer, sizeof(tpm_startup_resp), + tpm_startup_resp, sizeof(tpm_startup_resp)); +} + +void tpm_util_pcrextend(QTestState *s, tx_func *tx) +{ + unsigned char buffer[1024]; + unsigned char tpm_pcrextend[] = + "\x80\x02\x00\x00\x00\x41\x00\x00\x01\x82\x00\x00\x00\x0a\x00\x00" + "\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00" + "\x0b\x74\x65\x73\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00"; + + unsigned char tpm_pcrextend_resp[] = + "\x80\x02\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x01\x00\x00"; + + tx(s, tpm_pcrextend, sizeof(tpm_pcrextend), buffer, sizeof(buffer)); + + g_assert_cmpmem(buffer, sizeof(tpm_pcrextend_resp), + tpm_pcrextend_resp, sizeof(tpm_pcrextend_resp)); +} + +void tpm_util_pcrread(QTestState *s, tx_func *tx, + const unsigned char *exp_resp, size_t exp_resp_size) +{ + unsigned char buffer[1024]; + unsigned char tpm_pcrread[] = + "\x80\x01\x00\x00\x00\x14\x00\x00\x01\x7e\x00\x00\x00\x01\x00\x0b" + "\x03\x00\x04\x00"; + + tx(s, tpm_pcrread, sizeof(tpm_pcrread), buffer, sizeof(buffer)); + + g_assert_cmpmem(buffer, exp_resp_size, exp_resp, exp_resp_size); +} + +static gboolean tpm_util_swtpm_has_tpm2(void) +{ + gint mystdout; + gboolean succ; + unsigned i; + char buffer[10240]; + ssize_t n; + gchar *swtpm_argv[] = { + g_strdup("swtpm"), g_strdup("socket"), g_strdup("--help"), NULL + }; + + succ = g_spawn_async_with_pipes(NULL, swtpm_argv, NULL, + G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, + NULL, &mystdout, NULL, NULL); + if (!succ) { + goto cleanup; + } + + n = read(mystdout, buffer, sizeof(buffer) - 1); + if (n < 0) { + goto cleanup; + } + buffer[n] = 0; + if (!strstr(buffer, "--tpm2")) { + succ = false; + } + + cleanup: + for (i = 0; swtpm_argv[i]; i++) { + g_free(swtpm_argv[i]); + } + + return succ; +} + +gboolean tpm_util_swtpm_start(const char *path, GPid *pid, + SocketAddress **addr, GError **error) +{ + char *swtpm_argv_tpmstate = g_strdup_printf("dir=%s", path); + char *swtpm_argv_ctrl = g_strdup_printf("type=unixio,path=%s/sock", + path); + gchar *swtpm_argv[] = { + g_strdup("swtpm"), g_strdup("socket"), + g_strdup("--tpmstate"), swtpm_argv_tpmstate, + g_strdup("--ctrl"), swtpm_argv_ctrl, + g_strdup("--tpm2"), + NULL + }; + gboolean succ; + unsigned i; + + succ = tpm_util_swtpm_has_tpm2(); + if (!succ) { + goto cleanup; + } + + *addr = g_new0(SocketAddress, 1); + (*addr)->type = SOCKET_ADDRESS_TYPE_UNIX; + (*addr)->u.q_unix.path = g_build_filename(path, "sock", NULL); + + succ = g_spawn_async(NULL, swtpm_argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, pid, error); + +cleanup: + for (i = 0; swtpm_argv[i]; i++) { + g_free(swtpm_argv[i]); + } + + return succ; +} + +void tpm_util_swtpm_kill(GPid pid) +{ + int n; + + if (!pid) { + return; + } + + g_spawn_close_pid(pid); + + n = kill(pid, 0); + if (n < 0) { + return; + } + + kill(pid, SIGKILL); +} diff --git a/tests/tpm-util.h b/tests/tpm-util.h new file mode 100644 index 0000000000..d155d99aea --- /dev/null +++ b/tests/tpm-util.h @@ -0,0 +1,36 @@ +/* + * QTest TPM utilities + * + * Copyright (c) 2018 IBM Corporation + * + * Authors: + * Stefan Berger <stefanb@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef TESTS_TPM_UTIL_H +#define TESTS_TPM_UTIL_H + +#include "qemu/osdep.h" +#include "io/channel-socket.h" + +typedef void (tx_func)(QTestState *s, + const unsigned char *req, size_t req_size, + unsigned char *rsp, size_t rsp_size); + +void tpm_util_crb_transfer(QTestState *s, + const unsigned char *req, size_t req_size, + unsigned char *rsp, size_t rsp_size); + +void tpm_util_startup(QTestState *s, tx_func *tx); +void tpm_util_pcrextend(QTestState *s, tx_func *tx); +void tpm_util_pcrread(QTestState *s, tx_func *tx, + const unsigned char *exp_resp, size_t exp_resp_size); + +gboolean tpm_util_swtpm_start(const char *path, GPid *pid, + SocketAddress **addr, GError **error); +void tpm_util_swtpm_kill(GPid pid); + +#endif /* TESTS_TPM_UTIL_H */ |