Merge tpm 2018/05/23 v4

-----BEGIN PGP SIGNATURE-----
 
 iQEcBAABAgAGBQJbCMvcAAoJEHWtZYAqC0IRaZkIAKYYlLTjuiSXjOi3pwAIK9MS
 kBXqEqQcAXeKXFVYfyrzxQGJK8vVm+I/P+L+Q3jOLanRWM+gZsxGpct7fDHAQQMY
 iY+o5RAZUJH9rpr9mgvim332A52B/TmPl3viU6SFVM8y7PSOm3Tm7NsC17ak7XDF
 rEY2q5Cou08SU5rTz6Dif2adoz9/YkITMIamz2nVmnjxS5zDCfe+DEh+TrQJXGt5
 3n+Ywva7AzYCvuunKIscYD1QBOu7pMMOTduGkABduKyWr1oLeipG1QU/VTqz9S7O
 xDvySSZMYUohj2D71J7brc05gmMQb668geDIxuKmnXrSYgSoraJatGvfV0UTxOA=
 =BJKH
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/stefanberger/tags/pull-tpm-2018-05-23-4' into staging

Merge tpm 2018/05/23 v4

# gpg: Signature made Sat 26 May 2018 03:52:12 BST
# gpg:                using RSA key 75AD65802A0B4211
# gpg: Good signature from "Stefan Berger <stefanb@linux.vnet.ibm.com>"
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: B818 B9CA DF90 89C2 D5CE  C66B 75AD 6580 2A0B 4211

* remotes/stefanberger/tags/pull-tpm-2018-05-23-4:
  test: Add test cases that use the external swtpm with CRB interface
  docs: tpm: add VM save/restore example and troubleshooting guide
  tpm: extend TPM TIS with state migration support
  tpm: extend TPM emulator with state migration support

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2018-05-29 09:57:09 +01:00
commit b706ec24f1
8 changed files with 948 additions and 14 deletions

View File

@ -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 ... )

View File

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

View File

@ -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[] = {

View File

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

View File

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

247
tests/tpm-crb-swtpm-test.c Normal file
View File

@ -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;
}

186
tests/tpm-util.c Normal file
View File

@ -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);
}

36
tests/tpm-util.h Normal file
View File

@ -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 */