1f3217
From 9e69f8b280afe8eccd9188cc53b8117e1b238db7 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Tue, 12 Oct 2021 15:52:18 -0500
1f3217
Subject: [PATCH 01/10] gspawn: use close_and_invalidate more
1f3217
1f3217
---
1f3217
 glib/gspawn.c | 2 +-
1f3217
 1 file changed, 1 insertion(+), 1 deletion(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index a15fb1ca1..5d8422869 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -1710,7 +1710,7 @@ do_exec (gint                  child_err_report_fd,
1f3217
                 child_err_report_fd = safe_dup (child_err_report_fd);
1f3217
 
1f3217
               safe_dup2 (source_fds[i], target_fds[i]);
1f3217
-              (void) close (source_fds[i]);
1f3217
+              close_and_invalidate (&source_fds[i]);
1f3217
             }
1f3217
         }
1f3217
     }
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From fe2148fd5dd4f2e5c413c5cc0bb56c4a19304887 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Thu, 14 Oct 2021 10:43:52 -0500
1f3217
Subject: [PATCH 02/10] gspawn: Improve error message when dup fails
1f3217
1f3217
This error message is no longer accurate now that we allow arbitrary fd
1f3217
remapping.
1f3217
---
1f3217
 glib/gspawn.c | 2 +-
