From: DongHun Kwak Date: Fri, 29 Oct 2021 01:27:37 +0000 (+0900) Subject: Imported Upstream version 2.66.5 X-Git-Tag: upstream/2.66.5^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=b47d6166aeba31fa97d89743cfcc730a09c39090;p=platform%2Fupstream%2Fglib.git Imported Upstream version 2.66.5 --- diff --git a/NEWS b/NEWS index 56d27f6..a9becc9 100644 --- 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 ================================== diff --git a/docs/reference/gio/overview.xml b/docs/reference/gio/overview.xml index 7817ab6..d82f1ac 100644 --- a/docs/reference/gio/overview.xml +++ b/docs/reference/gio/overview.xml @@ -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. + + This environment variable is ignored when running in a setuid program. + @@ -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. + + This environment variable is ignored when running in a setuid program. + diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c index 3dd3cc8..0044cd3 100644 --- a/gio/gdbusaddress.c +++ b/gio/gdbusaddress.c @@ -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); diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index b779b30..2b139a0 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -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); } diff --git a/gio/giomodule.c b/gio/giomodule.c index dc4d6d3..aaf4636 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -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; diff --git a/gio/gresource.c b/gio/gresource.c index b7222b8..13632d9 100644 --- a/gio/gresource.c +++ b/gio/gresource.c @@ -32,6 +32,8 @@ #include #include +#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; diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index 0b94f76..8e203db 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -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++); diff --git a/gio/gsocket.c b/gio/gsocket.c index 0f8f925..e911de7 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -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, diff --git a/gio/tests/desktop-app-info.c b/gio/tests/desktop-app-info.c index f4e509a..bc50ff4 100644 --- a/gio/tests/desktop-app-info.c +++ b/gio/tests/desktop-app-info.c @@ -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 index 0000000..dffaa24 --- /dev/null +++ b/gio/tests/desktop-files/usr/applications/invalid-desktop.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Application +Name=appinfo-test +OnlyShowIn=../invalid/desktop;GNOME +NotShowIn=ROX; diff --git a/gio/tests/file.c b/gio/tests/file.c index c3877af..d876965 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -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, diff --git a/glib/gdate.c b/glib/gdate.c index 391b142..253ab65 100644 --- a/glib/gdate.c +++ b/glib/gdate.c @@ -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); diff --git a/glib/gdatetime.c b/glib/gdatetime.c index 02cc3bd..a43efab 100644 --- a/glib/gdatetime.c +++ b/glib/gdatetime.c @@ -80,6 +80,11 @@ #ifndef G_OS_WIN32 #include #include +#else +#if defined (_MSC_VER) && (_MSC_VER < 1800) +/* fallback implementation for isnan() on VS2012 and earlier */ +#define isnan _isnan +#endif #endif /* !G_OS_WIN32 */ /** diff --git a/glib/gspawn.c b/glib/gspawn.c index c37ac7c..c8e0ca8 100644 --- a/glib/gspawn.c +++ b/glib/gspawn.c @@ -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; } diff --git a/glib/gthread-posix.c b/glib/gthread-posix.c index f360559..f09f58a 100644 --- a/glib/gthread-posix.c +++ b/glib/gthread-posix.c @@ -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 */ diff --git a/glib/gthread-win32.c b/glib/gthread-win32.c index 54f74f2..0c37dc6 100644 --- a/glib/gthread-win32.c +++ b/glib/gthread-win32.c @@ -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 */ diff --git a/glib/tests/date.c b/glib/tests/date.c index 38de1d9..542293c 100644 --- a/glib/tests/date.c +++ b/glib/tests/date.c @@ -191,6 +191,10 @@ test_parse_invalid (void) { /* Incomplete UTF-8 sequence */ "\xfd", + /* Ridiculously long input */ + "12345678901234567890123456789012345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890123456789012345678901234567890", }; gsize i; diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c index 0203dd0..3b1d9cf 100644 --- a/glib/tests/gdatetime.c +++ b/glib/tests/gdatetime.c @@ -29,6 +29,10 @@ #ifdef G_OS_WIN32 #define WIN32_LEAN_AND_MEAN #include + +#ifndef NAN +#define NAN HUGE_VAL * 0.0f +#endif #endif #define ASSERT_DATE(dt,y,m,d) G_STMT_START { \ diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 6eb23e8..d35bd16 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -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 index 0000000..351254c --- /dev/null +++ b/glib/tests/path-test-subdir/meson.build @@ -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 index 0000000..f9f2cee --- /dev/null +++ b/glib/tests/path-test-subdir/spawn-test-helper.c @@ -0,0 +1,7 @@ +#include + +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 index 0000000..378c203 --- /dev/null +++ b/glib/tests/spawn-path-search-helper.c @@ -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 + * . + */ + +#include +#include + +#include + +#ifdef G_OS_UNIX +#include +#include +#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 index 0000000..f4278f3 --- /dev/null +++ b/glib/tests/spawn-path-search.c @@ -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 + * . + */ + +#include + +#ifdef G_OS_UNIX +#include +#include +#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 index 0000000..301f3f3 --- /dev/null +++ b/glib/tests/spawn-test-helper.c @@ -0,0 +1,7 @@ +#include + +int main (void) +{ + fprintf (stderr, "this is spawn-test-helper from glib/tests\n"); + return 0; +} diff --git a/meson.build b/meson.build index d938ddf..f334219 100644 --- a/meson.build +++ b/meson.build @@ -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 : [