[libvirt] [PATCH v9 06/17] qemu: Extend QEMU with external TPM support

Stefan Berger stefanb at linux.vnet.ibm.com
Mon Jun 4 15:46:45 UTC 2018


Implement functions for managing the storage of the external swtpm as well
as starting and stopping it. Also implement functions to use swtpm_setup,
which simulates the manufacturing of a TPM, which includes creation of
certificates for the device.

Further, the external TPM needs storage on the host that we need to set
up before it can be run. We can clean up the host once the domain is
undefined.

This patch also implements a small layer for external device support that
calls into the TPM device layer if a domain has an attached TPM. This is
the layer we will wire up later on.

Signed-off-by: Stefan Berger <stefanb at linux.vnet.ibm.com>
Reviewed-by: John Ferlan <jferlan at redhat.com>
Reviewed-by: Ján Tomko <jtomko at redhat.com>
---
 src/qemu/Makefile.inc.am  |   4 +
 src/qemu/qemu_domain.c    |   2 +
 src/qemu/qemu_extdevice.c | 154 ++++++++++
 src/qemu/qemu_extdevice.h |  53 ++++
 src/qemu/qemu_process.c   |  12 +
 src/qemu/qemu_tpm.c       | 746 ++++++++++++++++++++++++++++++++++++++++++++++
 src/qemu/qemu_tpm.h       |  50 ++++
 7 files changed, 1021 insertions(+)
 create mode 100644 src/qemu/qemu_extdevice.c
 create mode 100644 src/qemu/qemu_extdevice.h
 create mode 100644 src/qemu/qemu_tpm.c
 create mode 100644 src/qemu/qemu_tpm.h

diff --git a/src/qemu/Makefile.inc.am b/src/qemu/Makefile.inc.am
index 7f50501f18..46797af4be 100644
--- a/src/qemu/Makefile.inc.am
+++ b/src/qemu/Makefile.inc.am
@@ -19,6 +19,8 @@ QEMU_DRIVER_SOURCES = \
 	qemu/qemu_domain_address.h \
 	qemu/qemu_cgroup.c \
 	qemu/qemu_cgroup.h \
+	qemu/qemu_extdevice.c \
+	qemu/qemu_extdevice.h \
 	qemu/qemu_hostdev.c \
 	qemu/qemu_hostdev.h \
 	qemu/qemu_hotplug.c \
@@ -51,6 +53,8 @@ QEMU_DRIVER_SOURCES = \
 	qemu/qemu_security.h \
 	qemu/qemu_qapi.c \
 	qemu/qemu_qapi.h \
+	qemu/qemu_tpm.c \
+	qemu/qemu_tpm.h \
 	$(NULL)
 
 
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index e764687a85..642c023417 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -34,6 +34,7 @@
 #include "qemu_migration.h"
 #include "qemu_migration_params.h"
 #include "qemu_security.h"
+#include "qemu_extdevice.h"
 #include "viralloc.h"
 #include "virlog.h"
 #include "virerror.h"
@@ -7523,6 +7524,7 @@ qemuDomainRemoveInactive(virQEMUDriverPtr driver,
             VIR_WARN("unable to remove snapshot directory %s", snapDir);
         VIR_FREE(snapDir);
     }
+    qemuExtDevicesCleanupHost(driver, vm->def);
 
     virDomainObjListRemove(driver->domains, vm);
 
