From 52dd3d34e04a0ecbf3deffb9d20368d68281176c Mon Sep 17 00:00:00 2001
From: TJ Saunders <tj@castaglia.org>
Date: Tue, 15 Aug 2017 19:41:52 -0700
Subject: [PATCH] Bug#4312: Close any "extra" open fds at startup.

---
 contrib/mod_exec.c | 65 ++++--------------------------------------------------
 include/fsio.h     |  3 +++
 src/fsio.c         | 55 +++++++++++++++++++++++++++++++++++++++++++++
 src/main.c         |  1 +
 tests/api/fsio.c   |  7 ++++++
 5 files changed, 70 insertions(+), 61 deletions(-)

Index: proftpd-dfsg-1.3.6/contrib/mod_exec.c
===================================================================
--- proftpd-dfsg-1.3.6.orig/contrib/mod_exec.c	2018-02-20 21:24:24.000000000 +0100
+++ proftpd-dfsg-1.3.6/contrib/mod_exec.c	2018-02-20 21:24:24.000000000 +0100
@@ -31,17 +31,15 @@
 # include <sys/resource.h>
 #endif
 
-#define MOD_EXEC_VERSION	"mod_exec/0.9.14"
+#define MOD_EXEC_VERSION	"mod_exec/0.9.16"
 
 /* Make sure the version of proftpd is as necessary. */
-#if PROFTPD_VERSION_NUMBER < 0x0001030402
-# error "ProFTPD 1.3.4rc2 or later required"
+#if PROFTPD_VERSION_NUMBER < 0x0001030701
+# error "ProFTPD 1.3.7rc1 or later required"
 #endif
 
 module exec_module;
 
-#define EXEC_MAX_FD_COUNT		1024
-
 static pool *exec_pool = NULL;
 static int exec_engine = FALSE;
 static unsigned int exec_nexecs = 0;
