[libvirt] [PATCH v3 07/14] util: Extend virtpm.c with tpm-emulator support

Stefan Berger stefanb at linux.vnet.ibm.com
Fri May 4 20:21:18 UTC 2018


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

Signed-off-by: Stefan Berger <stefanb at linux.vnet.ibm.com>
---
 src/libvirt_private.syms |   5 +
 src/util/virtpm.c        | 536 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/util/virtpm.h        |  33 ++-
 3 files changed, 572 insertions(+), 2 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 33fe75b..eebfc72 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2984,6 +2984,11 @@ virTimeStringThenRaw;
 
 # util/virtpm.h
 virTPMCreateCancelPath;
+virTPMDeleteEmulatorStorage;
+virTPMEmulatorBuildCommand;
+virTPMEmulatorInitPaths;
+virTPMEmulatorPrepareHost;
+virTPMEmulatorStop;
 
 
 # util/virtypedparam.h
diff --git a/src/util/virtpm.c b/src/util/virtpm.c
index d5c10da..76bbb21 100644
--- a/src/util/virtpm.c
+++ b/src/util/virtpm.c
@@ -1,7 +1,7 @@
 /*
  * virtpm.c: TPM support
  *
- * Copyright (C) 2013 IBM Corporation
+ * Copyright (C) 2013,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
@@ -22,16 +22,36 @@
 
 #include <config.h>
 
+#include <sys/types.h>
 #include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <cap-ng.h>
 
+#include "conf/domain_conf.h"
+#include "viralloc.h"
+#include "vircommand.h"
 #include "virstring.h"
 #include "virerror.h"
 #include "viralloc.h"
 #include "virfile.h"
+#include "virkmod.h"
+#include "virlog.h"
 #include "virtpm.h"
+#include "virutil.h"
+#include "configmake.h"
 
 #define VIR_FROM_THIS VIR_FROM_NONE
 
+VIR_LOG_INIT("util.tpm")
+
+/*
+ * executables for the swtpm; to be found on the host
+ */
+static char *swtpm_path;
+static char *swtpm_setup;
+static char *swtpm_ioctl;
+
 /**
  * virTPMCreateCancelPath:
  * @devpath: Path to the TPM device
@@ -74,3 +94,517 @@ virTPMCreateCancelPath(const char *devpath)
  cleanup:
     return path;
 }
+
+/*
+ * virTPMEmulatorInit
+ *
+ * Initialize the Emulator functions by searching for necessary
+ * executables that we will use to start and setup the swtpm
+ */
+static int
+virTPMEmulatorInit(void)
+{
+    if (!swtpm_path) {
+        swtpm_path = virFindFileInPath("swtpm");
+        if (!swtpm_path) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("Could not find swtpm 'swtpm' 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;
+}
+
+/*
+ * virTPMCreateEmulatorStoragePath
+ *
+ * @swtpmStorageDir: directory for swtpm persistent state
+ * @vmname: The name of the VM for which to create the storage
+ *
+ * Create the swtpm's storage path
+ */
+static char *
+virTPMCreateEmulatorStoragePath(const char *swtpmStorageDir,
+                                const char *vmname)
+{
+    char *path = NULL;
+
+    ignore_value(virAsprintf(&path, "%s/%s/tpm1.2", swtpmStorageDir, vmname));
+
+    return path;
+}
+
+/*
+ * virtTPMGetTPMStorageDir:
+ *
+ * @storagepath: directory for swtpm's pesistent state
+ *
+ * Derive the 'TPMStorageDir' from the storagepath by searching
+ * for the last '/'.
+ */
+static char *
+virTPMGetTPMStorageDir(const char *storagepath)
+{
+    const char *tail = strrchr(storagepath, '/');
+    char *path = NULL;
+
+    if (!tail) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Could not get tail of storagedir %s"),
+                       storagepath);
+        return NULL;
+    }
+    ignore_value(VIR_STRNDUP(path, storagepath, tail - storagepath));
+
+    return path;
+}
+
+/*
+ * virTPMEmulatorInitStorage
+ *
+ * Initialize the TPM Emulator storage by creating its root directory,
+ * which is typically found in /var/lib/libvirt/tpm.
+ *
+ */
+static int
+virTPMEmulatorInitStorage(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;
+}
+
+/*
+ * virTPMCreateEmulatorStorage
+ *
+ * @storagepath: directory for swtpm's pesistent 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
+virTPMCreateEmulatorStorage(const char *storagepath,
+                            bool *created,
+                            uid_t swtpm_user, gid_t swtpm_group)
+{
+    int ret = -1;
+    char *swtpmStorageDir = virTPMGetTPMStorageDir(storagepath);
+
+    if (!swtpmStorageDir)
+        return -1;
+
+    if (virTPMEmulatorInitStorage(swtpmStorageDir) < 0)
+        return -1;
+
+    *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;
+}
+
+void
+virTPMDeleteEmulatorStorage(virDomainTPMDefPtr tpm)
+{
+    char *path = virTPMGetTPMStorageDir(tpm->data.emulator.storagepath);
+
+    if (path) {
+        ignore_value(virFileDeleteTree(path));
+        VIR_FREE(path);
+    }
+}
+
+/*
+ * virTPMCreateEmulatorSocket:
+ *
+ * @swtpmStateDir: the directory where to create the socket in
+ * @shortName: short and unique name of the domain
+ *
+ * Create the vTPM device name from the given parameters
+ */
+static char *
+virTPMCreateEmulatorSocket(const char *swtpmStateDir, const char *shortName)
+{
+    char *path = NULL;
+
+    ignore_value(virAsprintf(&path, "%s/%s-swtpm.sock", swtpmStateDir,
+                             shortName));
+
+    return path;
+}
+
+/*
+ * virTPMEmulatorInitPaths:
+ *
+ * @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
+ */
+int virTPMEmulatorInitPaths(virDomainTPMDefPtr tpm,
+                            const char *swtpmStorageDir,
+                            const unsigned char *uuid)
+{
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+
+    virUUIDFormat(uuid, uuidstr);
+
+    VIR_FREE(tpm->data.emulator.storagepath);
+    if (!(tpm->data.emulator.storagepath =
+            virTPMCreateEmulatorStoragePath(swtpmStorageDir, uuidstr)))
+        return -1;
+
+    return 0;
+}
+
+/*
+ * virTPMEmulatorPrepareHost:
+ *
+ * @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.
+ */
+int virTPMEmulatorPrepareHost(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 (virTPMEmulatorInit() < 0)
+        return -1;
+
+    /* create log dir ... */
+    if (virFileMakePathWithMode(logDir, 0730) < 0)
+        goto cleanup;
+
+    /* ... and adjust ownership */
+    if (virDirCreate(logDir, 0730, swtpm_user, swtpm_group,
+                     VIR_DIR_CREATE_ALLOW_EXIST) < 0)
+        goto cleanup;
+
+    /* create logfile name ... */
+    if (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 =
+          virTPMCreateEmulatorSocket(swtpmStateDir, shortName)))
+        goto cleanup;
+    tpm->data.emulator.source.type = VIR_DOMAIN_CHR_TYPE_UNIX;
+
+    ret = 0;
+
+ cleanup:
+    if (ret)
+        VIR_FREE(tpm->data.emulator.logfile);
+
+    return ret;
+}
+
+/*
+ * virTPMEmulatorRunSetup
+ *
+ * @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
+virTPMEmulatorRunSetup(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 rc = 0;
+    char uuid[VIR_UUID_STRING_BUFLEN];
+    char *vmid = NULL;
+    off_t logstart;
+
+    if (!privileged) {
+        return virFileWriteStr(logfile,
+                               _("Did not create EK and certificates since "
+                               "this requires privileged mode\n"),
+                               0600);
+    }
+
+    cmd = virCommandNew(swtpm_setup);
+    if (!cmd) {
+        rc = -1;
+        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);
+
+    /* get size of logfile */
+    logstart = virFileLength(logfile, -1);
+    if (logstart < 0)
+        logstart = 0;
+
+    if (virCommandRun(cmd, &exitstatus) < 0 || exitstatus != 0) {
+        char *buffer = NULL, *errors;
+        off_t loglength = virFileLength(logfile, -1);
+
+        if (loglength > logstart) {
+            ignore_value(virFileReadOffsetQuiet(logfile, logstart,
+                                                loglength - logstart,
+                                                &buffer));
+            errors = virStringFilterLines(buffer, "Error:", 160);
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Could not run '%s'. exitstatus: %d;\n"
+                         "%s"),
+                          swtpm_setup, exitstatus, errors);
+            VIR_FREE(buffer);
+            VIR_FREE(errors);
+        }
+        rc = -1;
+    }
+
+ cleanup:
+    VIR_FREE(vmid);
+    virCommandFree(cmd);
+
+    return rc;
+}
+
+/*
+ * virTPMEmulatorBuildCommand:
+ *
+ * @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.
+ */
+virCommandPtr
+virTPMEmulatorBuildCommand(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 (virTPMCreateEmulatorStorage(tpm->data.emulator.storagepath,
+                                    &created, swtpm_user, swtpm_group) < 0)
+        return NULL;
+
+    if (created &&
+        virTPMEmulatorRunSetup(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)
+        virTPMDeleteEmulatorStorage(tpm);
+
+    VIR_FREE(tpm->data.emulator.source.data.nix.path);
+    VIR_FREE(tpm->data.emulator.storagepath);
+
+    virCommandFree(cmd);
+
+    return NULL;
+}
+
+/*
+ * virTPMEmulatorStop
+ * @swtpmStateDir: A directory where the socket is located
+ * @shortName: short and unique name of the domain
+ *
+ * Gracefully stop the swptm
+ */
+void
+virTPMEmulatorStop(const char *swtpmStateDir, const char *shortName)
+{
+    virCommandPtr cmd;
+    char *pathname;
+    char *errbuf = NULL;
+
+    if (virTPMEmulatorInit() < 0)
+        return;
+
+    if (!(pathname = virTPMCreateEmulatorSocket(swtpmStateDir, shortName)))
+        return;
+
+    if (!virFileExists(pathname))
+        goto cleanup;
+
+    cmd = virCommandNew(swtpm_ioctl);
+    if (!cmd) {
+        VIR_FREE(pathname);
+        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);
+}
diff --git a/src/util/virtpm.h b/src/util/virtpm.h
index b21fc05..63f75b8 100644
--- a/src/util/virtpm.h
+++ b/src/util/virtpm.h
@@ -1,7 +1,7 @@
 /*
  * virtpm.h: TPM support
  *
- * Copyright (C) 2013 IBM Corporation
+ * Copyright (C) 2013,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
@@ -22,6 +22,37 @@
 #ifndef __VIR_TPM_H__
 # define __VIR_TPM_H__
 
+# include "vircommand.h"
+
+typedef struct _virDomainTPMDef virDomainTPMDef;
+typedef virDomainTPMDef *virDomainTPMDefPtr;
+
 char *virTPMCreateCancelPath(const char *devpath) ATTRIBUTE_NOINLINE;
 
+int virTPMEmulatorInitPaths(virDomainTPMDefPtr tpm,
+                            const char *swtpmStorageDir,
+                            const unsigned char *uuid)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+    ATTRIBUTE_RETURN_CHECK;
+int virTPMEmulatorPrepareHost(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)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+    ATTRIBUTE_NONNULL(6) ATTRIBUTE_RETURN_CHECK;
+virCommandPtr virTPMEmulatorBuildCommand(virDomainTPMDefPtr tpm,
+                                         const char *vmname,
+                                         const unsigned char *vmuuid,
+                                         bool privileged,
+                                         uid_t swtpm_user,
+                                         gid_t swtpm_group)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+    ATTRIBUTE_RETURN_CHECK;
+void virTPMEmulatorStop(const char *swtpmStateDir,
+                        const char *shortName)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+void virTPMDeleteEmulatorStorage(virDomainTPMDefPtr tpm)
+    ATTRIBUTE_NONNULL(1);
+
 #endif /* __VIR_TPM_H__ */
-- 
2.5.5




More information about the libvir-list mailing list