render / rpms / libvirt

Forked from rpms/libvirt 7 months ago
Clone
Pablo Greco 40546a
From 53d2fa1d52ddc8d1d219a6882f84241a996ea752 Mon Sep 17 00:00:00 2001
Pablo Greco 40546a
Message-Id: <53d2fa1d52ddc8d1d219a6882f84241a996ea752@dist-git>
Pablo Greco 40546a
From: Michal Privoznik <mprivozn@redhat.com>
Pablo Greco 40546a
Date: Tue, 30 Jul 2019 15:30:54 +0200
Pablo Greco 40546a
Subject: [PATCH] virCommand: use procfs to learn opened FDs
Pablo Greco 40546a
MIME-Version: 1.0
Pablo Greco 40546a
Content-Type: text/plain; charset=UTF-8
Pablo Greco 40546a
Content-Transfer-Encoding: 8bit
Pablo Greco 40546a
Pablo Greco 40546a
When spawning a child process, between fork() and exec() we close
Pablo Greco 40546a
all file descriptors and keep only those the caller wants us to
Pablo Greco 40546a
pass onto the child. The problem is how we do that. Currently, we
Pablo Greco 40546a
get the limit of opened files and then iterate through each one
Pablo Greco 40546a
of them and either close() it or make it survive exec(). This
Pablo Greco 40546a
approach is suboptimal (although, not that much in default
Pablo Greco 40546a
configurations where the limit is pretty low - 1024). We have
Pablo Greco 40546a
/proc where we can learn what FDs we hold open and thus we can
Pablo Greco 40546a
selectively close only those.
Pablo Greco 40546a
Pablo Greco 40546a
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Pablo Greco 40546a
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Pablo Greco 40546a
(cherry picked from commit 432faf259b696043ee5d7e8f657d855419a9a3fa)
Pablo Greco 40546a
Pablo Greco 40546a
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1721434
Pablo Greco 40546a
Pablo Greco 40546a
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Pablo Greco 40546a
Message-Id: <a9bee8e6412dc010caccef2bded857bb2a1c5eb0.1564493409.git.mprivozn@redhat.com>
Pablo Greco 40546a
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Pablo Greco 40546a
---
Pablo Greco 40546a
 src/util/vircommand.c | 86 +++++++++++++++++++++++++++++++++++++++----
Pablo Greco 40546a
 1 file changed, 78 insertions(+), 8 deletions(-)
Pablo Greco 40546a
Pablo Greco 40546a
diff --git a/src/util/vircommand.c b/src/util/vircommand.c
Pablo Greco 40546a
index 8ae9a952a3..2353a4a554 100644
Pablo Greco 40546a
--- a/src/util/vircommand.c
Pablo Greco 40546a
+++ b/src/util/vircommand.c
Pablo Greco 40546a
@@ -491,27 +491,97 @@ virExecCommon(virCommandPtr cmd, gid_t *groups, int ngroups)
Pablo Greco 40546a
     return ret;
Pablo Greco 40546a
 }
Pablo Greco 40546a
 
Pablo Greco 40546a
+# ifdef __linux__
Pablo Greco 40546a
+/* On Linux, we can utilize procfs and read the table of opened
Pablo Greco 40546a
+ * FDs and selectively close only those FDs we don't want to pass
Pablo Greco 40546a
+ * onto child process (well, the one we will exec soon since this
Pablo Greco 40546a
+ * is called from the child). */
Pablo Greco 40546a
+static int
Pablo Greco 40546a
+virCommandMassCloseGetFDsLinux(virCommandPtr cmd ATTRIBUTE_UNUSED,
Pablo Greco 40546a
+                               virBitmapPtr fds)
Pablo Greco 40546a
+{
Pablo Greco 40546a
+    DIR *dp = NULL;
Pablo Greco 40546a
+    struct dirent *entry;
Pablo Greco 40546a
+    const char *dirName = "/proc/self/fd";
Pablo Greco 40546a
+    int rc;
Pablo Greco 40546a
+    int ret = -1;
Pablo Greco 40546a
+
Pablo Greco 40546a
+    if (virDirOpen(&dp, dirName) < 0)
Pablo Greco 40546a
+        return -1;
Pablo Greco 40546a
+
Pablo Greco 40546a
+    while ((rc = virDirRead(dp, &entry, dirName)) > 0) {
Pablo Greco 40546a
+        int fd;
Pablo Greco 40546a
+
Pablo Greco 40546a
+        if (virStrToLong_i(entry->d_name, NULL, 10, &fd) < 0) {
Pablo Greco 40546a
+            virReportError(VIR_ERR_INTERNAL_ERROR,
Pablo Greco 40546a
+                           _("unable to parse FD: %s"),
Pablo Greco 40546a
+                           entry->d_name);
Pablo Greco 40546a
+            goto cleanup;
Pablo Greco 40546a
+        }
Pablo Greco 40546a
+
Pablo Greco 40546a
+        if (virBitmapSetBit(fds, fd) < 0) {
Pablo Greco 40546a
+            virReportError(VIR_ERR_INTERNAL_ERROR,
Pablo Greco 40546a
+                           _("unable to set FD as open: %d"),
Pablo Greco 40546a
+                           fd);
Pablo Greco 40546a
+            goto cleanup;
Pablo Greco 40546a
+        }
Pablo Greco 40546a
+    }
Pablo Greco 40546a
+
Pablo Greco 40546a
+    if (rc < 0)
Pablo Greco 40546a
+        goto cleanup;
Pablo Greco 40546a
+
Pablo Greco 40546a
+    ret = 0;
Pablo Greco 40546a
+ cleanup:
Pablo Greco 40546a
+    VIR_DIR_CLOSE(dp);
Pablo Greco 40546a
+    return ret;
Pablo Greco 40546a
+}
Pablo Greco 40546a
+
Pablo Greco 40546a
+# else /* !__linux__ */
Pablo Greco 40546a
+
Pablo Greco 40546a
+static int
Pablo Greco 40546a
+virCommandMassCloseGetFDsGeneric(virCommandPtr cmd ATTRIBUTE_UNUSED,
Pablo Greco 40546a
+                                 virBitmapPtr fds)
Pablo Greco 40546a
+{
Pablo Greco 40546a
+    virBitmapSetAll(fds);
Pablo Greco 40546a
+    return 0;
Pablo Greco 40546a
+}
Pablo Greco 40546a
+# endif /* !__linux__ */
Pablo Greco 40546a
+
Pablo Greco 40546a
 static int