diff --git a/src/qemu/qemu_extdevice.c b/src/qemu/qemu_extdevice.c
new file mode 100644
index 0000000000..790b19be9e
--- /dev/null
+++ b/src/qemu/qemu_extdevice.c
@@ -0,0 +1,154 @@
+/*
+ * qemu_extdevice.c: QEMU external devices support
+ *
+ * Copyright (C) 2014, 2018 IBM Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stefan Berger <stefanb at linux.vnet.ibm.com>
+ */
+
+#include <config.h>
+
+#include "qemu_extdevice.h"
+#include "qemu_domain.h"
+#include "qemu_tpm.h"
+
+#include "viralloc.h"
+#include "virlog.h"
+#include "virstring.h"
+#include "virtime.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_extdevice")
+
+int
+qemuExtDeviceLogCommand(qemuDomainLogContextPtr logCtxt,
+                        virCommandPtr cmd,
+                        const char *info)
+{
+    int ret = -1;
+    char *timestamp = NULL;
+    char *logline = NULL;
+    int logFD;
+
+    logFD = qemuDomainLogContextGetWriteFD(logCtxt);
+
+    if ((timestamp = virTimeStringNow()) == NULL)
+        goto cleanup;
+
+    if (virAsprintf(&logline, "%s: Starting external device: %s\n",
+                    timestamp, info) < 0)
+        goto cleanup;
+
+    if (safewrite(logFD, logline, strlen(logline)) < 0)
+        goto cleanup;
+
+    virCommandWriteArgLog(cmd, logFD);
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(timestamp);
+    VIR_FREE(logline);
+
+    return ret;
+}
+
+
+/*
+ * qemuExtDevicesInitPaths:
+ *
+ * @driver: QEMU driver
+ * @def: domain definition
+ *
+ * Initialize paths of external devices so that it is known where state is
+ * stored and we can remove directories and files in case of domain XML
+ * changes.
+ */
+static int
+qemuExtDevicesInitPaths(virQEMUDriverPtr driver,
+                        virDomainDefPtr def)
+{
+    int ret = 0;
+
+    if (def->tpm)
+        ret = qemuExtTPMInitPaths(driver, def);
+
+    return ret;
+}
+
+
+/*
+ * qemuExtDevicesPrepareHost:
+ *
+ * @driver: QEMU driver
+ * @def: domain definition
+ *
+ * Prepare host storage paths for external devices.
+ */
+int
+qemuExtDevicesPrepareHost(virQEMUDriverPtr driver,
+                          virDomainDefPtr def)
+{
+    int ret = 0;
+
+    if (def->tpm)
+        ret = qemuExtTPMPrepareHost(driver, def);
+
+    return ret;
+}
+
+
+void
+qemuExtDevicesCleanupHost(virQEMUDriverPtr driver,
+                          virDomainDefPtr def)
+{
+    if (qemuExtDevicesInitPaths(driver, def) < 0)
+        return;
+
+    if (def->tpm)
+        qemuExtTPMCleanupHost(def);
+}
+
+
+int
+qemuExtDevicesStart(virQEMUDriverPtr driver,
+                    virDomainDefPtr def,
+                    qemuDomainLogContextPtr logCtxt)
+{
+    int ret = 0;
+
+    if (qemuExtDevicesInitPaths(driver, def) < 0)
+        return -1;
+
+    if (def->tpm)
+        ret = qemuExtTPMStart(driver, def, logCtxt);
+
+    return ret;
+}
+
+
+void
+qemuExtDevicesStop(virQEMUDriverPtr driver,
+                   virDomainDefPtr def)
+{
+    if (qemuExtDevicesInitPaths(driver, def) < 0)
+        return;
+
+    if (def->tpm)
+        qemuExtTPMStop(driver, def);
+}
diff --git a/src/qemu/qemu_extdevice.h b/src/qemu/qemu_extdevice.h
new file mode 100644
index 0000000000..6de858b2a3
--- /dev/null
+++ b/src/qemu/qemu_extdevice.h
@@ -0,0 +1,53 @@
+/*
+ * qemu_extdevice.h: QEMU external devices support
+ *
+ * Copyright (C) 2018 IBM Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stefan Berger <stefanb at linux.vnet.ibm.com>
+ */
+#ifndef __QEMU_EXTDEVICE_H__
+# define __QEMU_EXTDEVICE_H__
+
+# include "qemu_conf.h"
+# include "qemu_domain.h"
+
+int qemuExtDeviceLogCommand(qemuDomainLogContextPtr logCtxt,
+                            virCommandPtr cmd,
+                            const char *info)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+    ATTRIBUTE_RETURN_CHECK;
+
+int qemuExtDevicesPrepareHost(virQEMUDriverPtr driver,
+                              virDomainDefPtr def)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2)
+    ATTRIBUTE_RETURN_CHECK;
+
+void qemuExtDevicesCleanupHost(virQEMUDriverPtr driver,
+                               virDomainDefPtr def)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+int qemuExtDevicesStart(virQEMUDriverPtr driver,
+                        virDomainDefPtr def,
+                        qemuDomainLogContextPtr logCtxt)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+    ATTRIBUTE_RETURN_CHECK;
+
+void qemuExtDevicesStop(virQEMUDriverPtr driver,
+                        virDomainDefPtr def)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+#endif /* __QEMU_EXTDEVICE_H__ */
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 30cc5904e0..bda226345f 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -50,6 +50,7 @@
 #include "qemu_migration_params.h"
 #include "qemu_interface.h"
 #include "qemu_security.h"
