[libvirt] [PATCH v3] qemu: Pass file descriptor when using TPM passthrough

Stefan Berger stefanb at linux.vnet.ibm.com
Thu Nov 20 15:08:48 UTC 2014


Pass the TPM file descriptor to QEMU via command line.
Instead of passing /dev/tpm0 we now pass /dev/fdset/10 and the additional
parameters -add-fd set=10,fd=20.

This addresses the use case when QEMU is started with non-root privileges
and QEMU cannot open /dev/tpm0 for example.

One problem is that for the passing of the file descriptor set to work,
virCommandReorderFDs must not be called on the virCommand. This is prevented
by setting a flag in the virCommandPassFDGetFDIndex that is checked to be
clear when virCommandReorderFDs is run.

Signed-off-by: Stefan Berger <stefanb at linux.vnet.ibm.com>

v2->v3: Fixed some memory leaks
---
 src/libvirt_private.syms |   1 +
 src/qemu/qemu_command.c  | 136 ++++++++++++++++++++++++++++++++++++++++++++---
 src/util/vircommand.c    |  33 ++++++++++++
 src/util/vircommand.h    |   3 ++
 4 files changed, 166 insertions(+), 7 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index aeec440..3194e8b 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1164,6 +1164,7 @@ virCommandNewArgList;
 virCommandNewArgs;
 virCommandNonblockingFDs;
 virCommandPassFD;
+virCommandPassFDGetFDIndex;
 virCommandPassListenFDs;
 virCommandRawStatus;
 virCommandRequireHandshake;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 8ed7934..17debba 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -159,6 +159,58 @@ VIR_ENUM_IMPL(qemuNumaPolicy, VIR_DOMAIN_NUMATUNE_MEM_LAST,
               "interleave");
 
 /**
+ * qemuVirCommandGetFDSet:
+ * @cmd: the command to modify
+ * @fd: fd to reassign to the child
+ *
+ * Get the parameters for the QEMU -add-fd command line option
+ * for the given file descriptor. The file descriptor must previously
+ * have been 'transferred' in a virCommandPassFD() call.
+ * This function for example returns "set=10,fd=20".
+ */
+static char *
+qemuVirCommandGetFDSet(virCommandPtr cmd, int fd)
+{
+    char *result = NULL;
+    int idx = virCommandPassFDGetFDIndex(cmd, fd);
+
+    if (idx >= 0) {
+        ignore_value(virAsprintf(&result, "set=%d,fd=%d", idx, fd) < 0);
+    } else {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("file descriptor %d has not been transferred"), fd);
+    }
+
+    return result;
+}
+
+/**
+ * qemuVirCommandGetDevSet:
+ * @cmd: the command to modify
+ * @fd: fd to reassign to the child
+ *
+ * Get the parameters for the QEMU path= parameter where a file
+ * descriptor is accessed via a file descriptor set, for example
+ * /dev/fdset/10. The file descriptor must previously have been
+ * 'transferred' in a virCommandPassFD() call.
+ */
+static char *
+qemuVirCommandGetDevSet(virCommandPtr cmd, int fd)
+{
+    char *result = NULL;
+    int idx = virCommandPassFDGetFDIndex(cmd, fd);
+
+    if (idx >= 0) {
+        ignore_value(virAsprintf(&result, "/dev/fdset/%d", idx) < 0);
+    } else {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("file descriptor %d has not been transferred"), fd);
+    }
+    return result;
+}
+
+
+/**
  * qemuPhysIfaceConnect:
  * @def: the definition of the VM (needed by 802.1Qbh and audit)
  * @driver: pointer to the driver instance
@@ -5926,14 +5978,20 @@ qemuBuildRNGDeviceArgs(virCommandPtr cmd,
 
 
 static char *qemuBuildTPMBackendStr(const virDomainDef *def,
+                                    virCommandPtr cmd,
                                     virQEMUCapsPtr qemuCaps,
-                                    const char *emulator)
+                                    const char *emulator,
+                                    int *tpmfd, int *cancelfd)
 {
     const virDomainTPMDef *tpm = def->tpm;
     virBuffer buf = VIR_BUFFER_INITIALIZER;
     const char *type = virDomainTPMBackendTypeToString(tpm->type);
-    char *cancel_path;
+    char *cancel_path = NULL;
     const char *tpmdev;
+    char *devset = NULL, *cancel_devset = NULL;
+
+    *tpmfd = -1;
+    *cancelfd = -1;
 
     virBufferAsprintf(&buf, "%s,id=tpm-%s", type, tpm->info.alias);
 
@@ -5946,11 +6004,49 @@ static char *qemuBuildTPMBackendStr(const virDomainDef *def,
         if (!(cancel_path = virTPMCreateCancelPath(tpmdev)))
             goto error;
 
-        virBufferAddLit(&buf, ",path=");
-        virBufferEscape(&buf, ',', ",", "%s", tpmdev);
+        if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_ADD_FD)) {
+            *tpmfd = open(tpmdev, O_RDWR);
+            if (*tpmfd < 0) {
+                virReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("Could not open TPM device %s"), tpmdev);
+                goto error;
+            }
+
+            virCommandPassFD(cmd, *tpmfd,
+                             VIR_COMMAND_PASS_FD_CLOSE_PARENT);
+            devset = qemuVirCommandGetDevSet(cmd, *tpmfd);
+            if (devset == NULL)
+                goto error;
+
+            *cancelfd = open(cancel_path, O_WRONLY);
+            if (*cancelfd < 0) {
+                virReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("Could not open TPM device's cancel path "
+                                 "%s"), cancel_path);
+                goto error;
+            }
+
+            virCommandPassFD(cmd, *cancelfd,
+                             VIR_COMMAND_PASS_FD_CLOSE_PARENT);
+            cancel_devset = qemuVirCommandGetDevSet(cmd, *cancelfd);
+            if (cancel_devset == NULL)
+                goto error;
+
+            virBufferAddLit(&buf, ",path=");
+            virBufferEscape(&buf, ',', ",", "%s", devset);
+            VIR_FREE(devset);
 
-        virBufferAddLit(&buf, ",cancel-path=");
-        virBufferEscape(&buf, ',', ",", "%s", cancel_path);
+            virBufferAddLit(&buf, ",cancel-path=");
+            virBufferEscape(&buf, ',', ",", "%s", cancel_devset);
+            VIR_FREE(cancel_devset);
+        } else {
+            /* all test cases will use this path */
+            virBufferAddLit(&buf, ",path=");
+            virBufferEscape(&buf, ',', ",", "%s", tpmdev);
+
+            virBufferAddLit(&buf, ",cancel-path=");
+            virBufferEscape(&buf, ',', ",", "%s", cancel_path);
+        }
         VIR_FREE(cancel_path);
 
         break;
