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