0d8777
From 68d6a69a8bc2f25e935608344d5b7e2b52cde85f Mon Sep 17 00:00:00 2001
0d8777
From: Ken Gaillot <kgaillot@redhat.com>
0d8777
Date: Mon, 21 Oct 2019 19:02:47 -0500
0d8777
Subject: [PATCH] Low: libcrmservice: don't close descriptors above current
0d8777
 limit
0d8777
0d8777
This is irrelevant in normal use. However, valgrind can open high-numbered
0d8777
file descriptors for its own use above the soft limit of the process being run
0d8777
under valgrind. If that process forks a child that tries to close all open file
0d8777
descriptors (e.g. the executor running an agent), the close fails because the
0d8777
file descriptors are invalid, and (ironically) valgrind warns about that.
0d8777
0d8777
This allows 5a73027 to work under valgrind. Additionally, we extend the
0d8777
efficient close method from that commit to pacemakerd's spawning of children.
0d8777
---
0d8777
 daemons/pacemakerd/pacemakerd.c | 10 ++----
0d8777
 include/crm/common/internal.h   |  3 ++
0d8777
 lib/common/io.c                 | 72 +++++++++++++++++++++++++++++++++++++++++
0d8777
 lib/services/services_linux.c   | 29 +----------------
0d8777
 4 files changed, 79 insertions(+), 35 deletions(-)
0d8777
0d8777
diff --git a/daemons/pacemakerd/pacemakerd.c b/daemons/pacemakerd/pacemakerd.c
0d8777
index fdc1d9f..d8ff53d 100644
0d8777
--- a/daemons/pacemakerd/pacemakerd.c
0d8777
+++ b/daemons/pacemakerd/pacemakerd.c
0d8777
@@ -13,6 +13,8 @@
0d8777
 #include <pwd.h>
0d8777
 #include <grp.h>
0d8777
 #include <poll.h>
0d8777
+#include <stdio.h>
0d8777
+#include <stdbool.h>
0d8777
 #include <sys/stat.h>
0d8777
 #include <sys/types.h>
0d8777
 #include <sys/time.h>
0d8777
@@ -290,10 +292,8 @@ static char *opts_vgrind[] = { NULL, NULL, NULL, NULL, NULL };
0d8777
 static gboolean
0d8777
 start_child(pcmk_child_t * child)
0d8777
 {
0d8777
-    int lpc = 0;
0d8777
     uid_t uid = 0;
0d8777
     gid_t gid = 0;
0d8777
-    struct rlimit oflimits;
0d8777
     gboolean use_valgrind = FALSE;
0d8777
     gboolean use_callgrind = FALSE;
0d8777
     const char *devnull = "/dev/null";
0d8777
@@ -396,11 +396,7 @@ start_child(pcmk_child_t * child)
0d8777
             crm_perror(LOG_ERR, "Could not set user to %d (%s)", uid, child->uid);
0d8777
         }
0d8777
 
0d8777
-        /* Close all open file descriptors */
0d8777
-        getrlimit(RLIMIT_NOFILE, &oflimits);
0d8777
-        for (lpc = 0; lpc < oflimits.rlim_cur; lpc++) {
0d8777
-            close(lpc);
0d8777
-        }
0d8777
+        pcmk__close_fds_in_child(true);
0d8777
 
0d8777
         (void)open(devnull, O_RDONLY);  /* Stdin:  fd 0 */
0d8777
         (void)open(devnull, O_WRONLY);  /* Stdout: fd 1 */
0d8777
diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h
0d8777
index b2eec00..da2c7d7 100644
0d8777
--- a/include/crm/common/internal.h
0d8777
+++ b/include/crm/common/internal.h
0d8777
@@ -13,6 +13,7 @@
0d8777
 #include <glib.h>       /* for gboolean */
0d8777
 #include <dirent.h>     /* for struct dirent */
0d8777
 #include <unistd.h>     /* for getpid() */
0d8777
+#include <stdbool.h>    /* for bool */
0d8777
 #include <sys/types.h>  /* for uid_t and gid_t */
0d8777
 
0d8777
 #include <crm/common/logging.h>
0d8777
@@ -33,6 +34,8 @@ int crm_write_sync(int fd, const char *contents);
0d8777
 int crm_set_nonblocking(int fd);
0d8777
 const char *crm_get_tmpdir(void);
0d8777
 
0d8777
+void pcmk__close_fds_in_child(bool);
0d8777
+
0d8777
 
0d8777
 /* internal procfs utilities (from procfs.c) */
0d8777
 
0d8777
diff --git a/lib/common/io.c b/lib/common/io.c
0d8777
index fa438dd..6cbab0a 100644
0d8777
--- a/lib/common/io.c
0d8777
+++ b/lib/common/io.c
0d8777
@@ -16,6 +16,7 @@
0d8777
 #include <sys/param.h>
0d8777
 #include <sys/types.h>
0d8777
 #include <sys/stat.h>
0d8777
+#include <sys/resource.h>
0d8777
 
0d8777
 #include <stdio.h>
0d8777
 #include <unistd.h>
0d8777
@@ -501,3 +502,74 @@ crm_get_tmpdir()
0d8777
 
0d8777
     return (dir && (*dir == '/'))? dir : "/tmp";
0d8777
 }
