2cf05b
From e3487aab5319df05c5a06a83e4d3e4a87c1e51a9 Mon Sep 17 00:00:00 2001
2cf05b
Message-Id: <e3487aab5319df05c5a06a83e4d3e4a87c1e51a9@dist-git>
2cf05b
From: Vasiliy Ulyanov <vulyanov@suse.de>
2cf05b
Date: Wed, 2 Feb 2022 17:28:16 +0100
2cf05b
Subject: [PATCH] qemu: tpm: Get swtpm pid without binary validation
2cf05b
2cf05b
Access to /proc/[pid]/exe may be restricted in certain environments (e.g.
2cf05b
in containers) and any attempt to stat(2) or readlink(2) the file will
2cf05b
result in 'permission denied' error if the calling process does not have
2cf05b
CAP_SYS_PTRACE capability. According to proc(5) manpage:
2cf05b
2cf05b
Permission to dereference or read (readlink(2)) this symbolic link is
2cf05b
governed by a ptrace access mode PTRACE_MODE_READ_FSCREDS check; see
2cf05b
ptrace(2).
2cf05b
2cf05b
The binary validation in virPidFileReadPathIfAlive may fail with EACCES.
2cf05b
Therefore instead do only the check that the pidfile is locked by the
2cf05b
correct process. To ensure this is always the case the daemonization and
2cf05b
pidfile handling of the swtpm command is now controlled by libvirt.
2cf05b
2cf05b
Signed-off-by: Vasiliy Ulyanov <vulyanov@suse.de>
2cf05b
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2cf05b
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2cf05b
(cherry picked from commit a9c500d2b50c5c041a1bb6ae9724402cf1cec8fe)
2cf05b
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2152188
2cf05b
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2cf05b
---
2cf05b
 src/qemu/qemu_tpm.c | 94 ++++++++++++++++++++++++++-------------------
2cf05b
 1 file changed, 54 insertions(+), 40 deletions(-)
2cf05b
2cf05b
diff --git a/src/qemu/qemu_tpm.c b/src/qemu/qemu_tpm.c
2cf05b
index 7e7b01768e..9c5d1ffed4 100644
2cf05b
--- a/src/qemu/qemu_tpm.c
2cf05b
+++ b/src/qemu/qemu_tpm.c
2cf05b
@@ -44,6 +44,7 @@
2cf05b
 #include "qemu_tpm.h"
2cf05b
 #include "virtpm.h"
2cf05b
 #include "virsecret.h"
2cf05b
+#include "virtime.h"
2cf05b
 
2cf05b
 #define VIR_FROM_THIS VIR_FROM_NONE
2cf05b
 
2cf05b
@@ -258,13 +259,13 @@ qemuTPMEmulatorGetPid(const char *swtpmStateDir,
2cf05b
                       const char *shortName,
2cf05b
                       pid_t *pid)
