dup2() may return an fd other than the desired fd if other threads are
involved.

fcntl() may close the fd used by other threads accidentally.

* lib/dup2.c (klibc_dup2dirfd): Ensure duplication to the desired fd.
(klibd_dup2): Do not close the desired fd.
* lib/fcntl.c (klibc_dupdirfd): New.
(klibc_fcntl): Use klibc_dupdirfd() instead of dup2().
---
 lib/dup2.c  | 32 +++++++++++++++++++++++++-------
 lib/fcntl.c | 42 +++++++++++++++++++++++++++++++++++++-----
 2 files changed, 62 insertions(+), 12 deletions(-)

diff --git a/lib/dup2.c b/lib/dup2.c
index 69b37196de..3468fbcb6e 100644
--- a/lib/dup2.c
+++ b/lib/dup2.c
@@ -117,7 +117,7 @@ klibc_dup2dirfd (int fd, int desired_fd)
   if (tempfd == -1)
     return -1;
 
-  if (tempfd == desired_fd)
+  if (tempfd >= desired_fd)
     {
       close (tempfd);
 
@@ -125,7 +125,29 @@ klibc_dup2dirfd (int fd, int desired_fd)
       if (__libc_Back_ioFHToPath (fd, path, sizeof (path)))
         return -1;
 
-      return open(path, O_RDONLY);
+      for (;;)
+        {
+          close (desired_fd);
+
+          dupfd = open (path, O_RDONLY);
+          if (dupfd == -1)
+            return -1;
+
+          if (dupfd == desired_fd)
+            return dupfd;
+
+          /* If lower FD was closed by other threads, fill again.  */
+          if (dupfd < desired_fd)
+            {
+              tempfd = dupfd;
+              break;
+            }
+
+          /* desired_fd was opened by other threads. Try again.  */
+          /* FIXME: Closing desired_fd opened by other threads may lead to
+             unexpected behavior.  */
+          close (dupfd);
+        }
     }
 
   dupfd = klibc_dup2dirfd (fd, desired_fd);
@@ -144,11 +166,7 @@ klibc_dup2 (int fd, int desired_fd)
   dupfd = dup2 (fd, desired_fd);
   if (dupfd == -1 && errno == ENOTSUP \
       && !fstat (fd, &sbuf) && S_ISDIR (sbuf.st_mode))
-    {
-      close (desired_fd);
-
-      return klibc_dup2dirfd (fd, desired_fd);
-    }
+    return klibc_dup2dirfd (fd, desired_fd);
 
   return dupfd;
 }
diff --git a/lib/fcntl.c b/lib/fcntl.c
index f4cf86cf2e..abdd7b8989 100644
--- a/lib/fcntl.c
+++ b/lib/fcntl.c
@@ -30,6 +30,7 @@
 
 #ifdef __KLIBC__
 # include <emx/io.h>
+# include <InnoTekLIBC/backend.h>
 #endif
 
 #if defined _WIN32 && ! defined __CYGWIN__
@@ -538,6 +539,41 @@ rpl_fcntl_DUPFD_CLOEXEC (int fd, int target)
 #undef fcntl
 
 #ifdef __KLIBC__
+static int
+klibc_dupdirfd (int fd, int minfd)
+{
+  int tempfd;
+  int dupfd;
+
+  tempfd = open ("NUL", O_RDONLY);
+  if (tempfd == -1)
+    return -1;
+
+  if (tempfd >= minfd)
+    {
+      close (tempfd);
+
+      char path[_MAX_PATH];
+      if (__libc_Back_ioFHToPath (fd, path, sizeof (path)))
+        return -1;
+
+      dupfd = open (path, O_RDONLY);
+      if (dupfd == -1)
+        return -1;
+
+      if (dupfd >= minfd)
+        return dupfd;
+
+      /* Lower FD was closed by other threads. Fill again.  */
+      tempfd = dupfd;
+    }
+
+  dupfd = klibc_dupdirfd (fd, minfd);
+
+  close (tempfd);
+
+  return dupfd;
+}
 
 static int
 klibc_fcntl (int fd, int action, /* arg */...)
@@ -560,11 +596,7 @@ klibc_fcntl (int fd, int action, /* arg */...)
       switch (action)
         {
         case F_DUPFD:
-          /* Find available fd */
-          while (fcntl (arg, F_GETFL) != -1 || errno != EBADF)
-            arg++;
-
-          result = dup2 (fd, arg);
+          result = klibc_dupdirfd (fd, arg);
           break;
 
         case F_GETFD:
-- 
2.50.1


Reply via email to