0d8777
+
0d8777
+/*!
0d8777
+ * \internal
0d8777
+ * \brief Close open file descriptors
0d8777
+ *
0d8777
+ * Close all file descriptors (except optionally stdin, stdout, and stderr),
0d8777
+ * which is a best practice for a new child process forked for the purpose of
0d8777
+ * executing an external program.
0d8777
+ *
0d8777
+ * \param[in] bool  If true, close stdin, stdout, and stderr as well
0d8777
+ */
0d8777
+void
0d8777
+pcmk__close_fds_in_child(bool all)
0d8777
+{
0d8777
+    DIR *dir;
0d8777
+    struct rlimit rlim;
0d8777
+    rlim_t max_fd;
0d8777
+    int min_fd = (all? 0 : (STDERR_FILENO + 1));
0d8777
+
0d8777
+    /* Find the current process's (soft) limit for open files. getrlimit()
0d8777
+     * should always work, but have a fallback just in case.
0d8777
+     */
0d8777
+    if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
0d8777
+        max_fd = rlim.rlim_cur - 1;
0d8777
+    } else {
0d8777
+        long conf_max = sysconf(_SC_OPEN_MAX);
0d8777
+
0d8777
+        max_fd = (conf_max > 0)? conf_max : 1024;
0d8777
+    }
0d8777
+
0d8777
+    /* /proc/self/fd (on Linux) or /dev/fd (on most OSes) contains symlinks to
0d8777
+     * all open files for the current process, named as the file descriptor.
0d8777
+     * Use this if available, because it's more efficient than a shotgun
0d8777
+     * approach to closing descriptors.
0d8777
+     */
0d8777
+#if SUPPORT_PROCFS
0d8777
+    dir = opendir("/proc/self/fd");
0d8777
+    if (dir == NULL) {
0d8777
+        dir = opendir("/dev/fd");
0d8777
+    }
0d8777
+#else
0d8777
+    dir = opendir("/dev/fd");
0d8777
+#endif
0d8777
+    if (dir != NULL) {
0d8777
+        struct dirent *entry;
0d8777
+        int dir_fd = dirfd(dir);
0d8777
+
0d8777
+        while ((entry = readdir(dir)) != NULL) {
0d8777
+            int lpc = atoi(entry->d_name);
0d8777
+
0d8777
+            /* How could one of these entries be higher than max_fd, you ask?
0d8777
+             * It isn't possible in normal operation, but when run under
0d8777
+             * valgrind, valgrind can open high-numbered file descriptors for
0d8777
+             * its own use that are higher than the process's soft limit.
0d8777
+             * These will show up in the fd directory but aren't closable.
0d8777
+             */
0d8777
+            if ((lpc >= min_fd) && (lpc <= max_fd) && (lpc != dir_fd)) {
0d8777
+                close(lpc);
0d8777
+            }
0d8777
+        }
0d8777
+        closedir(dir);
0d8777
+        return;
0d8777
+    }
0d8777
+
0d8777
+    /* If no fd directory is available, iterate over all possible descriptors.
0d8777
+     * This is less efficient due to the overhead of many system calls.
0d8777
+     */
0d8777
+    for (int lpc = max_fd; lpc >= min_fd; lpc--) {
0d8777
+        close(lpc);
0d8777
+    }
0d8777
+}
0d8777
diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c
0d8777
index 464fc5b..6870273 100644
0d8777
--- a/lib/services/services_linux.c
0d8777
+++ b/lib/services/services_linux.c
0d8777
@@ -444,9 +444,6 @@ services_handle_exec_error(svc_action_t * op, int error)
0d8777
 static void
0d8777
 action_launch_child(svc_action_t *op)
0d8777
 {
0d8777
-    int lpc;
0d8777
-    DIR *dir;
0d8777
-
0d8777
     /* SIGPIPE is ignored (which is different from signal blocking) by the gnutls library.
0d8777
      * Depending on the libqb version in use, libqb may set SIGPIPE to be ignored as well. 
0d8777
      * We do not want this to be inherited by the child process. By resetting this the signal
0d8777
@@ -476,31 +473,7 @@ action_launch_child(svc_action_t *op)
0d8777
      */
0d8777
     setpgid(0, 0);
0d8777
 
0d8777
-    // Close all file descriptors except stdin/stdout/stderr
0d8777
-#if SUPPORT_PROCFS
0d8777
-    dir = opendir("/proc/self/fd");
0d8777
-#else
0d8777
-    dir = opendir("/dev/fd");
0d8777
-#endif
0d8777
-    if (dir == NULL) { /* /proc or /dev/fd not available */
0d8777
-	/* Iterate over all possible fds, might be slow */
0d8777
-        for (lpc = getdtablesize() - 1; lpc > STDERR_FILENO; lpc--) {
0d8777
-            close(lpc);
0d8777
-        }
0d8777
-    } else {
0d8777
-        /* Iterate over fds obtained from /proc or /dev/fd */
0d8777
-        struct dirent *entry;
0d8777
-        int dir_fd = dirfd(dir);
0d8777
-
0d8777
-        while ((entry = readdir(dir)) != NULL) {
0d8777
-            lpc = atoi(entry->d_name);
0d8777
-            if (lpc > STDERR_FILENO && lpc != dir_fd) {
0d8777
-                close(lpc);
0d8777
-            }
0d8777
-        }
0d8777
-
0d8777
-        closedir(dir);
0d8777
-    }
0d8777
+    pcmk__close_fds_in_child(false);
0d8777
 
0d8777
 #if SUPPORT_CIBSECRETS
0d8777
     if (replace_secret_params(op->rsc, op->params) < 0) {
0d8777
-- 
0d8777
1.8.3.1
0d8777