@@ -5970,6 +6066,10 @@ static char *qemuBuildTPMBackendStr(const virDomainDef *def,
                    emulator, type);
 
  error:
+    VIR_FREE(devset);
+    VIR_FREE(cancel_devset);
+    VIR_FREE(cancel_path);
+
     virBufferFreeAndReset(&buf);
     return NULL;
 }
@@ -9223,13 +9323,35 @@ qemuBuildCommandLine(virConnectPtr conn,
 
     if (def->tpm) {
         char *optstr;
+        int tpmfd = -1;
+        int cancelfd = -1;
+        char *fdset;
 
-        if (!(optstr = qemuBuildTPMBackendStr(def, qemuCaps, emulator)))
+        if (!(optstr = qemuBuildTPMBackendStr(def, cmd, qemuCaps, emulator,
+                                              &tpmfd, &cancelfd)))
             goto error;
 
         virCommandAddArgList(cmd, "-tpmdev", optstr, NULL);
         VIR_FREE(optstr);
 
+        if (tpmfd >= 0) {
+            fdset = qemuVirCommandGetFDSet(cmd, tpmfd);
+            if (!fdset)
+                goto error;
+
+            virCommandAddArgList(cmd, "-add-fd", fdset, NULL);
+            VIR_FREE(fdset);
+        }
+
+        if (cancelfd >= 0) {
+            fdset = qemuVirCommandGetFDSet(cmd, cancelfd);
+            if (!fdset)
+                goto error;
+
+            virCommandAddArgList(cmd, "-add-fd", fdset, NULL);
+            VIR_FREE(fdset);
+        }
+
         if (!(optstr = qemuBuildTPMDevStr(def, qemuCaps, emulator)))
             goto error;
 
diff --git a/src/util/vircommand.c b/src/util/vircommand.c
index 6527d85..2616446 100644
--- a/src/util/vircommand.c
+++ b/src/util/vircommand.c
@@ -67,6 +67,7 @@ enum {
     VIR_EXEC_RUN_SYNC   = (1 << 3),
     VIR_EXEC_ASYNC_IO   = (1 << 4),
     VIR_EXEC_LISTEN_FDS = (1 << 5),
+    VIR_EXEC_FIXED_FDS  = (1 << 6),
 };
 
 typedef struct _virCommandFD virCommandFD;
@@ -214,6 +215,12 @@ virCommandReorderFDs(virCommandPtr cmd)
     if (!cmd || cmd->has_error || !cmd->npassfd)
         return;
 
+    if ((cmd->flags & VIR_EXEC_FIXED_FDS)) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("The fds are fixed and cannot be reordered"));
+        goto error;
+    }
+
     for (i = 0; i < cmd->npassfd; i++)
         maxfd = MAX(cmd->passfd[i].fd, maxfd);
 
@@ -1019,6 +1026,32 @@ virCommandPassListenFDs(virCommandPtr cmd)
     cmd->flags |= VIR_EXEC_LISTEN_FDS;
 }
 
+/*
+ * virCommandPassFDGetFDIndex:
+ * @cmd: pointer to virCommand
+ * @fd: FD to get index of
+ *
+ * Determine the index of the FD in the transfer set.
+ *
+ * Returns index >= 0 if @set contains @fd,
+ * -1 otherwise.
+ */
+int
+virCommandPassFDGetFDIndex(virCommandPtr cmd, int fd)
+{
+    size_t i = 0;
+
+    while (i < cmd->npassfd) {
+        if (cmd->passfd[i].fd == fd) {
+            cmd->flags |= VIR_EXEC_FIXED_FDS;
+            return i;
+        }
+        i++;
+    }
+
+    return -1;
+}
+
 /**
  * virCommandSetPidFile:
  * @cmd: the command to modify
diff --git a/src/util/vircommand.h b/src/util/vircommand.h
index bf65de4..198da2f 100644
--- a/src/util/vircommand.h
+++ b/src/util/vircommand.h
@@ -62,6 +62,9 @@ void virCommandPassFD(virCommandPtr cmd,
 
 void virCommandPassListenFDs(virCommandPtr cmd);
 
+int virCommandPassFDGetFDIndex(virCommandPtr cmd,
+                               int fd);
+
 void virCommandSetPidFile(virCommandPtr cmd,
                           const char *pidfile) ATTRIBUTE_NONNULL(2);
 
-- 
1.9.3




More information about the libvir-list mailing list