+#include "qemu_extdevice.h"
 
 #include "cpu/cpu.h"
 #include "datatypes.h"
@@ -6078,6 +6079,10 @@ qemuProcessPrepareHost(virQEMUDriverPtr driver,
     if (qemuProcessPrepareHostStorage(driver, vm, flags) < 0)
         goto cleanup;
 
+    VIR_DEBUG("Preparing external devices");
+    if (qemuExtDevicesPrepareHost(driver, vm->def) < 0)
+        goto cleanup;
+
     ret = 0;
  cleanup:
     virObjectUnref(cfg);
@@ -6198,6 +6203,9 @@ qemuProcessLaunch(virConnectPtr conn,
     if (qemuProcessGenID(vm, flags) < 0)
         goto cleanup;
 
+    if (qemuExtDevicesStart(driver, vm->def, logCtxt) < 0)
+        goto cleanup;
+
     VIR_DEBUG("Building emulator command line");
     if (!(cmd = qemuBuildCommandLine(driver,
                                      qemuDomainLogContextGetManager(logCtxt),
@@ -6442,6 +6450,8 @@ qemuProcessLaunch(virConnectPtr conn,
     ret = 0;
 
  cleanup:
+    if (ret < 0)
+        qemuExtDevicesStop(driver, vm->def);
     qemuDomainSecretDestroy(vm);
     virCommandFree(cmd);
     virObjectUnref(logCtxt);
@@ -6866,6 +6876,8 @@ void qemuProcessStop(virQEMUDriverPtr driver,
 
     qemuDomainCleanupRun(driver, vm);
 
+    qemuExtDevicesStop(driver, vm->def);
+
     /* Stop autodestroy in case guest is restarted */
     qemuProcessAutoDestroyRemove(driver, vm);
 
diff --git a/src/qemu/qemu_tpm.c b/src/qemu/qemu_tpm.c
new file mode 100644
index 0000000000..69d7698198
--- /dev/null
+++ b/src/qemu/qemu_tpm.c
@@ -0,0 +1,746 @@
+/*
+ * qemu_tpm.c: QEMU TPM support
+ *
+ * Copyright (C) 2018 IBM Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stefan Berger <stefanb at linux.vnet.ibm.com>
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <cap-ng.h>
+
+#include "qemu_extdevice.h"
+#include "qemu_domain.h"
+
+#include "conf/domain_conf.h"
+#include "vircommand.h"
+#include "viralloc.h"
+#include "virkmod.h"
+#include "virlog.h"
+#include "virutil.h"
+#include "viruuid.h"
+#include "virfile.h"
+#include "virstring.h"
+#include "configmake.h"
+#include "dirname.h"
+#include "qemu_tpm.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+VIR_LOG_INIT("qemu.tpm")
+
+/*
+ * executables for the swtpm; to be found on the host
+ */
+static char *swtpm_path;
+static char *swtpm_setup;
+static char *swtpm_ioctl;
+
+/*
+ * qemuTPMEmulatorInit
+ *
+ * Initialize the Emulator functions by searching for necessary
+ * executables that we will use to start and setup the swtpm
+ */
+static int
+qemuTPMEmulatorInit(void)
+{
+    if (!swtpm_path) {
+        swtpm_path = virFindFileInPath("swtpm");
+        if (!swtpm_path) {
+            virReportSystemError(ENOENT, "%s",
+                                 _("Unable to find 'swtpm' binary in $PATH"));
+            return -1;
+        }
+        if (!virFileIsExecutable(swtpm_path)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("TPM emulator %s is not an executable"),
+                           swtpm_path);
+            VIR_FREE(swtpm_path);
+            return -1;
+        }
+    }
+
+    if (!swtpm_setup) {
+        swtpm_setup = virFindFileInPath("swtpm_setup");
+        if (!swtpm_setup) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("Could not find 'swtpm_setup' in PATH"));
+            return -1;
+        }
+        if (!virFileIsExecutable(swtpm_setup)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("'%s' is not an executable"),
+                           swtpm_setup);
+            VIR_FREE(swtpm_setup);
+            return -1;
+        }
+    }
+
+    if (!swtpm_ioctl) {
+        swtpm_ioctl = virFindFileInPath("swtpm_ioctl");
+        if (!swtpm_ioctl) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("Could not find swtpm_ioctl in PATH"));
+            return -1;
+        }
+        if (!virFileIsExecutable(swtpm_ioctl)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("swtpm_ioctl program %s is not an executable"),
+                           swtpm_ioctl);
+            VIR_FREE(swtpm_ioctl);
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ * qemuTPMCreateEmulatorStoragePath
+ *
+ * @swtpmStorageDir: directory for swtpm persistent state
+ * @uuid: The UUID of the VM for which to create the storage
+ *
+ * Create the swtpm's storage path
+ */
+static char *
+qemuTPMCreateEmulatorStoragePath(const char *swtpmStorageDir,
+                                 const char *uuidstr)
+{
+    char *path = NULL;
+
+    ignore_value(virAsprintf(&path, "%s/%s/tpm1.2", swtpmStorageDir, uuidstr));
+
+    return path;
+}
+
+
+/*
+ * virtTPMGetTPMStorageDir:
+ *
+ * @storagepath: directory for swtpm's persistent state
+ *
+ * Derive the 'TPMStorageDir' from the storagepath by searching
+ * for the last '/'.
+ */
+static char *
+qemuTPMGetTPMStorageDir(const char *storagepath)
+{
+    char *ret = mdir_name(storagepath);
+
+    if (!ret)
+        virReportOOMError();
+
+    return ret;
+}
+
+
+/*
+ * qemuTPMEmulatorInitStorage
+ *
+ * Initialize the TPM Emulator storage by creating its root directory,
+ * which is typically found in /var/lib/libvirt/tpm.
+ *
+ */
+static int
+qemuTPMEmulatorInitStorage(const char *swtpmStorageDir)
+{
+    int rc = 0;
+
+    /* allow others to cd into this dir */
+    if (virFileMakePathWithMode(swtpmStorageDir, 0711) < 0) {
+        virReportSystemError(errno,
+                             _("Could not create TPM directory %s"),
+                             swtpmStorageDir);
+        rc = -1;
+    }
+
+    return rc;
+}
+
+
+/*
+ * qemuTPMCreateEmulatorStorage
+ *
+ * @storagepath: directory for swtpm's persistent state
+ * @created: a pointer to a bool that will be set to true if the
+ *           storage was created because it did not exist yet
+ * @swtpm_user: The uid that needs to be able to access the directory
+ * @swtpm_group: The gid that needs to be able to access the directory
+ *
+ * Unless the storage path for the swtpm for the given VM
+ * already exists, create it and make it accessible for the given userid.
+ * Adapt ownership of the directory and all swtpm's state files there.
+ */
+static int
+qemuTPMCreateEmulatorStorage(const char *storagepath,
+                             bool *created,
+                             uid_t swtpm_user,
+                             gid_t swtpm_group)
+{
+    int ret = -1;
+    char *swtpmStorageDir = qemuTPMGetTPMStorageDir(storagepath);
+
+    if (!swtpmStorageDir)
+        return -1;
+
+    if (qemuTPMEmulatorInitStorage(swtpmStorageDir) < 0)
+        goto cleanup;
+
+    *created = false;
+
+    if (!virFileExists(storagepath))
+        *created = true;
+
+    if (virDirCreate(storagepath, 0700, swtpm_user, swtpm_group,
+                     VIR_DIR_CREATE_ALLOW_EXIST) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Could not create directory %s as %u:%d"),
+                       storagepath, swtpm_user, swtpm_group);
+        goto cleanup;
+    }
+
+    if (virFileChownFiles(storagepath, swtpm_user, swtpm_group) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(swtpmStorageDir);
+
+    return ret;
+}
+
+
+static void
+qemuTPMDeleteEmulatorStorage(virDomainTPMDefPtr tpm)
+{
+    char *path = qemuTPMGetTPMStorageDir(tpm->data.emulator.storagepath);
+
+    if (path) {
+        ignore_value(virFileDeleteTree(path));
+        VIR_FREE(path);
+    }
+}
+
+
+/*
+ * qemuTPMCreateEmulatorSocket:
+ *
+ * @swtpmStateDir: the directory where to create the socket in
+ * @shortName: short and unique name of the domain
+ *
+ * Create the Unix socket path from the given parameters
+ */
+static char *
+qemuTPMCreateEmulatorSocket(const char *swtpmStateDir,
+                            const char *shortName)
+{
+    char *path = NULL;
+
+    ignore_value(virAsprintf(&path, "%s/%s-swtpm.sock", swtpmStateDir,
+                             shortName));
+
+    return path;
+}
+
+
+/*
+ * qemuTPMEmulatorInitPaths:
+ *
+ * @tpm: TPM definition for an emulator type
+ * @swtpmStorageDir: the general swtpm storage dir which is used as a base
+ *                   directory for creating VM specific directories
+ * @uuid: the UUID of the VM
+ */
+static int
+qemuTPMEmulatorInitPaths(virDomainTPMDefPtr tpm,
+                         const char *swtpmStorageDir,
+                         const unsigned char *uuid)
+{
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+
+    virUUIDFormat(uuid, uuidstr);
+
+    if (!tpm->data.emulator.storagepath &&
+        !(tpm->data.emulator.storagepath =
+            qemuTPMCreateEmulatorStoragePath(swtpmStorageDir, uuidstr)))
+        return -1;
+
+    return 0;
+}
+
+
+/*
+ * qemuTPMEmulatorPrepareHost:
+ *
+ * @tpm: tpm definition
+ * @logDir: directory where swtpm writes its logs into
+ * @vmname: name of the VM
+ * @swtpm_user: uid to run the swtpm with
+ * @swtpm_group: gid to run the swtpm with
+ * @swtpmStateDir: directory for swtpm's persistent state
+ * @qemu_user: uid that qemu will run with; we share the socket file with it
+ * @shortName: short and unique name of the domain
+ *
+ * Prepare the log directory for the swtpm and adjust ownership of it and the
+ * log file we will be using. Prepare the state directory where we will share
+ * the socket between tss and qemu users.
+ */
+static int
+qemuTPMEmulatorPrepareHost(virDomainTPMDefPtr tpm,
+                           const char *logDir,
+                           const char *vmname,
+                           uid_t swtpm_user,
+                           gid_t swtpm_group,
+                           const char *swtpmStateDir,
+                           uid_t qemu_user,
+                           const char *shortName)
+{
+    int ret = -1;
+
+    if (qemuTPMEmulatorInit() < 0)
+        return -1;
+
+    /* create log dir ... allow 'tss' user to cd into it */
+    if (virFileMakePathWithMode(logDir, 0711) < 0)
+        return -1;
+
+    /* ... and adjust ownership */
+    if (virDirCreate(logDir, 0730, swtpm_user, swtpm_group,
+                     VIR_DIR_CREATE_ALLOW_EXIST) < 0)
+        goto cleanup;
+
+    /* create logfile name ... */
+    if (!tpm->data.emulator.logfile &&
+        virAsprintf(&tpm->data.emulator.logfile, "%s/%s-swtpm.log",
+                    logDir, vmname) < 0)
+        goto cleanup;
+
+    /* ... and make sure it can be accessed by swtpm_user */
+    if (virFileExists(tpm->data.emulator.logfile) &&
+        chown(tpm->data.emulator.logfile, swtpm_user, swtpm_group) < 0) {
+        virReportSystemError(errno,
+                             _("Could not chown on swtpm logfile %s"),
+                             tpm->data.emulator.logfile);
+        goto cleanup;
+    }
+
+    /*
+      create our swtpm state dir ...
+      - QEMU user needs to be able to access the socket there
+      - swtpm group needs to be able to create files there
+      - in privileged mode 0570 would be enough, for non-privileged mode
+        we need 0770
+    */
+    if (virDirCreate(swtpmStateDir, 0770, qemu_user, swtpm_group,
+                     VIR_DIR_CREATE_ALLOW_EXIST) < 0)
+        goto cleanup;
+
+    /* create the socket filename */
+    if (!tpm->data.emulator.source.data.nix.path &&
+        !(tpm->data.emulator.source.data.nix.path =
+          qemuTPMCreateEmulatorSocket(swtpmStateDir, shortName)))
+        goto cleanup;
+    tpm->data.emulator.source.type = VIR_DOMAIN_CHR_TYPE_UNIX;
+
+    ret = 0;
+
+ cleanup:
+
+    return ret;
+}
+
+
+/*
+ * qemuTPMEmulatorRunSetup
+ *
+ * @storagepath: path to the directory for TPM state
+ * @vmname: the name of the VM
+ * @vmuuid: the UUID of the VM
+ * @privileged: whether we are running in privileged mode
+ * @swtpm_user: The userid to switch to when setting up the TPM;
+ *              typically this should be the uid of 'tss' or 'root'
+ * @swtpm_group: The group id to switch to
+ * @logfile: The file to write the log into; it must be writable
+ *           for the user given by userid or 'tss'
+ *
+ * Setup the external swtpm by creating endorsement key and
+ * certificates for it.
+ */
+static int
+qemuTPMEmulatorRunSetup(const char *storagepath,
+                        const char *vmname,
+                        const unsigned char *vmuuid,
+                        bool privileged,
+                        uid_t swtpm_user,
+                        gid_t swtpm_group,
+                        const char *logfile)
+{
+    virCommandPtr cmd = NULL;
+    int exitstatus;
+    int ret = -1;
+    char uuid[VIR_UUID_STRING_BUFLEN];
+    char *vmid = NULL;
+
+    if (!privileged)
+        return virFileWriteStr(logfile,
+                               _("Did not create EK and certificates since "
+                               "this requires privileged mode\n"),
+                               0600);
+
+    cmd = virCommandNew(swtpm_setup);
+    if (!cmd)
+        goto cleanup;
+
+    virUUIDFormat(vmuuid, uuid);
+    if (virAsprintf(&vmid, "%s:%s", vmname, uuid) < 0)
+        goto cleanup;
+
+    virCommandSetUID(cmd, swtpm_user);
+    virCommandSetGID(cmd, swtpm_group);
+
+    virCommandAddArgList(cmd,
+                         "--tpm-state", storagepath,
+                         "--vmid", vmid,
+                         "--logfile", logfile,
+                         "--createek",
+                         "--create-ek-cert",
+                         "--create-platform-cert",
+                         "--lock-nvram",
+                         "--not-overwrite",
+                         NULL);
+
+    virCommandClearCaps(cmd);
+
+    if (virCommandRun(cmd, &exitstatus) < 0 || exitstatus != 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Could not run '%s'. exitstatus: %d; "
+                         "Check error log '%s' for details."),
+                          swtpm_setup, exitstatus, logfile);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(vmid);
+    virCommandFree(cmd);
+
+    return ret;
+}
+
+
+/*
+ * qemuTPMEmulatorBuildCommand:
+ *
+ * @tpm: TPM definition
+ * @vmname: The name of the VM
+ * @vmuuid: The UUID of the VM
+ * @privileged: whether we are running in privileged mode
+ * @swtpm_user: The uid for the swtpm to run as (drop privileges to from root)
+ * @swtpm_group: The gid for the swtpm to run as
+ *
+ * Create the virCommand use for starting the emulator
+ * Do some initializations on the way, such as creation of storage
+ * and emulator setup.
+ */
+static virCommandPtr
+qemuTPMEmulatorBuildCommand(virDomainTPMDefPtr tpm,
+                            const char *vmname,
+                            const unsigned char *vmuuid,
+                            bool privileged,
+                            uid_t swtpm_user,
+                            gid_t swtpm_group)
+{
+    virCommandPtr cmd = NULL;
+    bool created = false;
+
+    if (qemuTPMCreateEmulatorStorage(tpm->data.emulator.storagepath,
+                                     &created, swtpm_user, swtpm_group) < 0)
+        return NULL;
+
+    if (created &&
+        qemuTPMEmulatorRunSetup(tpm->data.emulator.storagepath, vmname, vmuuid,
+                                privileged, swtpm_user, swtpm_group,
+                                tpm->data.emulator.logfile) < 0)
+        goto error;
+
+    unlink(tpm->data.emulator.source.data.nix.path);
+
+    cmd = virCommandNew(swtpm_path);
+    if (!cmd)
+        goto error;
+
+    virCommandClearCaps(cmd);
+
+    virCommandAddArgList(cmd, "socket", "--daemon", "--ctrl", NULL);
+    virCommandAddArgFormat(cmd, "type=unixio,path=%s,mode=0600",
+                           tpm->data.emulator.source.data.nix.path);
+
+    virCommandAddArg(cmd, "--tpmstate");
+    virCommandAddArgFormat(cmd, "dir=%s,mode=0600",
+                           tpm->data.emulator.storagepath);
+
+    virCommandAddArg(cmd, "--log");
+    virCommandAddArgFormat(cmd, "file=%s", tpm->data.emulator.logfile);
+
+    virCommandSetUID(cmd, swtpm_user);
+    virCommandSetGID(cmd, swtpm_group);
+
+    return cmd;
+
+ error:
+    if (created)
+        qemuTPMDeleteEmulatorStorage(tpm);
+
+    virCommandFree(cmd);
+
+    return NULL;
+}
+
+
+/*
+ * qemuTPMEmulatorStop
+ * @swtpmStateDir: A directory where the socket is located
+ * @shortName: short and unique name of the domain
+ *
+ * Gracefully stop the swptm
+ */
+static void
+qemuTPMEmulatorStop(const char *swtpmStateDir,
+                    const char *shortName)
+{
+    virCommandPtr cmd;
+    char *pathname;
+    char *errbuf = NULL;
+
+    if (qemuTPMEmulatorInit() < 0)
+        return;
+
+    if (!(pathname = qemuTPMCreateEmulatorSocket(swtpmStateDir, shortName)))
+        return;
+
+    if (!virFileExists(pathname))
+        goto cleanup;
+
+    cmd = virCommandNew(swtpm_ioctl);
+    if (!cmd)
+        goto cleanup;
+
+    virCommandAddArgList(cmd, "--unix", pathname, "-s", NULL);
+
+    virCommandSetErrorBuffer(cmd, &errbuf);
+
+    ignore_value(virCommandRun(cmd, NULL));
+
+    virCommandFree(cmd);
+
+    /* clean up the socket */
+    unlink(pathname);
+
+ cleanup:
+    VIR_FREE(pathname);
+    VIR_FREE(errbuf);
+}
+
+
+int
+qemuExtTPMInitPaths(virQEMUDriverPtr driver,
+                    virDomainDefPtr def)
+{
+    virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
+    int ret = 0;
+
+    switch (def->tpm->type) {
+    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
+        ret = qemuTPMEmulatorInitPaths(def->tpm, cfg->swtpmStorageDir,
+                                       def->uuid);
+        break;
+    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
+    case VIR_DOMAIN_TPM_TYPE_LAST:
+        break;
+    }
+
+    virObjectUnref(cfg);
+
+    return ret;
+}
+
+
+int
+qemuExtTPMPrepareHost(virQEMUDriverPtr driver,
+                      virDomainDefPtr def)
+{
+    virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
+    int ret = 0;
+    char *shortName = NULL;
+
+    switch (def->tpm->type) {
+    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
+        shortName = virDomainDefGetShortName(def);
+        if (!shortName)
+            goto cleanup;
+
+        ret = qemuTPMEmulatorPrepareHost(def->tpm, cfg->swtpmLogDir,
+                                         def->name, cfg->swtpm_user,
+                                         cfg->swtpm_group,
+                                         cfg->swtpmStateDir, cfg->user,
+                                         shortName);
+        break;
+    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
+    case VIR_DOMAIN_TPM_TYPE_LAST:
+        break;
+    }
+
+ cleanup:
+    VIR_FREE(shortName);
+    virObjectUnref(cfg);
+
+    return ret;
+}
+
+
+void
+qemuExtTPMCleanupHost(virDomainDefPtr def)
+{
+    switch (def->tpm->type) {
+    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
+        qemuTPMDeleteEmulatorStorage(def->tpm);
+        break;
+    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
+    case VIR_DOMAIN_TPM_TYPE_LAST:
+        /* nothing to do */
+        break;
+    }
+}
+
+
+/*
+ * qemuExtTPMStartEmulator:
+ *
+ * @driver: QEMU driver
+ * @def: domain definition
+ * @logCtxt: log context
+ *
+ * Start the external TPM Emulator:
+ * - have the command line built
+ * - start the external TPM Emulator and sync with it before QEMU start
+ */
+static int
+qemuExtTPMStartEmulator(virQEMUDriverPtr driver,
+                        virDomainDefPtr def,
+                        qemuDomainLogContextPtr logCtxt)
+{
+    int ret = -1;
+    virCommandPtr cmd = NULL;
+    int exitstatus;
+    char *errbuf = NULL;
+    virQEMUDriverConfigPtr cfg;
+    virDomainTPMDefPtr tpm = def->tpm;
+    char *shortName = virDomainDefGetShortName(def);
+
+    if (!shortName)
+        return -1;
+
+    cfg = virQEMUDriverGetConfig(driver);
+
+    /* stop any left-over TPM emulator for this VM */
+    qemuTPMEmulatorStop(cfg->swtpmStateDir, shortName);
+
+    if (!(cmd = qemuTPMEmulatorBuildCommand(tpm, def->name, def->uuid,
+                                            driver->privileged,
+                                            cfg->swtpm_user,
+                                            cfg->swtpm_group)))
+        goto cleanup;
+
+    if (qemuExtDeviceLogCommand(logCtxt, cmd, "TPM Emulator") < 0)
+        goto cleanup;
+
+    virCommandSetErrorBuffer(cmd, &errbuf);
+
+    if (virCommandRun(cmd, &exitstatus) < 0 || exitstatus != 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Could not start 'swtpm'. exitstatus: %d, "
+                         "error: %s"), exitstatus, errbuf);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(shortName);
+    VIR_FREE(errbuf);
+    virCommandFree(cmd);
+
+    virObjectUnref(cfg);
+
+    return ret;
+}
+
+
+int
+qemuExtTPMStart(virQEMUDriverPtr driver,
+                virDomainDefPtr def,
+                qemuDomainLogContextPtr logCtxt)
+{
+    int ret = 0;
+    virDomainTPMDefPtr tpm = def->tpm;
+
+    switch (tpm->type) {
+    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
+        ret = qemuExtTPMStartEmulator(driver, def, logCtxt);
+        break;
+    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
+    case VIR_DOMAIN_TPM_TYPE_LAST:
+        break;
+    }
+
+    return ret;
+}
+
+
+void
+qemuExtTPMStop(virQEMUDriverPtr driver,
+               virDomainDefPtr def)
+{
+    virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
+    char *shortName = NULL;
+
+    switch (def->tpm->type) {
+    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
+        shortName = virDomainDefGetShortName(def);
+        if (!shortName)
+            goto cleanup;
+
+        qemuTPMEmulatorStop(cfg->swtpmStateDir, shortName);
+        break;
+    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
+    case VIR_DOMAIN_TPM_TYPE_LAST:
+        break;
+    }
+
+ cleanup:
+    VIR_FREE(shortName);
+    virObjectUnref(cfg);
+}
diff --git a/src/qemu/qemu_tpm.h b/src/qemu/qemu_tpm.h
new file mode 100644
index 0000000000..20f3a9ccc4
--- /dev/null
+++ b/src/qemu/qemu_tpm.h
@@ -0,0 +1,50 @@
+/*
+ * qemu_tpm.h: QEMU TPM support
+ *
+ * Copyright (C) 2018 IBM Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stefan Berger <stefanb at linux.vnet.ibm.com>
+ */
+#ifndef __QEMU_TPM_H__
+# define __QEMU_TPM_H__
+
+# include "vircommand.h"
+
+int qemuExtTPMInitPaths(virQEMUDriverPtr driver,
+                        virDomainDefPtr def)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2)
+    ATTRIBUTE_RETURN_CHECK;
+
+int qemuExtTPMPrepareHost(virQEMUDriverPtr driver,
+                          virDomainDefPtr def)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2)
+    ATTRIBUTE_RETURN_CHECK;
+
+void qemuExtTPMCleanupHost(virDomainDefPtr def)
+    ATTRIBUTE_NONNULL(1);
+
+int qemuExtTPMStart(virQEMUDriverPtr driver,
+                    virDomainDefPtr def,
+                    qemuDomainLogContextPtr logCtxt)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+    ATTRIBUTE_RETURN_CHECK;
+
+void qemuExtTPMStop(virQEMUDriverPtr driver,
+                    virDomainDefPtr def)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+#endif /* __QEMU_TPM_H__ */
-- 
2.14.3




More information about the libvir-list mailing list