1f3217
 1 file changed, 1 insertion(+), 1 deletion(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index 5d8422869..e214a3998 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -2363,7 +2363,7 @@ fork_exec (gboolean              intermediate_child,
1f3217
               g_set_error (error,
1f3217
                            G_SPAWN_ERROR,
1f3217
                            G_SPAWN_ERROR_FAILED,
1f3217
-                           _("Failed to redirect output or input of child process (%s)"),
1f3217
+                           _("Failed to duplicate file descriptor for child process (%s)"),
1f3217
                            g_strerror (buf[1]));
1f3217
 
1f3217
               break;
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From 566eccdb0a2594b4d3ec13c7443028d968b41af8 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Tue, 12 Oct 2021 15:33:59 -0500
1f3217
Subject: [PATCH 03/10] gspawn: fix hangs when duping child_err_report_fd
1f3217
1f3217
In case child_err_report_fd conflicts with one of the target_fds, the
1f3217
code here is careful to dup child_err_report_fd in order to avoid
1f3217
conflating the two. It was a good idea, but evidently was not tested,
1f3217
because the newly-created fd is not created with CLOEXEC set. This means
1f3217
it stays open in the child process, causing the parent to hang forever
1f3217
waiting to read from the other end of the pipe. Oops!
1f3217
1f3217
The fix is simple: just set CLOEXEC. This removes our only usage of the
1f3217
safe_dup() function, so it can be dropped.
1f3217
1f3217
Fixes #2506
1f3217
---
1f3217
 glib/gspawn.c | 16 +---------------
1f3217
 1 file changed, 1 insertion(+), 15 deletions(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index e214a3998..8bbe573f7 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -1500,20 +1500,6 @@ safe_closefrom (int lowfd)
1f3217
 #endif
1f3217
 }
1f3217
 
1f3217
-/* This function is called between fork() and exec() and hence must be
1f3217
- * async-signal-safe (see signal-safety(7)). */
1f3217
-static gint
1f3217
-safe_dup (gint fd)
1f3217
-{
1f3217
-  gint ret;
1f3217
-
1f3217
-  do
1f3217
-    ret = dup (fd);
1f3217
-  while (ret < 0 && (errno == EINTR || errno == EBUSY));
1f3217
-
1f3217
-  return ret;
1f3217
-}
1f3217
-
1f3217
 /* This function is called between fork() and exec() and hence must be
1f3217
  * async-signal-safe (see signal-safety(7)). */
1f3217
 static gint
1f3217
@@ -1707,7 +1693,7 @@ do_exec (gint                  child_err_report_fd,
1f3217
           else
1f3217
             {
1f3217
               if (target_fds[i] == child_err_report_fd)
1f3217
-                child_err_report_fd = safe_dup (child_err_report_fd);
1f3217
+                child_err_report_fd = dupfd_cloexec (child_err_report_fd);
1f3217
 
1f3217
               safe_dup2 (source_fds[i], target_fds[i]);
1f3217
               close_and_invalidate (&source_fds[i]);
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From b703fa8b760ac9272c5a0ed3e3763b2f71ecf574 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Thu, 14 Oct 2021 10:44:57 -0500
1f3217
Subject: [PATCH 04/10] gspawn: fix fd remapping conflation issue
1f3217
1f3217
We currently dup all source fds to avoid possible conflation with the
1f3217
target fds, but fail to consider that the result of a dup might itself
1f3217
conflict with one of the target fds. Solve this the easy way by duping
1f3217
all source_fds to values that are greater than the largest fd in
1f3217
target_fds.
1f3217
1f3217
Fixes #2503
1f3217
---
1f3217
 glib/gspawn.c | 43 +++++++++++++++++++++++++------------------
1f3217
 1 file changed, 25 insertions(+), 18 deletions(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index 8bbe573f7..2b48b5600 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -1258,13 +1258,13 @@ unset_cloexec (int fd)
1f3217
 /* This function is called between fork() and exec() and hence must be
1f3217
  * async-signal-safe (see signal-safety(7)). */
1f3217
 static int
1f3217
-dupfd_cloexec (int parent_fd)
1f3217
+dupfd_cloexec (int old_fd, int new_fd_min)
1f3217
 {
1f3217
   int fd, errsv;
1f3217
 #ifdef F_DUPFD_CLOEXEC
1f3217
   do
1f3217
     {
1f3217
-      fd = fcntl (parent_fd, F_DUPFD_CLOEXEC, 3);
1f3217
+      fd = fcntl (old_fd, F_DUPFD_CLOEXEC, new_fd_min);
1f3217
       errsv = errno;
1f3217
     }
1f3217
   while (fd == -1 && errsv == EINTR);
1f3217
@@ -1275,7 +1275,7 @@ dupfd_cloexec (int parent_fd)
1f3217
   int result, flags;
1f3217
   do
1f3217
     {
1f3217
-      fd = fcntl (parent_fd, F_DUPFD, 3);
1f3217
+      fd = fcntl (old_fd, F_DUPFD, new_fd_min);
1f3217
       errsv = errno;
1f3217
     }
1f3217
   while (fd == -1 && errsv == EINTR);
1f3217
@@ -1563,6 +1563,7 @@ do_exec (gint                  child_err_report_fd,
1f3217
          gpointer              user_data)
1f3217
 {
1f3217
   gsize i;
1f3217
+  gint max_target_fd = 0;
1f3217
 
1f3217
   if (working_directory && chdir (working_directory) < 0)
1f3217
     write_err_and_exit (child_err_report_fd,
1f3217
@@ -1661,39 +1662,45 @@ do_exec (gint                  child_err_report_fd,
1f3217
   /*
1f3217
    * Work through the @source_fds and @target_fds mapping.
1f3217
    *
1f3217
-   * Based on code derived from
1f3217
+   * Based on code originally derived from
1f3217
    * gnome-terminal:src/terminal-screen.c:terminal_screen_child_setup(),
1f3217
-   * used under the LGPLv2+ with permission from author.
1f3217
+   * used under the LGPLv2+ with permission from author. (The code has
1f3217
+   * since migrated to vte:src/spawn.cc:SpawnContext::exec and is no longer
1f3217
+   * terribly similar to what we have here.)
1f3217
    */
1f3217
 
1f3217
-  /* Basic fd assignments (where source == target) we can just unset FD_CLOEXEC
1f3217
-   *
1f3217
-   * If we're doing remapping fd assignments, we need to handle
1f3217
-   * the case where the user has specified e.g.:
1f3217
-   * 5 -> 4, 4 -> 6
1f3217
-   *
1f3217
-   * We do this by duping the source fds temporarily in a first pass.
1f3217
-   *
1f3217
-   * If any of the @target_fds conflict with @child_err_report_fd, dup the
1f3217
-   * latter so it doesn’t get conflated.
1f3217
-   */
1f3217
   if (n_fds > 0)
1f3217
     {
1f3217
+      for (i = 0; i < n_fds; i++)
1f3217
+        max_target_fd = MAX (max_target_fd, target_fds[i]);
1f3217
+
1f3217
+      /* If we're doing remapping fd assignments, we need to handle
1f3217
+       * the case where the user has specified e.g. 5 -> 4, 4 -> 6.
1f3217
+       * We do this by duping all source fds, taking care to ensure the new
1f3217
+       * fds are larger than any target fd to avoid introducing new conflicts.
1f3217
+       */
1f3217
       for (i = 0; i < n_fds; i++)
1f3217
         {
1f3217
           if (source_fds[i] != target_fds[i])
1f3217
-            source_fds[i] = dupfd_cloexec (source_fds[i]);
1f3217
+            source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
1f3217
         }
1f3217
+
1f3217
       for (i = 0; i < n_fds; i++)
1f3217
         {
1f3217
+          /* For basic fd assignments (where source == target), we can just
1f3217
+           * unset FD_CLOEXEC.
1f3217
+           */
1f3217
           if (source_fds[i] == target_fds[i])
1f3217
             {
1f3217
               unset_cloexec (source_fds[i]);
1f3217
             }
1f3217
           else
1f3217
             {
1f3217
+              /* If any of the @target_fds conflict with @child_err_report_fd,
1f3217
+               * dup it so it doesn’t get conflated.
1f3217
+               */
1f3217
               if (target_fds[i] == child_err_report_fd)
1f3217
-                child_err_report_fd = dupfd_cloexec (child_err_report_fd);
1f3217
+                child_err_report_fd = dupfd_cloexec (child_err_report_fd, max_target_fd + 1);
1f3217
 
1f3217
               safe_dup2 (source_fds[i], target_fds[i]);
1f3217
               close_and_invalidate (&source_fds[i]);
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From ecc3538a942760e8b403c319d359711c8e166778 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@gnome.org>
1f3217
Date: Thu, 25 Feb 2021 12:20:39 -0600
1f3217
Subject: [PATCH 05/10] gspawn: Implement fd remapping for posix_spawn codepath
1f3217
1f3217
This means that GSubprocess will (sometimes) be able to use the
1f3217
optimized posix_spawn codepath instead of having to fall back to
1f3217
fork/exec.
1f3217
---
1f3217
 glib/gspawn.c | 65 +++++++++++++++++++++++++++++++++++++++++++++------
1f3217
 1 file changed, 58 insertions(+), 7 deletions(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index 2b48b5600..9ef78dbe1 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -1786,9 +1786,14 @@ do_posix_spawn (const gchar * const *argv,
1f3217
                 gint       *child_close_fds,
1f3217
                 gint        stdin_fd,
1f3217
                 gint        stdout_fd,
1f3217
-                gint        stderr_fd)
1f3217
+                gint        stderr_fd,
1f3217
+                const gint *source_fds,
1f3217
+                const gint *target_fds,
1f3217
+                gsize       n_fds)
1f3217
 {
1f3217
   pid_t pid;
1f3217
+  gint *duped_source_fds = NULL;
1f3217
+  gint max_target_fd = 0;
1f3217
   const gchar * const *argv_pass;
1f3217
   posix_spawnattr_t attr;
1f3217
   posix_spawn_file_actions_t file_actions;
1f3217
@@ -1797,7 +1802,8 @@ do_posix_spawn (const gchar * const *argv,
1f3217
   GSList *child_close = NULL;
1f3217
   GSList *elem;
1f3217
   sigset_t mask;
1f3217
-  int i, r;
1f3217
+  size_t i;
1f3217
+  int r;
1f3217
 
1f3217
   if (*argv[0] == '\0')
1f3217
     {
1f3217
@@ -1911,6 +1917,43 @@ do_posix_spawn (const gchar * const *argv,
1f3217
         goto out_close_fds;
1f3217
     }
1f3217
 
1f3217
+  /* If source_fds[i] != target_fds[i], we need to handle the case
1f3217
+   * where the user has specified, e.g., 5 -> 4, 4 -> 6. We do this
1f3217
+   * by duping the source fds, taking care to ensure the new fds are
1f3217
+   * larger than any target fd to avoid introducing new conflicts.
1f3217
+   *
1f3217
+   * If source_fds[i] == target_fds[i], then we just need to leak
1f3217
+   * the fd into the child process, which we *could* do by temporarily
1f3217
+   * unsetting CLOEXEC and then setting it again after we spawn if
1f3217
+   * it was originally set. POSIX requires that the addup2 action unset
1f3217
+   * CLOEXEC if source and target are identical, so you'd think doing it
1f3217
+   * manually wouldn't be needed, but unfortunately as of 2021 many
1f3217
+   * libcs still don't do so. Example nonconforming libcs:
1f3217
+   *  Bionic: https://android.googlesource.com/platform/bionic/+/f6e5b582604715729b09db3e36a7aeb8c24b36a4/libc/bionic/spawn.cpp#71
1f3217
+   *  uclibc-ng: https://cgit.uclibc-ng.org/cgi/cgit/uclibc-ng.git/tree/librt/spawn.c?id=7c36bcae09d66bbaa35cbb02253ae0556f42677e#n88
1f3217
+   *
1f3217
+   * Anyway, unsetting CLOEXEC ourselves would open a small race window
1f3217
+   * where the fd could be inherited into a child process if another
1f3217
+   * thread spawns something at the same time, because we have not
1f3217
+   * called fork() and are multithreaded here. This race is avoidable by
1f3217
+   * using dupfd_cloexec, which we already have to do to handle the
1f3217
+   * source_fds[i] != target_fds[i] case. So let's always do it!
1f3217
+   */
1f3217
+
1f3217
+  for (i = 0; i < n_fds; i++)
1f3217
+    max_target_fd = MAX (max_target_fd, target_fds[i]);
1f3217
+
1f3217
+  duped_source_fds = g_new (gint, n_fds);
1f3217
+  for (i = 0; i < n_fds; i++)
1f3217
+    duped_source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
1f3217
+
1f3217
+  for (i = 0; i < n_fds; i++)
1f3217
+    {
1f3217
+      r = posix_spawn_file_actions_adddup2 (&file_actions, duped_source_fds[i], target_fds[i]);
1f3217
+      if (r != 0)
1f3217
+        goto out_close_fds;
1f3217
+    }
1f3217
+
1f3217
   /* Intentionally close the fds in the child as the last file action,
1f3217
    * having been careful not to add the same fd to this list twice.
1f3217
    *
1f3217
@@ -1940,9 +1983,16 @@ do_posix_spawn (const gchar * const *argv,
1f3217
     *child_pid = pid;
1f3217
 
1f3217
 out_close_fds:
1f3217
-  for (i = 0; i < num_parent_close_fds; i++)
1f3217
+  for (i = 0; i < (size_t) num_parent_close_fds; i++)
1f3217
     close_and_invalidate (&parent_close_fds [i]);
1f3217
 
1f3217
+  if (duped_source_fds != NULL)
1f3217
+    {
1f3217
+      for (i = 0; i < n_fds; i++)
1f3217
+        close_and_invalidate (&duped_source_fds[i]);
1f3217
+      g_free (duped_source_fds);
1f3217
+    }
1f3217
+
1f3217
   posix_spawn_file_actions_destroy (&file_actions);
1f3217
 out_free_spawnattr:
1f3217
   posix_spawnattr_destroy (&attr);
1f3217
@@ -2030,10 +2080,8 @@ fork_exec (gboolean              intermediate_child,
1f3217
   child_close_fds[n_child_close_fds++] = -1;
1f3217
 
1f3217
 #ifdef POSIX_SPAWN_AVAILABLE
1f3217
-  /* FIXME: Handle @source_fds and @target_fds in do_posix_spawn() using the
1f3217
-   * file actions API. */
1f3217
   if (!intermediate_child && working_directory == NULL && !close_descriptors &&
1f3217
-      !search_path_from_envp && child_setup == NULL && n_fds == 0)
1f3217
+      !search_path_from_envp && child_setup == NULL)
1f3217
     {
1f3217
       g_trace_mark (G_TRACE_CURRENT_TIME, 0,
1f3217
                     "GLib", "posix_spawn",
1f3217
@@ -2050,7 +2098,10 @@ fork_exec (gboolean              intermediate_child,
1f3217
                                child_close_fds,
1f3217
                                stdin_fd,
1f3217
                                stdout_fd,
1f3217
-                               stderr_fd);
1f3217
+                               stderr_fd,
1f3217
+                               source_fds,
1f3217
+                               target_fds,
1f3217
+                               n_fds);
1f3217
       if (status == 0)
1f3217
         goto success;
1f3217
 
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From 731d6c32105dc97f2b777ef9a34c6b76d1489c04 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@gnome.org>
1f3217
Date: Thu, 25 Feb 2021 12:21:38 -0600
1f3217
Subject: [PATCH 06/10] gsubprocess: ensure we test fd remapping on the
1f3217
 posix_spawn() codepath
1f3217
1f3217
We should run test_pass_fd twice, once using gspawn's fork/exec codepath
1f3217
and once attempting to use its posix_spawn() codepath. There's no
1f3217
guarantee we'll actually get the posix_spawn() codepath, but it works
1f3217
for now on Linux.
1f3217
1f3217
For good measure, run it a third time with no flags at all.
1f3217
1f3217
This causes the test to fail if I separately break the fd remapping
1f3217
implementation. Without this, we fail to test fd remapping on the
1f3217
posix_spawn() codepath.
1f3217
---
1f3217
 gio/tests/gsubprocess.c | 44 ++++++++++++++++++++++++++++++++++++++---
1f3217
 1 file changed, 41 insertions(+), 3 deletions(-)
1f3217
1f3217
diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c
1f3217
index 7e22678ec..ba49c1c43 100644
1f3217
--- a/gio/tests/gsubprocess.c
1f3217
+++ b/gio/tests/gsubprocess.c
1f3217
@@ -1697,7 +1697,8 @@ test_child_setup (void)
1f3217
 }
1f3217
 
1f3217
 static void
1f3217
-test_pass_fd (void)
1f3217
+do_test_pass_fd (GSubprocessFlags     flags,
1f3217
+                 GSpawnChildSetupFunc child_setup)
1f3217
 {
1f3217
   GError *local_error = NULL;
1f3217
   GError **error = &local_error;
1f3217
@@ -1722,9 +1723,11 @@ test_pass_fd (void)
1f3217
   needdup_fd_str = g_strdup_printf ("%d", needdup_pipefds[1] + 1);
1f3217
 
1f3217
   args = get_test_subprocess_args ("write-to-fds", basic_fd_str, needdup_fd_str, NULL);
1f3217
-  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
1f3217
+  launcher = g_subprocess_launcher_new (flags);
1f3217
   g_subprocess_launcher_take_fd (launcher, basic_pipefds[1], basic_pipefds[1]);
1f3217
   g_subprocess_launcher_take_fd (launcher, needdup_pipefds[1], needdup_pipefds[1] + 1);
1f3217
+  if (child_setup != NULL)
1f3217
+    g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL);
1f3217
   proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error);
1f3217
   g_ptr_array_free (args, TRUE);
1f3217
   g_assert_no_error (local_error);
1f3217
@@ -1754,6 +1757,39 @@ test_pass_fd (void)
1f3217
   g_object_unref (proc);
1f3217
 }
1f3217
 
1f3217
+static void
1f3217
+test_pass_fd (void)
1f3217
+{
1f3217
+  do_test_pass_fd (G_SUBPROCESS_FLAGS_NONE, NULL);
1f3217
+}
1f3217
+
1f3217
+static void
1f3217
+empty_child_setup (gpointer user_data)
1f3217
+{
1f3217
+}
1f3217
+
1f3217
+static void
1f3217
+test_pass_fd_empty_child_setup (void)
1f3217
+{
1f3217
+  /* Using a child setup function forces gspawn to use fork/exec
1f3217
+   * rather than posix_spawn.
1f3217
+   */
1f3217
+  do_test_pass_fd (G_SUBPROCESS_FLAGS_NONE, empty_child_setup);
1f3217
+}
1f3217
+
1f3217
+static void
1f3217
+test_pass_fd_inherit_fds (void)
1f3217
+{
1f3217
+  /* Try to test the optimized posix_spawn codepath instead of
1f3217
+   * fork/exec. Currently this requires using INHERIT_FDS since gspawn's
1f3217
+   * posix_spawn codepath does not currently handle closing
1f3217
+   * non-inherited fds. Note that using INHERIT_FDS means our testing of
1f3217
+   * g_subprocess_launcher_take_fd() is less-comprehensive than when
1f3217
+   * using G_SUBPROCESS_FLAGS_NONE.
1f3217
+   */
1f3217
+  do_test_pass_fd (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL);
1f3217
+}
1f3217
+
1f3217
 #endif
1f3217
 
1f3217
 static void
1f3217
@@ -1891,7 +1927,9 @@ main (int argc, char **argv)
1f3217
   g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file);
1f3217
   g_test_add_func ("/gsubprocess/stdout-fd", test_stdout_fd);
1f3217
   g_test_add_func ("/gsubprocess/child-setup", test_child_setup);
1f3217
-  g_test_add_func ("/gsubprocess/pass-fd", test_pass_fd);
1f3217
+  g_test_add_func ("/gsubprocess/pass-fd/basic", test_pass_fd);
1f3217
+  g_test_add_func ("/gsubprocess/pass-fd/empty-child-setup", test_pass_fd_empty_child_setup);
1f3217
+  g_test_add_func ("/gsubprocess/pass-fd/inherit-fds", test_pass_fd_inherit_fds);
1f3217
 #endif
1f3217
   g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment);
1f3217
 
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From 4608940466a04a32d4e6e71dbe872cfecb136118 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Thu, 14 Oct 2021 11:01:33 -0500
1f3217
Subject: [PATCH 07/10] gspawn: Check from errors from safe_dup2() and
1f3217
 dupfd_cloexec()
1f3217
1f3217
Although unlikely, these functions can fail, e.g. if we run out of file
1f3217
descriptors. Check for errors to improve robustness. This is especially
1f3217
important now that I changed our use of dupfd_cloexec() to avoid
1f3217
returning fds smaller than the largest fd in target_fds. An application
1f3217
that attempts to remap to the highest-allowed fd value deserves at least
1f3217
some sort of attempt at error reporting, not silent failure.
1f3217
---
1f3217
 glib/gspawn.c | 40 +++++++++++++++++++++++++++++-----------
1f3217
 1 file changed, 29 insertions(+), 11 deletions(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index 9ef78dbe1..7ef678047 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -1572,7 +1572,6 @@ do_exec (gint                  child_err_report_fd,
1f3217
   /* Redirect pipes as required */
1f3217
   if (stdin_fd >= 0)
1f3217
     {
1f3217
-      /* dup2 can't actually fail here I don't think */
1f3217
       if (safe_dup2 (stdin_fd, 0) < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
@@ -1588,13 +1587,14 @@ do_exec (gint                  child_err_report_fd,
1f3217
       if (read_null < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
-      safe_dup2 (read_null, 0);
1f3217
+      if (safe_dup2 (read_null, 0) < 0)
1f3217
+        write_err_and_exit (child_err_report_fd,
1f3217
+                            CHILD_DUP2_FAILED);
1f3217
       close_and_invalidate (&read_null);
1f3217
     }
1f3217
 
1f3217
   if (stdout_fd >= 0)
1f3217
     {
1f3217
-      /* dup2 can't actually fail here I don't think */
1f3217
       if (safe_dup2 (stdout_fd, 1) < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
@@ -1609,13 +1609,14 @@ do_exec (gint                  child_err_report_fd,
1f3217
       if (write_null < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
-      safe_dup2 (write_null, 1);
1f3217
+      if (safe_dup2 (write_null, 1) < 0)
1f3217
+        write_err_and_exit (child_err_report_fd,
1f3217
+                            CHILD_DUP2_FAILED);
1f3217
       close_and_invalidate (&write_null);
1f3217
     }
1f3217
 
1f3217
   if (stderr_fd >= 0)
1f3217
     {
1f3217
-      /* dup2 can't actually fail here I don't think */
1f3217
       if (safe_dup2 (stderr_fd, 2) < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
@@ -1630,7 +1631,9 @@ do_exec (gint                  child_err_report_fd,
1f3217
       if (write_null < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
-      safe_dup2 (write_null, 2);
1f3217
+      if (safe_dup2 (write_null, 2) < 0)
1f3217
+        write_err_and_exit (child_err_report_fd,
1f3217
+                            CHILD_DUP2_FAILED);
1f3217
       close_and_invalidate (&write_null);
1f3217
     }
1f3217
 
1f3217
@@ -1643,7 +1646,8 @@ do_exec (gint                  child_err_report_fd,
1f3217
     {
1f3217
       if (child_setup == NULL && n_fds == 0)
1f3217
         {
1f3217
-          safe_dup2 (child_err_report_fd, 3);
1f3217
+          if (safe_dup2 (child_err_report_fd, 3) < 0)
1f3217
+            write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
1f3217
           set_cloexec (GINT_TO_POINTER (0), 3);
1f3217
           safe_closefrom (4);
1f3217
           child_err_report_fd = 3;
1f3217
@@ -1682,7 +1686,11 @@ do_exec (gint                  child_err_report_fd,
1f3217
       for (i = 0; i < n_fds; i++)
1f3217
         {
1f3217
           if (source_fds[i] != target_fds[i])
1f3217
-            source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
1f3217
+            {
1f3217
+              source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
1f3217
+              if (source_fds[i] < 0)
1f3217
+                write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
1f3217
+            }
1f3217
         }
1f3217
 
1f3217
       for (i = 0; i < n_fds; i++)
1f3217
@@ -1700,9 +1708,15 @@ do_exec (gint                  child_err_report_fd,
1f3217
                * dup it so it doesn’t get conflated.
1f3217
                */
1f3217
               if (target_fds[i] == child_err_report_fd)
1f3217
-                child_err_report_fd = dupfd_cloexec (child_err_report_fd, max_target_fd + 1);
1f3217
+                {
1f3217
+                  child_err_report_fd = dupfd_cloexec (child_err_report_fd, max_target_fd + 1);
1f3217
+                  if (child_err_report_fd < 0)
1f3217
+                    write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
1f3217
+                }
1f3217
+
1f3217
+              if (safe_dup2 (source_fds[i], target_fds[i]) < 0)
1f3217
+                write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
1f3217
 
1f3217
-              safe_dup2 (source_fds[i], target_fds[i]);
1f3217
               close_and_invalidate (&source_fds[i]);
1f3217
             }
1f3217
         }
1f3217
@@ -1945,7 +1959,11 @@ do_posix_spawn (const gchar * const *argv,
1f3217
 
1f3217
   duped_source_fds = g_new (gint, n_fds);
1f3217
   for (i = 0; i < n_fds; i++)
1f3217
-    duped_source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
1f3217
+    {
1f3217
+      duped_source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
1f3217
+      if (duped_source_fds[i] < 0)
1f3217
+        goto out_close_fds;
1f3217
+    }
1f3217
 
1f3217
   for (i = 0; i < n_fds; i++)
1f3217
     {
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From 0198b6a1c8c215f524d7c6ed2d240fb1b31d9865 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Wed, 20 Oct 2021 16:51:44 -0500
1f3217
Subject: [PATCH 08/10] gspawn: add new error message for open() failures
1f3217
1f3217
Reporting these as dup2() failures is bogus.
1f3217
---
1f3217
 glib/gspawn.c | 17 +++++++++++++----
1f3217
 1 file changed, 13 insertions(+), 4 deletions(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index 7ef678047..c2fe306dc 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -1532,6 +1532,7 @@ enum
1f3217
 {
1f3217
   CHILD_CHDIR_FAILED,
1f3217
   CHILD_EXEC_FAILED,
1f3217
+  CHILD_OPEN_FAILED,
1f3217
   CHILD_DUP2_FAILED,
1f3217
   CHILD_FORK_FAILED
1f3217
 };
1f3217
@@ -1586,7 +1587,7 @@ do_exec (gint                  child_err_report_fd,
1f3217
       gint read_null = safe_open ("/dev/null", O_RDONLY);
1f3217
       if (read_null < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
-                            CHILD_DUP2_FAILED);
1f3217
+                            CHILD_OPEN_FAILED);
1f3217
       if (safe_dup2 (read_null, 0) < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
@@ -1608,7 +1609,7 @@ do_exec (gint                  child_err_report_fd,
1f3217
       gint write_null = safe_open ("/dev/null", O_WRONLY);
1f3217
       if (write_null < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
-                            CHILD_DUP2_FAILED);
1f3217
+                            CHILD_OPEN_FAILED);
1f3217
       if (safe_dup2 (write_null, 1) < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
@@ -1630,7 +1631,7 @@ do_exec (gint                  child_err_report_fd,
1f3217
       gint write_null = safe_open ("/dev/null", O_WRONLY);
1f3217
       if (write_null < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
-                            CHILD_DUP2_FAILED);
1f3217
+                            CHILD_OPEN_FAILED);
1f3217
       if (safe_dup2 (write_null, 2) < 0)
1f3217
         write_err_and_exit (child_err_report_fd,
1f3217
                             CHILD_DUP2_FAILED);
1f3217
@@ -2420,7 +2421,15 @@ fork_exec (gboolean              intermediate_child,
1f3217
                            g_strerror (buf[1]));
1f3217
 
1f3217
               break;
1f3217
-              
1f3217
+
1f3217
+            case CHILD_OPEN_FAILED:
1f3217
+              g_set_error (error,
1f3217
+                           G_SPAWN_ERROR,
1f3217
+                           G_SPAWN_ERROR_FAILED,
1f3217
+                           _("Failed to open file to remap file descriptor (%s)"),
1f3217
+                           g_strerror (buf[1]));
1f3217
+              break;
1f3217
+
1f3217
             case CHILD_DUP2_FAILED:
1f3217
               g_set_error (error,
1f3217
                            G_SPAWN_ERROR,
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From e4abb5f3db85b2f730e192e6398f26934e41ba21 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Tue, 26 Oct 2021 21:27:15 -0500
1f3217
Subject: [PATCH 09/10] Add tests for GSubprocess fd conflation issues
1f3217
1f3217
This tests for #2503. It's fragile, but there is no non-fragile way to
1f3217
test this. If the test breaks in the future, it will pass without
1f3217
successfully testing the bug, not fail spuriously, so I think this is
1f3217
OK.
1f3217
---
1f3217
 gio/tests/gsubprocess-testprog.c |  53 +++++++++++-
1f3217
 gio/tests/gsubprocess.c          | 144 +++++++++++++++++++++++++++++++
1f3217
 2 files changed, 195 insertions(+), 2 deletions(-)
1f3217
1f3217
diff --git a/gio/tests/gsubprocess-testprog.c b/gio/tests/gsubprocess-testprog.c
1f3217
index c9b06c2a2..58cb1c71d 100644
1f3217
--- a/gio/tests/gsubprocess-testprog.c
1f3217
+++ b/gio/tests/gsubprocess-testprog.c
1f3217
@@ -5,8 +5,6 @@
1f3217
 #include <errno.h>
1f3217
 #ifdef G_OS_UNIX
1f3217
 #include <unistd.h>
1f3217
-#include <gio/gunixinputstream.h>
1f3217
-#include <gio/gunixoutputstream.h>
1f3217
 #else
1f3217
 #include <io.h>
1f3217
 #endif
1f3217
@@ -150,6 +148,55 @@ write_to_fds (int argc, char **argv)
1f3217
   return 0;
1f3217
 }
1f3217
 
1f3217
+static int
1f3217
+read_from_fd (int argc, char **argv)
1f3217
+{
1f3217
+  int fd;
1f3217
+  const char expectedResult[] = "Yay success!";
1f3217
+  guint8 buf[sizeof (expectedResult) + 1];
1f3217
+  gsize bytes_read;
1f3217
+  FILE *f;
1f3217
+
1f3217
+  if (argc != 3)
1f3217
+    {
1f3217
+      g_print ("Usage: %s read-from-fd FD\n", argv[0]);
1f3217
+      return 1;
1f3217
+    }
1f3217
+
1f3217
+  fd = atoi (argv[2]);
1f3217
+  if (fd == 0)
1f3217
+    {
1f3217
+      g_warning ("Argument \"%s\" does not look like a valid nonzero file descriptor", argv[2]);
1f3217
+      return 1;
1f3217
+    }
1f3217
+
1f3217
+  f = fdopen (fd, "r");
1f3217
+  if (f == NULL)
1f3217
+    {
1f3217
+      g_warning ("Failed to open fd %d: %s", fd, g_strerror (errno));
1f3217
+      return 1;
1f3217
+    }
1f3217
+
1f3217
+  bytes_read = fread (buf, 1, sizeof (buf), f);
1f3217
+  if (bytes_read != sizeof (expectedResult))
1f3217
+    {
1f3217
+      g_warning ("Read %zu bytes, but expected %zu", bytes_read, sizeof (expectedResult));
1f3217
+      return 1;
1f3217
+    }
1f3217
+
1f3217
+  if (memcmp (expectedResult, buf, sizeof (expectedResult)) != 0)
1f3217
+    {
1f3217
+      buf[sizeof (expectedResult)] = '\0';
1f3217
+      g_warning ("Expected \"%s\" but read  \"%s\"", expectedResult, (char *)buf);
1f3217
+      return 1;
1f3217
+    }
1f3217
+
1f3217
+  if (fclose (f) == -1)
1f3217
+    g_assert_not_reached ();
1f3217
+
1f3217
+  return 0;
1f3217
+}
1f3217
+
1f3217
 static int
1f3217
 env_mode (int argc, char **argv)
1f3217
 {
1f3217
@@ -242,6 +289,8 @@ main (int argc, char **argv)
1f3217
     return sleep_forever_mode (argc, argv);
1f3217
   else if (strcmp (mode, "write-to-fds") == 0)
1f3217
     return write_to_fds (argc, argv);
1f3217
+  else if (strcmp (mode, "read-from-fd") == 0)
1f3217
+    return read_from_fd (argc, argv);
1f3217
   else if (strcmp (mode, "env") == 0)
1f3217
     return env_mode (argc, argv);
1f3217
   else if (strcmp (mode, "cwd") == 0)
1f3217
diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c
1f3217
index ba49c1c43..a6e24c2e8 100644
1f3217
--- a/gio/tests/gsubprocess.c
1f3217
+++ b/gio/tests/gsubprocess.c
1f3217
@@ -5,6 +5,7 @@
1f3217
 #include <sys/wait.h>
1f3217
 #include <glib-unix.h>
1f3217
 #include <gio/gunixinputstream.h>
1f3217
+#include <gio/gunixoutputstream.h>
1f3217
 #include <gio/gfiledescriptorbased.h>
1f3217
 #include <unistd.h>
1f3217
 #include <fcntl.h>
1f3217
@@ -1790,6 +1791,146 @@ test_pass_fd_inherit_fds (void)
1f3217
   do_test_pass_fd (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL);
1f3217
 }
1f3217
 
1f3217
+static void
1f3217
+do_test_fd_conflation (GSubprocessFlags     flags,
1f3217
+                       GSpawnChildSetupFunc child_setup)
1f3217
+{
1f3217
+  char success_message[] = "Yay success!";
1f3217
+  GError *error = NULL;
1f3217
+  GOutputStream *output_stream;
1f3217
+  GSubprocessLauncher *launcher;
1f3217
+  GSubprocess *proc;
1f3217
+  GPtrArray *args;
1f3217
+  int unused_pipefds[2];
1f3217
+  int pipefds[2];
1f3217
+  gsize bytes_written;
1f3217
+  gboolean success;
1f3217
+  char *fd_str;
1f3217
+
1f3217
+  /* This test must run in a new process because it is extremely sensitive to
1f3217
+   * order of opened fds.
1f3217
+   */
1f3217
+  if (!g_test_subprocess ())
1f3217
+    {
1f3217
+      g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR);
1f3217
+      g_test_trap_assert_passed ();
1f3217
+      return;
1f3217
+    }
1f3217
+
1f3217
+  g_unix_open_pipe (unused_pipefds, FD_CLOEXEC, &error);
1f3217
+  g_assert_no_error (error);
1f3217
+
1f3217
+  g_unix_open_pipe (pipefds, FD_CLOEXEC, &error);
1f3217
+  g_assert_no_error (error);
1f3217
+
1f3217
+  /* The fds should be sequential since we are in a new process. */
1f3217
+  g_assert_cmpint (unused_pipefds[0] /* 3 */, ==, unused_pipefds[1] - 1);
1f3217
+  g_assert_cmpint (unused_pipefds[1] /* 4 */, ==, pipefds[0] - 1);
1f3217
+  g_assert_cmpint (pipefds[0] /* 5 */, ==, pipefds[1] /* 6 */ - 1);
1f3217
+
1f3217
+  /* Because GSubprocess allows arbitrary remapping of fds, it has to be careful
1f3217
+   * to avoid fd conflation issues, e.g. it should properly handle 5 -> 4 and
1f3217
+   * 4 -> 5 at the same time. GIO previously attempted to handle this by naively
1f3217
+   * dup'ing the source fds, but this was not good enough because it was
1f3217
+   * possible that the dup'ed result could still conflict with one of the target
1f3217
+   * fds. For example:
1f3217
+   *
1f3217
+   * source_fd 5 -> target_fd 9, source_fd 3 -> target_fd 7
1f3217
+   *
1f3217
+   * dup(5) -> dup returns 8
1f3217
+   * dup(3) -> dup returns 9
1f3217
+   *
1f3217
+   * After dup'ing, we wind up with: 8 -> 9, 9 -> 7. That means that after we
1f3217
+   * dup2(8, 9), we have clobbered fd 9 before we dup2(9, 7). The end result is
1f3217
+   * we have remapped 5 -> 9 as expected, but then remapped 5 -> 7 instead of
1f3217
+   * 3 -> 7 as the application intended.
1f3217
+   *
1f3217
+   * This issue has been fixed in the simplest way possible, by passing a
1f3217
+   * minimum fd value when using F_DUPFD_CLOEXEC that is higher than any of the
1f3217
+   * target fds, to guarantee all source fds are different than all target fds,
1f3217
+   * eliminating any possibility of conflation.
1f3217
+   *
1f3217
+   * Anyway, that is why we have the unused_pipefds here. We need to open fds in
1f3217
+   * a certain order in order to trick older GSubprocess into conflating the
1f3217
+   * fds. The primary goal of this test is to ensure this particular conflation
1f3217
+   * issue is not reintroduced. See glib#2503.
1f3217
+   *
1f3217
+   * Be aware this test is necessarily extremely fragile. To reproduce these
1f3217
+   * bugs, it relies on internals of gspawn and gmain that will likely change
1f3217
+   * in the future, eventually causing this test to no longer test the the bugs
1f3217
+   * it was originally designed to test. That is OK! If the test fails, at
1f3217
+   * least you know *something* is wrong.
1f3217
+   */
1f3217
+  launcher = g_subprocess_launcher_new (flags);
1f3217
+  g_subprocess_launcher_take_fd (launcher, pipefds[0] /* 5 */, pipefds[1] + 3 /* 9 */);
1f3217
+  g_subprocess_launcher_take_fd (launcher, unused_pipefds[0] /* 3 */, pipefds[1] + 1 /* 7 */);
1f3217
+  if (child_setup != NULL)
1f3217
+    g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL);
1f3217
+  fd_str = g_strdup_printf ("%d", pipefds[1] + 3);
1f3217
+  args = get_test_subprocess_args ("read-from-fd", fd_str, NULL);
1f3217
+  proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, &error);
1f3217
+  g_assert_no_error (error);
1f3217
+  g_assert_nonnull (proc);
1f3217
+  g_ptr_array_free (args, TRUE);
1f3217
+  g_object_unref (launcher);
1f3217
+  g_free (fd_str);
1f3217
+
1f3217
+  /* Close the read ends of the pipes. */
1f3217
+  close (unused_pipefds[0]);
1f3217
+  close (pipefds[0]);
1f3217
+
1f3217
+  /* Also close the write end of the unused pipe. */
1f3217
+  close (unused_pipefds[1]);
1f3217
+
1f3217
+  /* So now pipefds[0] should be inherited into the subprocess as
1f3217
+   * pipefds[1] + 2, and unused_pipefds[0] should be inherited as
1f3217
+   * pipefds[1] + 1. We will write to pipefds[1] and the subprocess will verify
1f3217
+   * that it reads the expected data. But older broken GIO will accidentally
1f3217
+   * clobber pipefds[1] + 2 with pipefds[1] + 1! This will cause the subprocess
1f3217
+   * to hang trying to read from the wrong pipe.
1f3217
+   */
1f3217
+  output_stream = g_unix_output_stream_new (pipefds[1], TRUE);
1f3217
+  success = g_output_stream_write_all (output_stream,
1f3217
+                                       success_message, sizeof (success_message),
1f3217
+                                       &bytes_written,
1f3217
+                                       NULL,
1f3217
+                                       &error);
1f3217
+  g_assert_no_error (error);
1f3217
+  g_assert_cmpint (bytes_written, ==, sizeof (success_message));
1f3217
+  g_assert_true (success);
1f3217
+  g_object_unref (output_stream);
1f3217
+
1f3217
+  success = g_subprocess_wait_check (proc, NULL, &error);
1f3217
+  g_assert_no_error (error);
1f3217
+  g_object_unref (proc);
1f3217
+}
1f3217
+
1f3217
+static void
1f3217
+test_fd_conflation (void)
1f3217
+{
1f3217
+  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, NULL);
1f3217
+}
1f3217
+
1f3217
+static void
1f3217
+test_fd_conflation_empty_child_setup (void)
1f3217
+{
1f3217
+  /* Using a child setup function forces gspawn to use fork/exec
1f3217
+   * rather than posix_spawn.
1f3217
+   */
1f3217
+  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup);
1f3217
+}
1f3217
+
1f3217
+static void
1f3217
+test_fd_conflation_inherit_fds (void)
1f3217
+{
1f3217
+  /* Try to test the optimized posix_spawn codepath instead of
1f3217
+   * fork/exec. Currently this requires using INHERIT_FDS since gspawn's
1f3217
+   * posix_spawn codepath does not currently handle closing
1f3217
+   * non-inherited fds.
1f3217
+   */
1f3217
+  do_test_fd_conflation (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL);
1f3217
+}
1f3217
+
1f3217
 #endif
1f3217
 
1f3217
 static void
1f3217
@@ -1930,6 +2071,9 @@ main (int argc, char **argv)
1f3217
   g_test_add_func ("/gsubprocess/pass-fd/basic", test_pass_fd);
1f3217
   g_test_add_func ("/gsubprocess/pass-fd/empty-child-setup", test_pass_fd_empty_child_setup);
1f3217
   g_test_add_func ("/gsubprocess/pass-fd/inherit-fds", test_pass_fd_inherit_fds);
1f3217
+  g_test_add_func ("/gsubprocess/fd-conflation/basic", test_fd_conflation);
1f3217
+  g_test_add_func ("/gsubprocess/fd-conflation/empty-child-setup", test_fd_conflation_empty_child_setup);
1f3217
+  g_test_add_func ("/gsubprocess/fd-conflation/inherit-fds", test_fd_conflation_inherit_fds);
1f3217
 #endif
1f3217
   g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment);
1f3217
 
1f3217
-- 
1f3217
2.33.1
1f3217
1f3217
From 5542612c805857a244561ec160e904dd302ae799 Mon Sep 17 00:00:00 2001
1f3217
From: Michael Catanzaro <mcatanzaro@redhat.com>
1f3217
Date: Wed, 27 Oct 2021 18:30:47 -0500
1f3217
Subject: [PATCH 10/10] Add test for child_err_report_fd conflation with target
1f3217
 fds
1f3217
1f3217
This tests for glib#2506.
1f3217
---
1f3217
 gio/tests/gsubprocess.c | 42 ++++++++++++++++++++++++++++++++++-------
1f3217
 1 file changed, 35 insertions(+), 7 deletions(-)
1f3217
1f3217
diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c
1f3217
index a6e24c2e8..4629cdea7 100644
1f3217
--- a/gio/tests/gsubprocess.c
1f3217
+++ b/gio/tests/gsubprocess.c
1f3217
@@ -1793,7 +1793,8 @@ test_pass_fd_inherit_fds (void)
1f3217
 
1f3217
 static void
1f3217
 do_test_fd_conflation (GSubprocessFlags     flags,
1f3217
-                       GSpawnChildSetupFunc child_setup)
1f3217
+                       GSpawnChildSetupFunc child_setup,
1f3217
+                       gboolean             test_child_err_report_fd)
1f3217
 {
1f3217
   char success_message[] = "Yay success!";
1f3217
   GError *error = NULL;
1f3217
@@ -1803,6 +1804,7 @@ do_test_fd_conflation (GSubprocessFlags     flags,
1f3217
   GPtrArray *args;
1f3217
   int unused_pipefds[2];
1f3217
   int pipefds[2];
1f3217
+  int fd_to_pass_to_child;
1f3217
   gsize bytes_written;
1f3217
   gboolean success;
1f3217
   char *fd_str;
1f3217
@@ -1855,18 +1857,26 @@ do_test_fd_conflation (GSubprocessFlags     flags,
1f3217
    * fds. The primary goal of this test is to ensure this particular conflation
1f3217
    * issue is not reintroduced. See glib#2503.
1f3217
    *
1f3217
+   * This test also has an alternate mode of operation where it instead tests
1f3217
+   * for conflation with gspawn's child_err_report_fd, glib#2506.
1f3217
+   *
1f3217
    * Be aware this test is necessarily extremely fragile. To reproduce these
1f3217
    * bugs, it relies on internals of gspawn and gmain that will likely change
1f3217
    * in the future, eventually causing this test to no longer test the the bugs
1f3217
    * it was originally designed to test. That is OK! If the test fails, at
1f3217
    * least you know *something* is wrong.
1f3217
    */
1f3217
+  if (test_child_err_report_fd)
1f3217
+    fd_to_pass_to_child = pipefds[1] + 2 /* 8 */;
1f3217
+  else
1f3217
+    fd_to_pass_to_child = pipefds[1] + 3 /* 9 */;
1f3217
+
1f3217
   launcher = g_subprocess_launcher_new (flags);
1f3217
-  g_subprocess_launcher_take_fd (launcher, pipefds[0] /* 5 */, pipefds[1] + 3 /* 9 */);
1f3217
+  g_subprocess_launcher_take_fd (launcher, pipefds[0] /* 5 */, fd_to_pass_to_child);
1f3217
   g_subprocess_launcher_take_fd (launcher, unused_pipefds[0] /* 3 */, pipefds[1] + 1 /* 7 */);
1f3217
   if (child_setup != NULL)
1f3217
     g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL);
1f3217
-  fd_str = g_strdup_printf ("%d", pipefds[1] + 3);
1f3217
+  fd_str = g_strdup_printf ("%d", fd_to_pass_to_child);
1f3217
   args = get_test_subprocess_args ("read-from-fd", fd_str, NULL);
1f3217
   proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, &error);
1f3217
   g_assert_no_error (error);
1f3217
@@ -1882,12 +1892,20 @@ do_test_fd_conflation (GSubprocessFlags     flags,
1f3217
   /* Also close the write end of the unused pipe. */
1f3217
   close (unused_pipefds[1]);
1f3217
 
1f3217
-  /* So now pipefds[0] should be inherited into the subprocess as
1f3217
+  /* If doing our normal test:
1f3217
+   *
1f3217
+   * So now pipefds[0] should be inherited into the subprocess as
1f3217
    * pipefds[1] + 2, and unused_pipefds[0] should be inherited as
1f3217
    * pipefds[1] + 1. We will write to pipefds[1] and the subprocess will verify
1f3217
    * that it reads the expected data. But older broken GIO will accidentally
1f3217
    * clobber pipefds[1] + 2 with pipefds[1] + 1! This will cause the subprocess
1f3217
    * to hang trying to read from the wrong pipe.
1f3217
+   *
1f3217
+   * If testing conflation with child_err_report_fd:
1f3217
+   *
1f3217
+   * We are actually already done. The real test succeeded if we made it this
1f3217
+   * far without hanging while spawning the child. But let's continue with our
1f3217
+   * write and read anyway, to ensure things are good.
1f3217
    */
1f3217
   output_stream = g_unix_output_stream_new (pipefds[1], TRUE);
1f3217
   success = g_output_stream_write_all (output_stream,
1f3217
@@ -1908,7 +1926,7 @@ do_test_fd_conflation (GSubprocessFlags     flags,
1f3217
 static void
1f3217
 test_fd_conflation (void)
1f3217
 {
1f3217
-  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, NULL);
1f3217
+  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, NULL, FALSE);
1f3217
 }
1f3217
 
1f3217
 static void
1f3217
@@ -1917,7 +1935,7 @@ test_fd_conflation_empty_child_setup (void)
1f3217
   /* Using a child setup function forces gspawn to use fork/exec
1f3217
    * rather than posix_spawn.
1f3217
    */
1f3217
-  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup);
1f3217
+  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, FALSE);
1f3217
 }
1f3217
 
1f3217
 static void
1f3217
@@ -1928,7 +1946,16 @@ test_fd_conflation_inherit_fds (void)
1f3217
    * posix_spawn codepath does not currently handle closing
1f3217
    * non-inherited fds.
1f3217
    */
1f3217
-  do_test_fd_conflation (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL);
1f3217
+  do_test_fd_conflation (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL, FALSE);
1f3217
+}
1f3217
+
1f3217
+static void
1f3217
+test_fd_conflation_child_err_report_fd (void)
1f3217
+{
1f3217
+  /* Using a child setup function forces gspawn to use fork/exec
1f3217
+   * rather than posix_spawn.
1f3217
+   */
1f3217
+  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, TRUE);
1f3217
 }
1f3217
 
1f3217
 #endif
1f3217
@@ -2074,6 +2101,7 @@ main (int argc, char **argv)
1f3217
   g_test_add_func ("/gsubprocess/fd-conflation/basic", test_fd_conflation);
1f3217
   g_test_add_func ("/gsubprocess/fd-conflation/empty-child-setup", test_fd_conflation_empty_child_setup);
1f3217
   g_test_add_func ("/gsubprocess/fd-conflation/inherit-fds", test_fd_conflation_inherit_fds);
1f3217
+  g_test_add_func ("/gsubprocess/fd-conflation/child-err-report-fd", test_fd_conflation_child_err_report_fd);
1f3217
 #endif
1f3217
   g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment);
1f3217
 
1f3217
-- 
1f3217
2.33.1
1f3217