Imported Upstream version 2.66.5 upstream/2.66.5
authorDongHun Kwak <dh0128.kwak@samsung.com>
Fri, 29 Oct 2021 01:27:37 +0000 (10:27 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Fri, 29 Oct 2021 01:27:37 +0000 (10:27 +0900)
25 files changed:
NEWS
docs/reference/gio/overview.xml
gio/gdbusaddress.c
gio/gdesktopappinfo.c
gio/giomodule.c
gio/gresource.c
gio/gsettingsschema.c
gio/gsocket.c
gio/tests/desktop-app-info.c
gio/tests/desktop-files/usr/applications/invalid-desktop.desktop [new file with mode: 0644]
gio/tests/file.c
glib/gdate.c
glib/gdatetime.c
glib/gspawn.c
glib/gthread-posix.c
glib/gthread-win32.c
glib/tests/date.c
glib/tests/gdatetime.c
glib/tests/meson.build
glib/tests/path-test-subdir/meson.build [new file with mode: 0644]
glib/tests/path-test-subdir/spawn-test-helper.c [new file with mode: 0644]
glib/tests/spawn-path-search-helper.c [new file with mode: 0644]
glib/tests/spawn-path-search.c [new file with mode: 0644]
glib/tests/spawn-test-helper.c [new file with mode: 0644]
meson.build

diff --git a/NEWS b/NEWS
index 56d27f6..a9becc9 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,35 @@
+Overview of changes in GLib 2.66.5
+==================================
+
+* Fix some issues with handling over-long (invalid) input when parsing for `GDate` (!1824)
+
+* Don’t load GIO modules or parse other GIO environment variables when `AT_SECURE`
+  is set (i.e. in a setuid/setgid/setcap process). GIO has always been
+  documented as not being safe to use in privileged processes, but people persist
+  in using it unsafely, so these changes should harden things against potential
+  attacks at least a little. Unfortunately they break a couple of projects which
+  were relying on reading `DBUS_SESSION_BUS_ADDRESS`, so GIO continues to read
+  that for setgid/setcap (but not setuid) processes. This loophole will be closed
+  in GLib 2.70 (see issue #2316), which should give modules 6 months to change
+  their behaviour. (Work by Simon McVittie and Philip Withnall) (#2168, #2305)
+
+* Fix `g_spawn()` searching `PATH` when it wasn’t meant to (work by
+  Simon McVittie and Thomas Haller) (!1913)
+
+* Bugs fixed:
+ - #2168 giomodule: Loads GIO modules even if setuid, etc.
+ - #2210 g_private_replace ordering issue
+ - #2305 GIO security hardening causing gnome-keyring to regress when session bus is provided by dbus-launch (dbus-x11)
+ - !1820 gthread: Destroy value after replacing it in g_private_replace()
+ - !1824 Backport !1821 “gdate: Limit length of dates which can be parsed as valid” to glib-2-66
+ - !1831 gdatetime.c: Fix MSVC builds for lack of NAN items
+ - !1836 Backport !1827 “Windows: fix FD_READ condition flag still set on recoverable UDP socket errors.” to glib-2-66
+ - !1864 Backport !1862 “gio: Ignore various environment variables when running as setuid” to glib-2-66
+ - !1872 Backport !1868 “gdesktopappinfo: Fix validation of XDG_CURRENT_DESKTOP” to glib-2-66
+ - !1913 Backport !1902 “spawn: Don't set a search path if we don't want to search PATH” to glib-2-66
+ - !1922 Backport !1920 “Resolve GDBus regressions in setcap/setgid programs” to glib-2-66
+
+
 Overview of changes in GLib 2.66.4
 ==================================
 
index 7817ab6..d82f1ac 100644 (file)
@@ -436,6 +436,9 @@ Gvfs is also heavily distributed and relies on a session bus to be present.
        modules from this alternate directory instead of the directory
        built into GIO. This is useful when running tests, for example.
       </para>
+      <para>
+        This environment variable is ignored when running in a setuid program.
+      </para>
     </formalpara>
 
     <formalpara>
@@ -446,6 +449,9 @@ Gvfs is also heavily distributed and relies on a session bus to be present.
         paths separated by a colon, GIO will attempt to load
         additional modules from within the path.
       </para>
+      <para>
+        This environment variable is ignored when running in a setuid program.
+      </para>
     </formalpara>
 
     <formalpara>
index 3dd3cc8..0044cd3 100644 (file)
@@ -30,6 +30,7 @@
 #include "gdbusaddress.h"
 #include "gdbuserror.h"
 #include "gioenumtypes.h"
+#include "glib-private.h"
 #include "gnetworkaddress.h"
 #include "gsocketclient.h"
 #include "giostream.h"
@@ -1279,6 +1280,7 @@ g_dbus_address_get_for_bus_sync (GBusType       bus_type,
                                  GCancellable  *cancellable,
                                  GError       **error)
 {
+  gboolean has_elevated_privileges = GLIB_PRIVATE_CALL (g_check_setuid) ();
   gchar *ret, *s = NULL;
   const gchar *starter_bus;
   GError *local_error;
@@ -1317,10 +1319,16 @@ g_dbus_address_get_for_bus_sync (GBusType       bus_type,
       _g_dbus_debug_print_unlock ();
     }
 
+  /* Don’t load the addresses from the environment if running as setuid, as they
+   * come from an unprivileged caller. */
   switch (bus_type)
     {
     case G_BUS_TYPE_SYSTEM:
-      ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS"));
+      if (has_elevated_privileges)
+        ret = NULL;
+      else
+        ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS"));
+
       if (ret == NULL)
         {
           ret = g_strdup ("unix:path=/var/run/dbus/system_bus_socket");
@@ -1328,7 +1336,33 @@ g_dbus_address_get_for_bus_sync (GBusType       bus_type,
       break;
 
     case G_BUS_TYPE_SESSION:
-      ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
+      if (has_elevated_privileges)
+        {
+#ifdef G_OS_UNIX
+          if (geteuid () == getuid ())
+            {
+              /* Ideally we shouldn't do this, because setgid and
+               * filesystem capabilities are also elevated privileges
+               * with which we should not be trusting environment variables
+               * from the caller. Unfortunately, there are programs with
+               * elevated privileges that rely on the session bus being
+               * available. We already prevent the really dangerous
+               * transports like autolaunch: and unixexec: when our
+               * privileges are elevated, so this can only make us connect
+               * to the wrong AF_UNIX or TCP socket. */
+              ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
+            }
+          else
+#endif
+            {
+              ret = NULL;
+            }
+        }
+      else
+        {
+          ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
+        }
+
       if (ret == NULL)
         {
           ret = get_session_address_platform_specific (&local_error);
index b779b30..2b139a0 100644 (file)
@@ -305,6 +305,55 @@ desktop_file_dir_app_name_is_masked (DesktopFileDir *dir,
   return FALSE;
 }
 
+/* Not much to go on from https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
+ * so validate it as a non-empty alphanumeric ASCII string with `-` and `_` allowed.
+ *
+ * Validation is important as the desktop IDs are used to construct filenames,
+ * and may be set by an unprivileged caller if running in a setuid program. */
+static gboolean
+validate_xdg_desktop (const gchar *desktop)
+{
+  gsize i;
+
+  for (i = 0; desktop[i] != '\0'; i++)
+    if (desktop[i] != '-' && desktop[i] != '_' &&
+        !g_ascii_isalnum (desktop[i]))
+      return FALSE;
+
+  if (i == 0)
+    return FALSE;
+
+  return TRUE;
+}
+
+static char **
+get_valid_current_desktops (const char *value)
+{
+  char **tmp;
+  gsize i;
+  GPtrArray *valid_desktops;
+
+  if (value == NULL)
+    value = g_getenv ("XDG_CURRENT_DESKTOP");
+  if (value == NULL)
+    value = "";
+
+  tmp = g_strsplit (value, G_SEARCHPATH_SEPARATOR_S, 0);
+  valid_desktops = g_ptr_array_new_full (g_strv_length (tmp) + 1, g_free);
+  for (i = 0; tmp[i]; i++)
+    {
+      if (validate_xdg_desktop (tmp[i]))
+        g_ptr_array_add (valid_desktops, tmp[i]);
+      else
+        g_free (tmp[i]);
+    }
+  g_ptr_array_add (valid_desktops, NULL);
+  g_free (tmp);
+  tmp = (char **) g_ptr_array_steal (valid_desktops, NULL);
+  g_ptr_array_unref (valid_desktops);
+  return tmp;
+}
+
 static const gchar * const *
 get_lowercase_current_desktops (void)
 {
@@ -312,23 +361,15 @@ get_lowercase_current_desktops (void)
 
   if (g_once_init_enter (&result))
     {
-      const gchar *envvar;
-      gchar **tmp;
-
-      envvar = g_getenv ("XDG_CURRENT_DESKTOP");
+      char **tmp = get_valid_current_desktops (NULL);
+      gsize i, j;
 
-      if (envvar)
+      for (i = 0; tmp[i]; i++)
         {
-          gint i, j;
-
-          tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0);
-
-          for (i = 0; tmp[i]; i++)
-            for (j = 0; tmp[i][j]; j++)
-              tmp[i][j] = g_ascii_tolower (tmp[i][j]);
+          /* Convert to lowercase. */
+          for (j = 0; tmp[i][j]; j++)
+            tmp[i][j] = g_ascii_tolower (tmp[i][j]);
         }
-      else
-        tmp = g_new0 (gchar *, 0 + 1);
 
       g_once_init_leave (&result, tmp);
     }
@@ -343,15 +384,7 @@ get_current_desktops (const gchar *value)
 
   if (g_once_init_enter (&result))
     {
-      gchar **tmp;
-
-      if (!value)
-        value = g_getenv ("XDG_CURRENT_DESKTOP");
-
-      if (!value)
-        value = "";
-
-      tmp = g_strsplit (value, ":", 0);
+      char **tmp = get_valid_current_desktops (value);
 
       g_once_init_leave (&result, tmp);
     }
index dc4d6d3..aaf4636 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "giomodule.h"
 #include "giomodule-priv.h"
+#include "glib-private.h"
 #include "glocalfilemonitor.h"
 #include "gnativevolumemonitor.h"
 #include "gproxyresolver.h"
@@ -806,6 +807,9 @@ _g_io_module_get_default_type (const gchar *extension_point,
       return G_TYPE_INVALID;
     }
 
+  /* It’s OK to query the environment here, even when running as setuid, because
+   * it only allows a choice between existing already-loaded modules. No new
+   * code is loaded based on the environment variable value. */
   use_this = envvar ? g_getenv (envvar) : NULL;
   if (g_strcmp0 (use_this, "help") == 0)
     {
@@ -955,6 +959,9 @@ _g_io_module_get_default (const gchar         *extension_point,
       return NULL;
     }
 
+  /* It’s OK to query the environment here, even when running as setuid, because
+   * it only allows a choice between existing already-loaded modules. No new
+   * code is loaded based on the environment variable value. */
   use_this = envvar ? g_getenv (envvar) : NULL;
   if (g_strcmp0 (use_this, "help") == 0)
     {
@@ -1150,8 +1157,16 @@ static gchar *
 get_gio_module_dir (void)
 {
   gchar *module_dir;
-
-  module_dir = g_strdup (g_getenv ("GIO_MODULE_DIR"));
+  gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
+
+  /* If running as setuid, loading modules from an arbitrary directory
+   * controlled by the unprivileged user who is running the program could allow
+   * for execution of arbitrary code (in constructors in modules).
+   * Don’t allow it.
+   *
+   * If a setuid program somehow needs to load additional GIO modules, it should
+   * explicitly call g_io_modules_scan_all_in_directory(). */
+  module_dir = !is_setuid ? g_strdup (g_getenv ("GIO_MODULE_DIR")) : NULL;
   if (module_dir == NULL)
     {
 #ifdef G_OS_WIN32
@@ -1183,13 +1198,14 @@ _g_io_modules_ensure_loaded (void)
 
   if (!loaded_dirs)
     {
+      gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
       gchar *module_dir;
 
       loaded_dirs = TRUE;
       scope = g_io_module_scope_new (G_IO_MODULE_SCOPE_BLOCK_DUPLICATES);
 
-      /* First load any overrides, extras */
-      module_path = g_getenv ("GIO_EXTRA_MODULES");
+      /* First load any overrides, extras (but not if running as setuid!) */
+      module_path = !is_setuid ? g_getenv ("GIO_EXTRA_MODULES") : NULL;
       if (module_path)
        {
          gchar **paths;
index b7222b8..13632d9 100644 (file)
@@ -32,6 +32,8 @@
 #include <gio/gzlibdecompressor.h>
 #include <gio/gconverterinputstream.h>
 
+#include "glib-private.h"
+
 struct _GResource
 {
   int ref_count;
@@ -159,7 +161,7 @@ G_DEFINE_BOXED_TYPE (GResource, g_resource, g_resource_ref, g_resource_unref)
  * replace resources in the program or library, without recompiling, for debugging or quick hacking and testing
  * purposes. Since GLib 2.50, it is possible to use the `G_RESOURCE_OVERLAYS` environment variable to selectively overlay
  * resources with replacements from the filesystem.  It is a %G_SEARCHPATH_SEPARATOR-separated list of substitutions to perform
- * during resource lookups.
+ * during resource lookups. It is ignored when running in a setuid process.
  *
  * A substitution has the form
  *
@@ -330,10 +332,13 @@ g_resource_find_overlay (const gchar    *path,
 
   if (g_once_init_enter (&overlay_dirs))
     {
+      gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
       const gchar * const *result;
       const gchar *envvar;
 
-      envvar = g_getenv ("G_RESOURCE_OVERLAYS");
+      /* Don’t load overlays if setuid, as they could allow reading privileged
+       * files. */
+      envvar = !is_setuid ? g_getenv ("G_RESOURCE_OVERLAYS") : NULL;
       if (envvar != NULL)
         {
           gchar **parts;
index 0b94f76..8e203db 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "config.h"
 
+#include "glib-private.h"
 #include "gsettingsschema-internal.h"
 #include "gsettings.h"
 
@@ -343,6 +344,7 @@ initialise_schema_sources (void)
    */
   if G_UNLIKELY (g_once_init_enter (&initialised))
     {
+      gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
       const gchar * const *dirs;
       const gchar *path;
       gchar **extra_schema_dirs;
@@ -357,7 +359,9 @@ initialise_schema_sources (void)
 
       try_prepend_data_dir (g_get_user_data_dir ());
 
-      if ((path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL)
+      /* Disallow loading extra schemas if running as setuid, as that could
+       * allow reading privileged files. */
+      if (!is_setuid && (path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL)
         {
           extra_schema_dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0);
           for (i = 0; extra_schema_dirs[i]; i++);
index 0f8f925..e911de7 100644 (file)
@@ -5475,10 +5475,10 @@ g_socket_receive_message_with_timeout (GSocket                 *socket,
            if (errsv == WSAEINTR)
              continue;
 
+           win32_unset_event_mask (socket, FD_READ);
+
             if (errsv == WSAEWOULDBLOCK)
               {
-                win32_unset_event_mask (socket, FD_READ);
-
                 if (timeout_us != 0)
                   {
                     if (!block_on_timeout (socket, G_IO_IN, timeout_us,
index f4e509a..bc50ff4 100644 (file)
@@ -571,7 +571,8 @@ assert_implementations (const gchar *interface,
                       "gnome-terminal.desktop nautilus-autorun-software.desktop gcr-viewer.desktop "         \
                       "nautilus-connect-server.desktop kde4-dolphin.desktop gnome-music.desktop "            \
                       "kde4-konqbrowser.desktop gucharmap.desktop kde4-okular.desktop nautilus.desktop "     \
-                      "gedit.desktop evince.desktop file-roller.desktop dconf-editor.desktop glade.desktop"
+                      "gedit.desktop evince.desktop file-roller.desktop dconf-editor.desktop glade.desktop " \
+                      "invalid-desktop.desktop"
 #define HOME_APPS     "epiphany-weather-for-toronto-island-9c6a4e022b17686306243dada811d550d25eb1fb.desktop"
 #define ALL_HOME_APPS HOME_APPS " eog.desktop"
 
@@ -726,6 +727,9 @@ test_show_in (void)
   assert_shown ("gcr-prompter.desktop", TRUE, "GNOME-Classic");
   assert_shown ("gcr-prompter.desktop", TRUE, "GNOME-Classic:KDE");
   assert_shown ("gcr-prompter.desktop", TRUE, "KDE:GNOME-Classic");
+  assert_shown ("invalid-desktop.desktop", TRUE, "GNOME");
+  assert_shown ("invalid-desktop.desktop", FALSE, "../invalid/desktop");
+  assert_shown ("invalid-desktop.desktop", FALSE, "../invalid/desktop:../invalid/desktop");
 }
 
 /* Test g_desktop_app_info_launch_uris_as_manager() and
diff --git a/gio/tests/desktop-files/usr/applications/invalid-desktop.desktop b/gio/tests/desktop-files/usr/applications/invalid-desktop.desktop
new file mode 100644 (file)
index 0000000..dffaa24
--- /dev/null
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Type=Application
+Name=appinfo-test
+OnlyShowIn=../invalid/desktop;GNOME
+NotShowIn=ROX;
index c3877af..d876965 100644 (file)
@@ -1041,7 +1041,7 @@ test_measure (void)
   if (size > 0)
     g_assert_cmpuint (num_bytes, ==, size);
   g_assert_cmpuint (num_dirs, ==, 6);
-  g_assert_cmpuint (num_files, ==, 31);
+  g_assert_cmpuint (num_files, ==, 32);
 
   g_object_unref (file);
   g_free (path);
@@ -1131,7 +1131,7 @@ test_measure_async (void)
   g_free (path);
 
   data->expected_dirs = 6;
-  data->expected_files = 31;
+  data->expected_files = 32;
 
   g_file_measure_disk_usage_async (file,
                                    G_FILE_MEASURE_APPARENT_SIZE,
index 391b142..253ab65 100644 (file)
@@ -1229,14 +1229,21 @@ g_date_set_parse (GDate       *d,
 {
   GDateParseTokens pt;
   guint m = G_DATE_BAD_MONTH, day = G_DATE_BAD_DAY, y = G_DATE_BAD_YEAR;
+  gsize str_len;
   
   g_return_if_fail (d != NULL);
   
   /* set invalid */
   g_date_clear (d, 1);
 
+  /* Anything longer than this is ridiculous and could take a while to normalize.
+   * This limit is chosen arbitrarily. */
+  str_len = strlen (str);
+  if (str_len > 200)
+    return;
+
   /* The input has to be valid UTF-8. */
-  if (!g_utf8_validate (str, -1, NULL))
+  if (!g_utf8_validate_len (str, str_len, NULL))
     return;
 
   G_LOCK (g_date_global);
index 02cc3bd..a43efab 100644 (file)
 #ifndef G_OS_WIN32
 #include <sys/time.h>
 #include <time.h>
+#else
+#if defined (_MSC_VER) && (_MSC_VER < 1800)
+/* fallback implementation for isnan() on VS2012 and earlier */
+#define isnan _isnan
+#endif
 #endif /* !G_OS_WIN32 */
 
 /**
index c37ac7c..c8e0ca8 100644 (file)
@@ -1753,8 +1753,10 @@ fork_exec_with_fds (gboolean              intermediate_child,
   gint status;
   const gchar *chosen_search_path;
   gchar *search_path_buffer = NULL;
+  gchar *search_path_buffer_heap = NULL;
   gsize search_path_buffer_len = 0;
   gchar **argv_buffer = NULL;
+  gchar **argv_buffer_heap = NULL;
   gsize argv_buffer_len = 0;
 
 #ifdef POSIX_SPAWN_AVAILABLE
@@ -1819,7 +1821,7 @@ fork_exec_with_fds (gboolean              intermediate_child,
   if (search_path && chosen_search_path == NULL)
     chosen_search_path = g_getenv ("PATH");
 
-  if (chosen_search_path == NULL)
+  if ((search_path || search_path_from_envp) && chosen_search_path == NULL)
     {
       /* There is no 'PATH' in the environment.  The default
        * * search path in libc is the current directory followed by
@@ -1834,25 +1836,58 @@ fork_exec_with_fds (gboolean              intermediate_child,
       chosen_search_path = "/bin:/usr/bin:.";
     }
 
+  if (search_path || search_path_from_envp)
+    g_assert (chosen_search_path != NULL);
+  else
+    g_assert (chosen_search_path == NULL);
+
   /* Allocate a buffer which the fork()ed child can use to assemble potential
    * paths for the binary to exec(), combining the argv[0] and elements from
    * the chosen_search_path. This can’t be done in the child because malloc()
    * (or alloca()) are not async-signal-safe (see `man 7 signal-safety`).
    *
    * Add 2 for the nul terminator and a leading `/`. */
-  search_path_buffer_len = strlen (chosen_search_path) + strlen (argv[0]) + 2;
-  search_path_buffer = g_malloc (search_path_buffer_len);
+  if (chosen_search_path != NULL)
+    {
+      search_path_buffer_len = strlen (chosen_search_path) + strlen (argv[0]) + 2;
+      if (search_path_buffer_len < 4000)
+        {
+          /* Prefer small stack allocations to avoid valgrind leak warnings
+           * in forked child. The 4000B cutoff is arbitrary. */
+          search_path_buffer = g_alloca (search_path_buffer_len);
+        }
+      else
+        {
+          search_path_buffer_heap = g_malloc (search_path_buffer_len);
+          search_path_buffer = search_path_buffer_heap;
+        }
+    }
+
+  if (search_path || search_path_from_envp)
+    g_assert (search_path_buffer != NULL);
+  else
+    g_assert (search_path_buffer == NULL);
 
   /* And allocate a buffer which is 2 elements longer than @argv, so that if
    * script_execute() has to be called later on, it can build a wrapper argv
    * array in this buffer. */
   argv_buffer_len = g_strv_length (argv) + 2;
-  argv_buffer = g_new (gchar *, argv_buffer_len);
+  if (argv_buffer_len < 4000 / sizeof (gchar *))
+    {
+      /* Prefer small stack allocations to avoid valgrind leak warnings
+       * in forked child. The 4000B cutoff is arbitrary. */
+      argv_buffer = g_newa (gchar *, argv_buffer_len);
+    }
+  else
+    {
+      argv_buffer_heap = g_new (gchar *, argv_buffer_len);
+      argv_buffer = argv_buffer_heap;
+    }
 
   if (!g_unix_open_pipe (child_err_report_pipe, pipe_flags, error))
     {
-      g_free (search_path_buffer);
-      g_free (argv_buffer);
+      g_free (search_path_buffer_heap);
+      g_free (argv_buffer_heap);
       return FALSE;
     }
 
@@ -2099,8 +2134,8 @@ fork_exec_with_fds (gboolean              intermediate_child,
       close_and_invalidate (&child_err_report_pipe[0]);
       close_and_invalidate (&child_pid_report_pipe[0]);
 
-      g_free (search_path_buffer);
-      g_free (argv_buffer);
+      g_free (search_path_buffer_heap);
+      g_free (argv_buffer_heap);
 
       if (child_pid)
         *child_pid = pid;
@@ -2134,8 +2169,8 @@ fork_exec_with_fds (gboolean              intermediate_child,
   close_and_invalidate (&child_pid_report_pipe[0]);
   close_and_invalidate (&child_pid_report_pipe[1]);
 
-  g_free (search_path_buffer);
-  g_free (argv_buffer);
+  g_free (search_path_buffer_heap);
+  g_free (argv_buffer_heap);
 
   return FALSE;
 }
index f360559..f09f58a 100644 (file)
@@ -1116,11 +1116,12 @@ g_private_replace (GPrivate *key,
   gint status;
 
   old = pthread_getspecific (*impl);
-  if (old && key->notify)
-    key->notify (old);
 
   if G_UNLIKELY ((status = pthread_setspecific (*impl, value)) != 0)
     g_thread_abort (status, "pthread_setspecific");
+
+  if (old && key->notify)
+    key->notify (old);
 }
 
 /* {{{1 GThread */
index 54f74f2..0c37dc6 100644 (file)
@@ -373,9 +373,9 @@ g_private_replace (GPrivate *key,
   gpointer old;
 
   old = TlsGetValue (impl);
+  TlsSetValue (impl, value);
   if (old && key->notify)
     key->notify (old);
-  TlsSetValue (impl, value);
 }
 
 /* {{{1 GThread */
index 38de1d9..542293c 100644 (file)
@@ -191,6 +191,10 @@ test_parse_invalid (void)
     {
       /* Incomplete UTF-8 sequence */
       "\xfd",
+      /* Ridiculously long input */
+      "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
+      "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
+      "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
     };
   gsize i;
 
index 0203dd0..3b1d9cf 100644 (file)
 #ifdef G_OS_WIN32
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
+
+#ifndef NAN
+#define NAN HUGE_VAL * 0.0f
+#endif
 #endif
 
 #define ASSERT_DATE(dt,y,m,d) G_STMT_START { \
index 6eb23e8..d35bd16 100644 (file)
@@ -89,6 +89,7 @@ glib_tests = {
   'slist' : {},
   'sort' : {},
   'spawn-multithreaded' : {},
+  'spawn-path-search' : {},
   'spawn-singlethread' : {},
   'strfuncs' : {},
   'string' : {},
@@ -235,6 +236,20 @@ foreach test_name, extra_args : glib_tests
   test(test_name, exe, env : test_env, timeout : timeout, suite : suite)
 endforeach
 
+executable('spawn-path-search-helper', 'spawn-path-search-helper.c',
+  c_args : test_cargs,
+  dependencies : test_deps,
+  install_dir: installed_tests_execdir,
+  install: installed_tests_enabled,
+)
+
+executable('spawn-test-helper', 'spawn-test-helper.c',
+  c_args : test_cargs,
+  dependencies : test_deps,
+  install_dir: installed_tests_execdir,
+  install: installed_tests_enabled,
+)
+
 # test-spawn-echo helper binary required by the spawn tests above
 executable('test-spawn-echo', 'test-spawn-echo.c',
   c_args : test_cargs,
@@ -266,3 +281,5 @@ if not meson.is_cross_build() and host_system != 'windows'
     )
   endif
 endif
+
+subdir('path-test-subdir')
diff --git a/glib/tests/path-test-subdir/meson.build b/glib/tests/path-test-subdir/meson.build
new file mode 100644 (file)
index 0000000..351254c
--- /dev/null
@@ -0,0 +1,6 @@
+executable('spawn-test-helper', 'spawn-test-helper.c',
+  c_args : test_cargs,
+  dependencies : test_deps,
+  install_dir: join_paths(installed_tests_execdir, 'path-test-subdir'),
+  install: installed_tests_enabled,
+)
diff --git a/glib/tests/path-test-subdir/spawn-test-helper.c b/glib/tests/path-test-subdir/spawn-test-helper.c
new file mode 100644 (file)
index 0000000..f9f2cee
--- /dev/null
@@ -0,0 +1,7 @@
+#include <stdio.h>
+
+int main (void)
+{
+  fprintf (stderr, "this is spawn-test-helper from path-test-subdir\n");
+  return 5;
+}
diff --git a/glib/tests/spawn-path-search-helper.c b/glib/tests/spawn-path-search-helper.c
new file mode 100644 (file)
index 0000000..378c203
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2021 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
+
+static void
+child_setup (gpointer user_data)
+{
+}
+
+typedef struct
+{
+  int wait_status;
+  gboolean done;
+} ChildStatus;
+
+static ChildStatus child_status = { -1, FALSE };
+
+static void
+child_watch_cb (GPid pid,
+                gint status,
+                gpointer user_data)
+{
+  child_status.wait_status = status;
+  child_status.done = TRUE;
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+  gboolean search_path = FALSE;
+  gboolean search_path_from_envp = FALSE;
+  gboolean slow_path = FALSE;
+  gboolean unset_path_in_envp = FALSE;
+  gchar *chdir_child = NULL;
+  gchar *set_path_in_envp = NULL;
+  gchar **envp = NULL;
+  GOptionEntry entries[] =
+  {
+    { "chdir-child", '\0',
+      G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &chdir_child,
+      "Run PROGRAM in this working directory", NULL },
+    { "search-path", '\0',
+      G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &search_path,
+      "Search PATH for PROGRAM", NULL },
+    { "search-path-from-envp", '\0',
+      G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &search_path_from_envp,
+      "Search PATH from specified environment", NULL },
+    { "set-path-in-envp", '\0',
+      G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &set_path_in_envp,
+      "Set PATH in specified environment to this value", "PATH", },
+    { "unset-path-in-envp", '\0',
+      G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &unset_path_in_envp,
+      "Unset PATH in specified environment", NULL },
+    { "slow-path", '\0',
+      G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &slow_path,
+      "Use a child-setup function to avoid the posix_spawn fast path", NULL },
+    { NULL }
+  };
+  GError *error = NULL;
+  int ret = 1;
+  GSpawnFlags spawn_flags = G_SPAWN_DO_NOT_REAP_CHILD;
+  GPid pid;
+  GOptionContext *context = NULL;
+
+  context = g_option_context_new ("PROGRAM [ARGS...]");
+  g_option_context_add_main_entries (context, entries, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, &error))
+    {
+      ret = 2;
+      goto out;
+    }
+
+  if (argc < 2)
+    {
+      g_set_error (&error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                   "Usage: %s [OPTIONS] PROGRAM [ARGS...]", argv[0]);
+      ret = 2;
+      goto out;
+    }
+
+  envp = g_get_environ ();
+
+  if (set_path_in_envp != NULL && unset_path_in_envp)
+    {
+      g_set_error (&error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                   "Cannot both set PATH and unset it");
+      ret = 2;
+      goto out;
+    }
+
+  if (set_path_in_envp != NULL)
+    envp = g_environ_setenv (envp, "PATH", set_path_in_envp, TRUE);
+
+  if (unset_path_in_envp)
+    envp = g_environ_unsetenv (envp, "PATH");
+
+  if (search_path)
+    spawn_flags |= G_SPAWN_SEARCH_PATH;
+
+  if (search_path_from_envp)
+    spawn_flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
+
+  if (!g_spawn_async_with_pipes (chdir_child,
+                                 &argv[1],
+                                 envp,
+                                 spawn_flags,
+                                 slow_path ? child_setup : NULL,
+                                 NULL,  /* user_data */
+                                 &pid,
+                                 NULL,  /* stdin */
+                                 NULL,  /* stdout */
+                                 NULL,  /* stderr */
+                                 &error))
+    {
+      ret = 1;
+      goto out;
+    }
+
+  g_child_watch_add (pid, child_watch_cb, NULL);
+
+  while (!child_status.done)
+    g_main_context_iteration (NULL, TRUE);
+
+  g_spawn_close_pid (pid);
+
+#ifdef G_OS_UNIX
+  if (WIFEXITED (child_status.wait_status))
+    ret = WEXITSTATUS (child_status.wait_status);
+  else
+    ret = 1;
+#else
+  ret = child_status.wait_status;
+#endif
+
+out:
+  if (error != NULL)
+    fprintf (stderr, "%s\n", error->message);
+
+  g_free (set_path_in_envp);
+  g_free (chdir_child);
+  g_clear_error (&error);
+  g_strfreev (envp);
+  g_option_context_free (context);
+  return ret;
+}
diff --git a/glib/tests/spawn-path-search.c b/glib/tests/spawn-path-search.c
new file mode 100644 (file)
index 0000000..f4278f3
--- /dev/null
@@ -0,0 +1,454 @@
+/*
+ * Copyright 2021 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
+
+static void
+test_do_not_search (void)
+{
+  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
+  gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL);
+  gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL);
+  gchar **envp = g_get_environ ();
+  gchar *out = NULL;
+  gchar *err = NULL;
+  GError *error = NULL;
+  int wait_status = -1;
+
+  g_test_summary ("Without G_SPAWN_SEARCH_PATH, spawn-test-helper "
+                  "means ./spawn-test-helper.");
+
+  envp = g_environ_setenv (envp, "PATH", subdir, TRUE);
+
+  g_ptr_array_add (argv,
+                   g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL));
+  g_ptr_array_add (argv, g_strdup ("--"));
+  g_ptr_array_add (argv, g_strdup ("spawn-test-helper"));
+  g_ptr_array_add (argv, NULL);
+
+  g_spawn_sync (here,
+                (char **) argv->pdata,
+                envp,
+                G_SPAWN_DEFAULT,
+                NULL,  /* child setup */
+                NULL,  /* user data */
+                &out,
+                &err,
+                &wait_status,
+                &error);
+  g_assert_no_error (error);
+
+  g_test_message ("%s", out);
+  g_test_message ("%s", err);
+  g_assert_nonnull (strstr (err, "this is spawn-test-helper from glib/tests"));
+
+#ifdef G_OS_UNIX
+  g_assert_true (WIFEXITED (wait_status));
+  g_assert_cmpint (WEXITSTATUS (wait_status), ==, 0);
+#endif
+
+  g_strfreev (envp);
+  g_free (here);
+  g_free (subdir);
+  g_free (out);
+  g_free (err);
+  g_ptr_array_unref (argv);
+}
+
+static void
+test_search_path (void)
+{
+  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
+  gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL);
+  gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL);
+  gchar **envp = g_get_environ ();
+  gchar *out = NULL;
+  gchar *err = NULL;
+  GError *error = NULL;
+  int wait_status = -1;
+
+  g_test_summary ("With G_SPAWN_SEARCH_PATH, spawn-test-helper "
+                  "means $PATH/spawn-test-helper.");
+
+  envp = g_environ_setenv (envp, "PATH", subdir, TRUE);
+
+  g_ptr_array_add (argv,
+                   g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL));
+  g_ptr_array_add (argv, g_strdup ("--search-path"));
+  g_ptr_array_add (argv, g_strdup ("--"));
+  g_ptr_array_add (argv, g_strdup ("spawn-test-helper"));
+  g_ptr_array_add (argv, NULL);
+
+  g_spawn_sync (here,
+                (char **) argv->pdata,
+                envp,
+                G_SPAWN_DEFAULT,
+                NULL,  /* child setup */
+                NULL,  /* user data */
+                &out,
+                &err,
+                &wait_status,
+                &error);
+  g_assert_no_error (error);
+
+  g_test_message ("%s", out);
+  g_test_message ("%s", err);
+  g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir"));
+
+#ifdef G_OS_UNIX
+  g_assert_true (WIFEXITED (wait_status));
+  g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5);
+#endif
+
+  g_strfreev (envp);
+  g_free (here);
+  g_free (subdir);
+  g_free (out);
+  g_free (err);
+  g_ptr_array_unref (argv);
+}
+
+static void
+test_search_path_from_envp (void)
+{
+  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
+  gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL);
+  gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL);
+  gchar **envp = g_get_environ ();
+  gchar *out = NULL;
+  gchar *err = NULL;
+  GError *error = NULL;
+  int wait_status = -1;
+
+  g_test_summary ("With G_SPAWN_SEARCH_PATH_FROM_ENVP, spawn-test-helper "
+                  "means $PATH/spawn-test-helper with $PATH from envp.");
+
+  envp = g_environ_setenv (envp, "PATH", here, TRUE);
+
+  g_ptr_array_add (argv,
+                   g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL));
+  g_ptr_array_add (argv, g_strdup ("--search-path-from-envp"));
+  g_ptr_array_add (argv, g_strdup ("--set-path-in-envp"));
+  g_ptr_array_add (argv, g_strdup (subdir));
+  g_ptr_array_add (argv, g_strdup ("--"));
+  g_ptr_array_add (argv, g_strdup ("spawn-test-helper"));
+  g_ptr_array_add (argv, NULL);
+
+  g_spawn_sync (here,
+                (char **) argv->pdata,
+                envp,
+                G_SPAWN_DEFAULT,
+                NULL,  /* child setup */
+                NULL,  /* user data */
+                &out,
+                &err,
+                &wait_status,
+                &error);
+  g_assert_no_error (error);
+
+  g_test_message ("%s", out);
+  g_test_message ("%s", err);
+  g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir"));
+
+#ifdef G_OS_UNIX
+  g_assert_true (WIFEXITED (wait_status));
+  g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5);
+#endif
+
+  g_strfreev (envp);
+  g_free (here);
+  g_free (subdir);
+  g_free (out);
+  g_free (err);
+  g_ptr_array_unref (argv);
+}
+
+static void
+test_search_path_ambiguous (void)
+{
+  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
+  gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL);
+  gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL);
+  gchar **envp = g_get_environ ();
+  gchar *out = NULL;
+  gchar *err = NULL;
+  GError *error = NULL;
+  int wait_status = -1;
+
+  g_test_summary ("With G_SPAWN_SEARCH_PATH and G_SPAWN_SEARCH_PATH_FROM_ENVP, "
+                  "the latter wins.");
+
+  envp = g_environ_setenv (envp, "PATH", here, TRUE);
+
+  g_ptr_array_add (argv,
+                   g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL));
+  g_ptr_array_add (argv, g_strdup ("--search-path"));
+  g_ptr_array_add (argv, g_strdup ("--search-path-from-envp"));
+  g_ptr_array_add (argv, g_strdup ("--set-path-in-envp"));
+  g_ptr_array_add (argv, g_strdup (subdir));
+  g_ptr_array_add (argv, g_strdup ("--"));
+  g_ptr_array_add (argv, g_strdup ("spawn-test-helper"));
+  g_ptr_array_add (argv, NULL);
+
+  g_spawn_sync (here,
+                (char **) argv->pdata,
+                envp,
+                G_SPAWN_DEFAULT,
+                NULL,  /* child setup */
+                NULL,  /* user data */
+                &out,
+                &err,
+                &wait_status,
+                &error);
+  g_assert_no_error (error);
+
+  g_test_message ("%s", out);
+  g_test_message ("%s", err);
+  g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir"));
+
+#ifdef G_OS_UNIX
+  g_assert_true (WIFEXITED (wait_status));
+  g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5);
+#endif
+
+  g_strfreev (envp);
+  g_free (here);
+  g_free (subdir);
+  g_free (out);
+  g_free (err);
+  g_ptr_array_unref (argv);
+}
+
+static void
+test_search_path_fallback_in_environ (void)
+{
+  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
+  gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL);
+  gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL);
+  gchar **envp = g_get_environ ();
+  gchar *out = NULL;
+  gchar *err = NULL;
+  GError *error = NULL;
+  int wait_status = -1;
+
+  g_test_summary ("With G_SPAWN_SEARCH_PATH but no PATH, a fallback is used.");
+  /* We can't make a meaningful assertion about what the fallback *is*,
+   * but we can assert that it *includes* the current working directory. */
+
+  if (g_file_test ("/usr/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE) ||
+      g_file_test ("/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE))
+    {
+      g_test_skip ("Not testing fallback with unknown spawn-test-helper "
+                   "executable in /usr/bin:/bin");
+      return;
+    }
+
+  envp = g_environ_unsetenv (envp, "PATH");
+
+  g_ptr_array_add (argv,
+                   g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL));
+  g_ptr_array_add (argv, g_strdup ("--search-path"));
+  g_ptr_array_add (argv, g_strdup ("--set-path-in-envp"));
+  g_ptr_array_add (argv, g_strdup (subdir));
+  g_ptr_array_add (argv, g_strdup ("--"));
+  g_ptr_array_add (argv, g_strdup ("spawn-test-helper"));
+  g_ptr_array_add (argv, NULL);
+
+  g_spawn_sync (here,
+                (char **) argv->pdata,
+                envp,
+                G_SPAWN_DEFAULT,
+                NULL,  /* child setup */
+                NULL,  /* user data */
+                &out,
+                &err,
+                &wait_status,
+                &error);
+  g_assert_no_error (error);
+
+  g_test_message ("%s", out);
+  g_test_message ("%s", err);
+  g_assert_nonnull (strstr (err, "this is spawn-test-helper from glib/tests"));
+
+#ifdef G_OS_UNIX
+  g_assert_true (WIFEXITED (wait_status));
+  g_assert_cmpint (WEXITSTATUS (wait_status), ==, 0);
+#endif
+
+  g_strfreev (envp);
+  g_free (here);
+  g_free (subdir);
+  g_free (out);
+  g_free (err);
+  g_ptr_array_unref (argv);
+}
+
+static void
+test_search_path_fallback_in_envp (void)
+{
+  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
+  gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL);
+  gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL);
+  gchar **envp = g_get_environ ();
+  gchar *out = NULL;
+  gchar *err = NULL;
+  GError *error = NULL;
+  int wait_status = -1;
+
+  g_test_summary ("With G_SPAWN_SEARCH_PATH_FROM_ENVP but no PATH, a fallback is used.");
+  /* We can't make a meaningful assertion about what the fallback *is*,
+   * but we can assert that it *includes* the current working directory. */
+
+  if (g_file_test ("/usr/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE) ||
+      g_file_test ("/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE))
+    {
+      g_test_skip ("Not testing fallback with unknown spawn-test-helper "
+                   "executable in /usr/bin:/bin");
+      return;
+    }
+
+  envp = g_environ_setenv (envp, "PATH", subdir, TRUE);
+
+  g_ptr_array_add (argv,
+                   g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL));
+  g_ptr_array_add (argv, g_strdup ("--search-path-from-envp"));
+  g_ptr_array_add (argv, g_strdup ("--unset-path-in-envp"));
+  g_ptr_array_add (argv, g_strdup ("--"));
+  g_ptr_array_add (argv, g_strdup ("spawn-test-helper"));
+  g_ptr_array_add (argv, NULL);
+
+  g_spawn_sync (here,
+                (char **) argv->pdata,
+                envp,
+                G_SPAWN_DEFAULT,
+                NULL,  /* child setup */
+                NULL,  /* user data */
+                &out,
+                &err,
+                &wait_status,
+                &error);
+  g_assert_no_error (error);
+
+  g_test_message ("%s", out);
+  g_test_message ("%s", err);
+  g_assert_nonnull (strstr (err, "this is spawn-test-helper from glib/tests"));
+
+#ifdef G_OS_UNIX
+  g_assert_true (WIFEXITED (wait_status));
+  g_assert_cmpint (WEXITSTATUS (wait_status), ==, 0);
+#endif
+
+  g_strfreev (envp);
+  g_free (here);
+  g_free (subdir);
+  g_free (out);
+  g_free (err);
+  g_ptr_array_unref (argv);
+}
+
+static void
+test_search_path_heap_allocation (void)
+{
+  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
+  /* Must be longer than the arbitrary 4000 byte limit for stack allocation
+   * in gspawn.c */
+  char placeholder[4096];
+  gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL);
+  gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL);
+  gchar *long_dir = NULL;
+  gchar *long_path = NULL;
+  gchar **envp = g_get_environ ();
+  gchar *out = NULL;
+  gchar *err = NULL;
+  GError *error = NULL;
+  int wait_status = -1;
+  gsize i;
+
+  memset (placeholder, '_', sizeof (placeholder));
+  /* Force search_path_buffer to be heap-allocated */
+  long_dir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", placeholder, NULL);
+  long_path = g_strjoin (G_SEARCHPATH_SEPARATOR_S, subdir, long_dir, NULL);
+  envp = g_environ_setenv (envp, "PATH", long_path, TRUE);
+
+  g_ptr_array_add (argv,
+                   g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL));
+  g_ptr_array_add (argv, g_strdup ("--search-path"));
+  g_ptr_array_add (argv, g_strdup ("--"));
+  g_ptr_array_add (argv, g_strdup ("spawn-test-helper"));
+
+  /* Add enough arguments to make argv longer than the arbitrary 4000 byte
+   * limit for stack allocation in gspawn.c.
+   * This assumes sizeof (char *) >= 4. */
+  for (i = 0; i < 1001; i++)
+    g_ptr_array_add (argv, g_strdup ("_"));
+
+  g_ptr_array_add (argv, NULL);
+
+  g_spawn_sync (here,
+                (char **) argv->pdata,
+                envp,
+                G_SPAWN_DEFAULT,
+                NULL,  /* child setup */
+                NULL,  /* user data */
+                &out,
+                &err,
+                &wait_status,
+                &error);
+  g_assert_no_error (error);
+
+  g_test_message ("%s", out);
+  g_test_message ("%s", err);
+  g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir"));
+
+#ifdef G_OS_UNIX
+  g_assert_true (WIFEXITED (wait_status));
+  g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5);
+#endif
+
+  g_strfreev (envp);
+  g_free (here);
+  g_free (subdir);
+  g_free (out);
+  g_free (err);
+  g_ptr_array_unref (argv);
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/spawn/do-not-search", test_do_not_search);
+  g_test_add_func ("/spawn/search-path", test_search_path);
+  g_test_add_func ("/spawn/search-path-from-envp", test_search_path_from_envp);
+  g_test_add_func ("/spawn/search-path-ambiguous", test_search_path_ambiguous);
+  g_test_add_func ("/spawn/search-path-heap-allocation",
+                   test_search_path_heap_allocation);
+  g_test_add_func ("/spawn/search-path-fallback-in-environ",
+                   test_search_path_fallback_in_environ);
+  g_test_add_func ("/spawn/search-path-fallback-in-envp",
+                   test_search_path_fallback_in_envp);
+
+  return g_test_run ();
+}
diff --git a/glib/tests/spawn-test-helper.c b/glib/tests/spawn-test-helper.c
new file mode 100644 (file)
index 0000000..301f3f3
--- /dev/null
@@ -0,0 +1,7 @@
+#include <stdio.h>
+
+int main (void)
+{
+  fprintf (stderr, "this is spawn-test-helper from glib/tests\n");
+  return 0;
+}
index d938ddf..f334219 100644 (file)
@@ -1,5 +1,5 @@
 project('glib', 'c', 'cpp',
-  version : '2.66.4',
+  version : '2.66.5',
   # NOTE: We keep this pinned at 0.49 because that's what Debian 10 ships
   meson_version : '>= 0.49.2',
   default_options : [