From f2917459f745bebf931bccd5cc2c33aa81ef4d12 Mon Sep 17 00:00:00 2001
From: Peter Wu <peter@lekensteyn.nl>
Date: Sat, 24 Nov 2018 13:22:57 +0100
Subject: [PATCH 301/682] gspawn: Fix g_spawn deadlock in a multi-threaded
 program on Linux

opendir and closedir are not async-signal-safe, these may call malloc
under the hood and cause a deadlock in a multi-threaded program.
This only affected Linux when /proc is mounted, other systems use a
slower path that iterates through all potential file descriptors.
Fixes a long-standing problem (since GLib 2.14.2).

Closes #945 and #1014
---
 glib/gspawn.c | 94 +++++++++++++++++++++++++++++++++++----------------
 1 file changed, 65 insertions(+), 29 deletions(-)

diff --git a/glib/gspawn.c b/glib/gspawn.c
index 23ade06ae..69d3fec10 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -47,6 +47,10 @@
 #include <sys/resource.h>
 #endif /* HAVE_SYS_RESOURCE_H */
 
+#ifdef __linux__
+#include <sys/syscall.h>  /* for syscall and SYS_getdents64 */
+#endif
+
 #include "gspawn.h"
 #include "gspawn-private.h"
 #include "gthread.h"
@@ -1125,6 +1129,44 @@ set_cloexec (void *data, gint fd)
 }
 
 #ifndef HAVE_FDWALK
+#ifdef __linux__
+struct linux_dirent64
+{
+  guint64        d_ino;    /* 64-bit inode number */
+  guint64        d_off;    /* 64-bit offset to next structure */
+  unsigned short d_reclen; /* Size of this dirent */
+  unsigned char  d_type;   /* File type */
+  char           d_name[]; /* Filename (null-terminated) */
+};
+
+static gint
+filename_to_fd (const char *p)
+{
+  char c;
+  int fd = 0;
+  const int cutoff = G_MAXINT / 10;
+  const int cutlim = G_MAXINT % 10;
+
+  if (*p == '\0')
+    return -1;
+
+  while ((c = *p++) != '\0')
+    {
+      if (!g_ascii_isdigit (c))
+        return -1;
+      c -= '0';
+
+      /* Check for overflow. */
+      if (fd > cutoff || (fd == cutoff && c > cutlim))
+        return -1;
+
+      fd = fd * 10 + c;
+    }
+
+  return fd;
+}
+#endif
+
 static int
 fdwalk (int (*cb)(void *data, int fd), void *data)
 {
@@ -1136,45 +1178,39 @@ fdwalk (int (*cb)(void *data, int fd), void *data)
   struct rlimit rl;
 #endif
 
-#ifdef __linux__  
-  DIR *d;
-
-  if ((d = opendir("/proc/self/fd"))) {
-      struct dirent *de;
-
-      while ((de = readdir(d))) {
-          glong l;
-          gchar *e = NULL;
-
-          if (de->d_name[0] == '.')
-              continue;
-            
-          errno = 0;
-          l = strtol(de->d_name, &e, 10);
-          if (errno != 0 || !e || *e)
-              continue;
-
-          fd = (gint) l;
+#ifdef __linux__
+  /* Avoid use of opendir/closedir since these are not async-signal-safe. */
+  int dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY);
+  if (dir_fd >= 0)
+    {
+      char buf[4096];
+      int pos, nread;
+      struct linux_dirent64 *de;
 
-          if ((glong) fd != l)
-              continue;
+      while ((nread = syscall (SYS_getdents64, dir_fd, buf, sizeof(buf))) > 0)
+        {
+          for (pos = 0; pos < nread; pos += de->d_reclen)
+            {
+              de = (struct linux_dirent64 *)(buf + pos);
 
-          if (fd == dirfd(d))
-              continue;
+              fd = filename_to_fd (de->d_name);
+              if (fd < 0 || fd == dir_fd)
+                  continue;
 
-          if ((res = cb (data, fd)) != 0)
-              break;
+              if ((res = cb (data, fd)) != 0)
+                  break;
+            }
         }
-      
-      closedir(d);
+
+      close (dir_fd);
       return res;
-  }
+    }
 
   /* If /proc is not mounted or not accessible we fall back to the old
    * rlimit trick */
 
 #endif
-  
+
 #ifdef HAVE_SYS_RESOURCE_H
       
   if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
-- 
2.19.1