Pablo Greco 40546a
 virCommandMassClose(virCommandPtr cmd,
Pablo Greco 40546a
                     int childin,
Pablo Greco 40546a
                     int childout,
Pablo Greco 40546a
                     int childerr)
Pablo Greco 40546a
 {
Pablo Greco 40546a
+    VIR_AUTOPTR(virBitmap) fds = NULL;
Pablo Greco 40546a
     int openmax = sysconf(_SC_OPEN_MAX);
Pablo Greco 40546a
-    int fd;
Pablo Greco 40546a
-    int tmpfd;
Pablo Greco 40546a
+    int fd = -1;
Pablo Greco 40546a
 
Pablo Greco 40546a
-    if (openmax < 0) {
Pablo Greco 40546a
-        virReportSystemError(errno,  "%s",
Pablo Greco 40546a
-                             _("sysconf(_SC_OPEN_MAX) failed"));
Pablo Greco 40546a
+    /* In general, it is not safe to call malloc() between fork() and exec()
Pablo Greco 40546a
+     * because the child might have forked at the worst possible time, i.e.
Pablo Greco 40546a
+     * when another thread was in malloc() and thus held its lock. That is to
Pablo Greco 40546a
+     * say, POSIX does not mandate malloc() to be async-safe. Fortunately,
Pablo Greco 40546a
+     * glibc developers are aware of this and made malloc() async-safe.
Pablo Greco 40546a
+     * Therefore we can safely allocate memory here (and transitively call
Pablo Greco 40546a
+     * opendir/readdir) without a deadlock. */
Pablo Greco 40546a
+
Pablo Greco 40546a
+    if (!(fds = virBitmapNew(openmax)))
Pablo Greco 40546a
         return -1;
Pablo Greco 40546a
-    }
Pablo Greco 40546a
 
Pablo Greco 40546a
-    for (fd = 3; fd < openmax; fd++) {
Pablo Greco 40546a
+# ifdef __linux__
Pablo Greco 40546a
+    if (virCommandMassCloseGetFDsLinux(cmd, fds) < 0)
Pablo Greco 40546a
+        return -1;
Pablo Greco 40546a
+# else
Pablo Greco 40546a
+    if (virCommandMassCloseGetFDsGeneric(cmd, fds) < 0)
Pablo Greco 40546a
+        return -1;
Pablo Greco 40546a
+# endif
Pablo Greco 40546a
+
Pablo Greco 40546a
+    fd = virBitmapNextSetBit(fds, 2);
Pablo Greco 40546a
+    for (; fd >= 0; fd = virBitmapNextSetBit(fds, fd)) {
Pablo Greco 40546a
         if (fd == childin || fd == childout || fd == childerr)
Pablo Greco 40546a
             continue;
Pablo Greco 40546a
         if (!virCommandFDIsSet(cmd, fd)) {
Pablo Greco 40546a
-            tmpfd = fd;
Pablo Greco 40546a
+            int tmpfd = fd;
Pablo Greco 40546a
             VIR_MASS_CLOSE(tmpfd);
Pablo Greco 40546a
         } else if (virSetInherit(fd, true) < 0) {
Pablo Greco 40546a
             virReportSystemError(errno, _("failed to preserve fd %d"), fd);
Pablo Greco 40546a
-- 
Pablo Greco 40546a
2.22.0
Pablo Greco 40546a