utils: Directly use syscall() to close open FDs in closefrom()
authorTobias Brunner <tobias@strongswan.org>
Thu, 11 Jun 2015 10:19:56 +0000 (12:19 +0200)
committerTobias Brunner <tobias@strongswan.org>
Mon, 17 Aug 2015 09:19:44 +0000 (11:19 +0200)
This avoids any allocations, since calling malloc() after fork() is
potentially unsafe.

Fixes #990.

configure.ac
src/libstrongswan/utils/utils.c

index be296fb..1813275 100644 (file)
@@ -591,7 +591,7 @@ AC_CHECK_FUNC([syslog], [
 ])
 AM_CONDITIONAL(USE_SYSLOG, [test "x$syslog" = xtrue])
 
-AC_CHECK_HEADERS(sys/sockio.h glob.h net/if_tun.h)
+AC_CHECK_HEADERS(sys/sockio.h sys/syscall.h glob.h net/if_tun.h)
 AC_CHECK_HEADERS(net/pfkeyv2.h netipsec/ipsec.h netinet6/ipsec.h linux/udp.h)
 AC_CHECK_HEADERS([netinet/ip6.h linux/fib_rules.h], [], [],
 [
index 55eab8e..c396540 100644 (file)
 #endif
 
 #ifndef HAVE_CLOSEFROM
+#if defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <sys/syscall.h>
+/* This is from the kernel sources.  We limit the length of directory names to
+ * 256 as we only use it to enumerate FDs. */
+struct linux_dirent64 {
+       u_int64_t d_ino;
+       int64_t d_off;
+       unsigned short  d_reclen;
+       unsigned char d_type;
+       char d_name[256];
+};
+#else /* !defined(__linux__) || !defined(HAVE_SYS_SYSCALL_H) */
 # include <dirent.h>
+#endif /* defined(__linux__) && defined(HAVE_SYS_SYSCALL_H) */
 #endif
 
 #include <library.h>
@@ -120,14 +135,46 @@ void wait_sigint()
  */
 void closefrom(int low_fd)
 {
+       int max_fd, dir_fd, fd;
+
+       /* try to close only open file descriptors on Linux... */
+#if defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)
+       /* By directly using a syscall we avoid any calls that might be unsafe after
+        * fork() (e.g. malloc()). */
+       char buffer[sizeof(struct linux_dirent64)];
+       struct linux_dirent64 *entry;
+       int offset, len;
+
+       dir_fd = open("/proc/self/fd", O_RDONLY);
+       if (dir_fd != -1)
+       {
+               while ((len = syscall(SYS_getdents64, dir_fd, buffer,
+                                                         sizeof(buffer))) > 0)
+               {
+                       for (offset = 0; offset < len; offset += entry->d_reclen)
+                       {
+                               entry = (struct linux_dirent64*)(buffer + offset);
+                               if (!isdigit(entry->d_name[0]))
+                               {
+                                       continue;
+                               }
+                               fd = atoi(entry->d_name);
+                               if (fd != dir_fd && fd >= low_fd)
+                               {
+                                       close(fd);
+                               }
+                       }
+               }
+               close(dir_fd);
+               return;
+       }
+#else /* !defined(__linux__) || !defined(HAVE_SYS_SYSCALL_H) */
+       /* This is potentially unsafe when called after fork() in multi-threaded
+        * applications.  In particular opendir() will require an allocation.
+        * Depends on how the malloc() implementation handles such situations. */
        DIR *dir;
        struct dirent *entry;
-       int max_fd, dir_fd, fd;
 
-       /* try to close only open file descriptors on Linux... This is potentially
-        * unsafe when called after fork() in multi-threaded applications.  In
-        * particular opendir() will require an allocation.  So it depends on how
-        * the malloc() implementation handles such situations */
        dir = opendir(FD_DIR);
        if (dir)
        {
@@ -147,6 +194,7 @@ void closefrom(int low_fd)
                closedir(dir);
                return;
        }
+#endif /* defined(__linux__) && defined(HAVE_SYS_SYSCALL_H) */
 
        /* ...fall back to closing all fds otherwise */
 #ifdef WIN32