[glib: 1/2] gspawn: Fix g_spawn deadlock in a multi-threaded program on Linux
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib: 1/2] gspawn: Fix g_spawn deadlock in a multi-threaded program on Linux
- Date: Mon, 26 Nov 2018 10:49:12 +0000 (UTC)
commit f2917459f745bebf931bccd5cc2c33aa81ef4d12
Author: Peter Wu <peter lekensteyn nl>
Date: Sat Nov 24 13:22:57 2018 +0100
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)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]