2cf05b
 {
2cf05b
-    g_autofree char *swtpm = virTPMGetSwtpm();
2cf05b
     g_autofree char *pidfile = qemuTPMEmulatorCreatePidFilename(swtpmStateDir,
2cf05b
                                                                 shortName);
2cf05b
+
2cf05b
     if (!pidfile)
2cf05b
         return -1;
2cf05b
 
2cf05b
-    if (virPidFileReadPathIfAlive(pidfile, pid, swtpm) < 0)
2cf05b
+    if (virPidFileReadPathIfLocked(pidfile, pid) < 0)
2cf05b
         return -1;
2cf05b
 
2cf05b
     return 0;
2cf05b
@@ -660,9 +661,6 @@ qemuTPMEmulatorReconfigure(const char *storagepath,
2cf05b
  * @privileged: whether we are running in privileged mode
2cf05b
  * @swtpm_user: The uid for the swtpm to run as (drop privileges to from root)
2cf05b
  * @swtpm_group: The gid for the swtpm to run as
2cf05b
- * @swtpmStateDir: the directory where swtpm writes the pid file and creates the
2cf05b
- *                 Unix socket
2cf05b
- * @shortName: the short name of the VM
2cf05b
  * @incomingMigration: whether we have an incoming migration
2cf05b
  *
2cf05b
  * Create the virCommand use for starting the emulator
2cf05b
@@ -676,13 +674,10 @@ qemuTPMEmulatorBuildCommand(virDomainTPMDef *tpm,
2cf05b
                             bool privileged,
2cf05b
                             uid_t swtpm_user,
2cf05b
                             gid_t swtpm_group,
2cf05b
-                            const char *swtpmStateDir,
2cf05b
-                            const char *shortName,
2cf05b
                             bool incomingMigration)
2cf05b
 {
2cf05b
     g_autoptr(virCommand) cmd = NULL;
2cf05b
     bool created = false;
2cf05b
-    g_autofree char *pidfile = NULL;
2cf05b
     g_autofree char *swtpm = virTPMGetSwtpm();
2cf05b
     int pwdfile_fd = -1;
2cf05b
     int migpwdfile_fd = -1;
2cf05b
@@ -721,7 +716,7 @@ qemuTPMEmulatorBuildCommand(virDomainTPMDef *tpm,
2cf05b
 
2cf05b
     virCommandClearCaps(cmd);
2cf05b
 
2cf05b
-    virCommandAddArgList(cmd, "socket", "--daemon", "--ctrl", NULL);
2cf05b
+    virCommandAddArgList(cmd, "socket", "--ctrl", NULL);
2cf05b
     virCommandAddArgFormat(cmd, "type=unixio,path=%s,mode=0600",
2cf05b
                            tpm->data.emulator.source->data.nix.path);
2cf05b
 
2cf05b
@@ -748,12 +743,6 @@ qemuTPMEmulatorBuildCommand(virDomainTPMDef *tpm,
2cf05b
         break;
2cf05b
     }
2cf05b
 
2cf05b
-    if (!(pidfile = qemuTPMEmulatorCreatePidFilename(swtpmStateDir, shortName)))
2cf05b
-        goto error;
2cf05b
-
2cf05b
-    virCommandAddArg(cmd, "--pid");
2cf05b
-    virCommandAddArgFormat(cmd, "file=%s", pidfile);
2cf05b
-
2cf05b
     if (tpm->data.emulator.hassecretuuid) {
2cf05b
         if (!virTPMSwtpmCapsGet(VIR_TPM_SWTPM_FEATURE_CMDARG_PWD_FD)) {
2cf05b
             virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED,
2cf05b
@@ -904,12 +893,14 @@ qemuExtTPMStartEmulator(virQEMUDriver *driver,
2cf05b
                         bool incomingMigration)
2cf05b
 {
2cf05b
     g_autoptr(virCommand) cmd = NULL;
2cf05b
-    int exitstatus = 0;
2cf05b
-    g_autofree char *errbuf = NULL;
2cf05b
+    VIR_AUTOCLOSE errfd = -1;
2cf05b
     g_autoptr(virQEMUDriverConfig) cfg = NULL;
2cf05b
     g_autofree char *shortName = virDomainDefGetShortName(vm->def);
2cf05b
-    int cmdret = 0, timeout, rc;
2cf05b
-    pid_t pid;
2cf05b
+    g_autofree char *pidfile = NULL;
2cf05b
+    virTimeBackOffVar timebackoff;
2cf05b
+    const unsigned long long timeout = 1000; /* ms */
2cf05b
+    int cmdret = 0;
2cf05b
+    pid_t pid = -1;
2cf05b
 
2cf05b
     if (!shortName)
2cf05b
         return -1;
2cf05b
@@ -923,48 +914,71 @@ qemuExtTPMStartEmulator(virQEMUDriver *driver,
2cf05b
                                             driver->privileged,
2cf05b
                                             cfg->swtpm_user,
2cf05b
                                             cfg->swtpm_group,
2cf05b
-                                            cfg->swtpmStateDir, shortName,
2cf05b
                                             incomingMigration)))
2cf05b
         return -1;
2cf05b
 
2cf05b
     if (qemuExtDeviceLogCommand(driver, vm, cmd, "TPM Emulator") < 0)
2cf05b
         return -1;
2cf05b
 
2cf05b
-    virCommandSetErrorBuffer(cmd, &errbuf);
2cf05b
+    if (!(pidfile = qemuTPMEmulatorCreatePidFilename(cfg->swtpmStateDir, shortName)))
2cf05b
+        return -1;
2cf05b
+
2cf05b
+    virCommandDaemonize(cmd);
2cf05b
+    virCommandSetPidFile(cmd, pidfile);
2cf05b
+    virCommandSetErrorFD(cmd, &errfd);
2cf05b
 
2cf05b
     if (qemuSecurityStartTPMEmulator(driver, vm, cmd,
2cf05b
                                      cfg->swtpm_user, cfg->swtpm_group,
2cf05b
-                                     &exitstatus, &cmdret) < 0)
2cf05b
+                                     NULL, &cmdret) < 0)
2cf05b
         return -1;
2cf05b
 
2cf05b
-    if (cmdret < 0 || exitstatus != 0) {
2cf05b
-        virReportError(VIR_ERR_INTERNAL_ERROR,
2cf05b
-                       _("Could not start 'swtpm'. exitstatus: %d, "
2cf05b
-                         "error: %s"), exitstatus, errbuf);
2cf05b
-        return -1;
2cf05b
+    if (cmdret < 0) {
2cf05b
+        /* virCommandRun() hidden in qemuSecurityStartTPMEmulator()
2cf05b
+         * already reported error. */
2cf05b
+        goto error;
2cf05b
     }
2cf05b
 
2cf05b
-    /* check that the swtpm has written its pid into the file */
2cf05b
-    timeout = 1000; /* ms */
2cf05b
-    while (timeout > 0) {
2cf05b
-        rc = qemuTPMEmulatorGetPid(cfg->swtpmStateDir, shortName, &pid;;
2cf05b
-        if (rc < 0) {
2cf05b
-            timeout -= 50;
2cf05b
-            g_usleep(50 * 1000);
2cf05b
+    if (virPidFileReadPath(pidfile, &pid) < 0) {
2cf05b
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
2cf05b
+                       _("swtpm didn't show up"));
2cf05b
+        goto error;
2cf05b
+    }
2cf05b
+
2cf05b
+    if (virTimeBackOffStart(&timebackoff, 1, timeout) < 0)
2cf05b
+        goto error;
2cf05b
+    while (virTimeBackOffWait(&timebackoff)) {
2cf05b
+        char errbuf[1024] = { 0 };
2cf05b
+
2cf05b
+        if (virFileExists(tpm->data.emulator.source->data.nix.path))
2cf05b
+            break;
2cf05b
+
2cf05b
+        if (virProcessKill(pid, 0) == 0)
2cf05b
             continue;
2cf05b
+
2cf05b
+        if (saferead(errfd, errbuf, sizeof(errbuf) - 1) < 0) {
2cf05b
+            virReportSystemError(errno, "%s",
2cf05b
+                                 _("swtpm died unexpectedly"));
2cf05b
+        } else {
2cf05b
+            virReportError(VIR_ERR_OPERATION_FAILED,
2cf05b
+                           _("swtpm died and reported: %s"), errbuf);
2cf05b
         }
2cf05b
-        if (rc == 0 && pid == (pid_t)-1)
2cf05b
-            goto error;
2cf05b
-        break;
2cf05b
+        goto error;
2cf05b
     }
2cf05b
-    if (timeout <= 0)
2cf05b
+
2cf05b
+    if (!virFileExists(tpm->data.emulator.source->data.nix.path)) {
2cf05b
+        virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s",
2cf05b
+                       _("swtpm socket did not show up"));
2cf05b
         goto error;
2cf05b
+    }
2cf05b
 
2cf05b
     return 0;
2cf05b
 
2cf05b
  error:
2cf05b
-    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
2cf05b
-                   _("swtpm failed to start"));
2cf05b
+    virCommandAbort(cmd);
2cf05b
+    if (pid >= 0)
2cf05b
+        virProcessKillPainfully(pid, true);
2cf05b
+    if (pidfile)
2cf05b
+        unlink(pidfile);
2cf05b
     return -1;
2cf05b
 }
2cf05b
 
2cf05b
-- 
2cf05b
2.39.0
2cf05b