@@ -263,10 +261,6 @@
 }
 
 static void exec_prepare_fds(int stdin_fd, int stdout_fd, int stderr_fd) {
-  long nfiles = 0;
-  register unsigned int i = 0;
-  struct rlimit rlim;
-
   if (stdin_fd < 0) {
     stdin_fd = open("/dev/null", O_RDONLY);
     if (stdin_fd < 0) {
@@ -314,59 +308,8 @@
    * dup /dev/null.  For stdout and stderr, we dup some pipes, so that
    * we can capture what the command may write to stdout or stderr.  The
    * stderr output will be logged to the ExecLog.
-   *
-   * First, use getrlimit() to obtain the maximum number of open files
-   * for this process -- then close that number.
    */
-#if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE)
-# if defined(RLIMIT_NOFILE)
-  if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
-# elif defined(RLIMIT_OFILE)
-  if (getrlimit(RLIMIT_OFILE, &rlim) < 0) {
-# endif
-    /* Ignore ENOSYS (and EPERM, since some libc's use this as ENOSYS). */
-    if (errno != ENOSYS &&
-        errno != EPERM) {
-      exec_log("getrlimit() error: %s", strerror(errno));
-    }
-
-    /* Pick some arbitrary high number. */
-    nfiles = EXEC_MAX_FD_COUNT;
-
-  } else {
-    nfiles = rlim.rlim_max;
-  }
-
-#else /* no RLIMIT_NOFILE or RLIMIT_OFILE */
-   nfiles = EXEC_MAX_FD_COUNT;
-#endif
-
-  /* Yes, using a long for the nfiles variable is not quite kosher; it should
-   * be an unsigned type, otherwise a large limit (say, RLIMIT_INFINITY)
-   * might overflow the data type.  In that case, though, we want to know
-   * about it -- and using a signed type, we will know if the overflowed
-   * value is a negative number.  Chances are we do NOT want to be closing
-   * fds whose value is as high as they can possibly get; that's too many
-   * fds to iterate over.  Long story short, using a long int is just fine.
-   * (Plus it makes mod_exec work on Mac OSX 10.4; without this tweak,
-   * mod_exec's forked processes never return/exit.)
-   */
-
-  if (nfiles < 0 ||
-      nfiles > EXEC_MAX_FD_COUNT) {
-    nfiles = EXEC_MAX_FD_COUNT;
-  }
-
-  /* Close the "non-standard" file descriptors. */
-  for (i = 3; i < nfiles; i++) {
-
-    /* This is a potentially long-running loop, so handle signals. */
-    pr_signals_handle();
-
-    close(i);
-  }
-
-  return;
+  pr_fs_close_extra_fds();
 }
 
 static void exec_prepare_pipes(void) {
Index: proftpd-dfsg-1.3.6/include/fsio.h
===================================================================
--- proftpd-dfsg-1.3.6.orig/include/fsio.h	2018-02-20 21:24:24.000000000 +0100
+++ proftpd-dfsg-1.3.6/include/fsio.h	2018-02-20 21:24:24.000000000 +0100
@@ -413,6 +413,9 @@
 void pr_fs_globfree(glob_t *);
 void pr_resolve_fs_map(void);
 
+/* Close all but the main three fds. */
+void pr_fs_close_extra_fds(void);
+
 /* The main three fds (stdin, stdout, stderr) need to be protected, reserved
  * for use.  This function uses dup(2) to open new fds on the given fd
  * until the new fd is not one of the big three.
Index: proftpd-dfsg-1.3.6/src/fsio.c
===================================================================
--- proftpd-dfsg-1.3.6.orig/src/fsio.c	2018-02-20 21:24:24.000000000 +0100
+++ proftpd-dfsg-1.3.6/src/fsio.c	2018-02-20 21:24:24.000000000 +0100
@@ -6478,6 +6478,61 @@
   return (buf > start ? start : NULL);
 }
 
+#define FSIO_MAX_FD_COUNT		1024
+
+void pr_fs_close_extra_fds(void) {
+  register unsigned int i;
+  long nfiles = 0;
+  struct rlimit rlim;
+
+  /* Close any but the big three open fds.
+   *
+   * First, use getrlimit() to obtain the maximum number of open files
+   * for this process -- then close that number.
+   */
+#if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE)
+# if defined(RLIMIT_NOFILE)
+  if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
+# elif defined(RLIMIT_OFILE)
+  if (getrlimit(RLIMIT_OFILE, &rlim) < 0) {
+# endif
+    /* Ignore ENOSYS (and EPERM, since some libc's use this as ENOSYS); pick
+     * some arbitrary high number.
+     */
+    nfiles = FSIO_MAX_FD_COUNT;
+
+  } else {
+    nfiles = rlim.rlim_max;
+  }
+
+#else /* no RLIMIT_NOFILE or RLIMIT_OFILE */
+   nfiles = FSIO_MAX_FD_COUNT;
+#endif
+
+  /* Yes, using a long for the nfiles variable is not quite kosher; it should
+   * be an unsigned type, otherwise a large limit (say, RLIMIT_INFINITY)
+   * might overflow the data type.  In that case, though, we want to know
+   * about it -- and using a signed type, we will know if the overflowed
+   * value is a negative number.  Chances are we do NOT want to be closing
+   * fds whose value is as high as they can possibly get; that's too many
+   * fds to iterate over.  Long story short, using a long int is just fine.
+   * (Plus it makes mod_exec work on Mac OSX 10.4; without this tweak,
+   * mod_exec's forked processes never return/exit.)
+   */
+
+  if (nfiles < 0 ||
+      nfiles > FSIO_MAX_FD_COUNT) {
+    nfiles = FSIO_MAX_FD_COUNT;
+  }
+
+  /* Close the "non-standard" file descriptors. */
+  for (i = 3; i < nfiles; i++) {
+    /* This is a potentially long-running loop, so handle signals. */
+    pr_signals_handle();
+    (void) close(i);
+  }
+}
+
 /* Be generous in the maximum allowed number of dup fds, in our search for
  * one that is outside the big three.
  *
Index: proftpd-dfsg-1.3.6/src/main.c
===================================================================
--- proftpd-dfsg-1.3.6.orig/src/main.c	2018-02-20 21:24:24.000000000 +0100
+++ proftpd-dfsg-1.3.6/src/main.c	2018-02-20 21:24:24.000000000 +0100
@@ -2232,6 +2232,7 @@
 
   memset(&session, 0, sizeof(session));
 
+  pr_fs_close_extra_fds();
   pr_proctitle_init(argc, argv, envp);
 
   /* Seed rand */
Index: proftpd-dfsg-1.3.6/tests/api/fsio.c
===================================================================
--- proftpd-dfsg-1.3.6.orig/tests/api/fsio.c	2018-02-20 21:24:24.000000000 +0100
+++ proftpd-dfsg-1.3.6/tests/api/fsio.c	2018-02-20 21:24:24.000000000 +0100
@@ -3888,6 +3888,12 @@
 }
 END_TEST
 
+START_TEST (fs_close_extra_fds_test) {
+  mark_point();
+  pr_fs_close_extra_fds();
+}
+END_TEST
+
 START_TEST (fs_get_usable_fd_test) {
   int fd, res;
 
@@ -4630,6 +4636,7 @@
   tcase_add_test(testcase, fs_split_path_test);
   tcase_add_test(testcase, fs_join_path_test);
   tcase_add_test(testcase, fs_virtual_path_test);
+  tcase_add_test(testcase, fs_close_extra_fds_test);
   tcase_add_test(testcase, fs_get_usable_fd_test);
   tcase_add_test(testcase, fs_get_usable_fd2_test);
   tcase_add_test(testcase, fs_getsize_test);
