Merge branch '2168-io-module-default' into 'master'

gio: Ignore various environment variables when running as setuid

Closes #2168

See merge request GNOME/glib!1862
diff --git a/docs/reference/gio/overview.xml b/docs/reference/gio/overview.xml
index b442285..816afb9 100644
--- a/docs/reference/gio/overview.xml
+++ b/docs/reference/gio/overview.xml
@@ -436,6 +436,9 @@
 	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 @@
         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>
diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c
index 26d52ea..d26c4d2 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"
@@ -1285,6 +1286,7 @@
                                  GCancellable  *cancellable,
                                  GError       **error)
 {
+  gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
   gchar *ret, *s = NULL;
   const gchar *starter_bus;
   GError *local_error;
@@ -1323,10 +1325,12 @@
       _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"));
+      ret = !is_setuid ? g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS")) : NULL;
       if (ret == NULL)
         {
           ret = g_strdup ("unix:path=/var/run/dbus/system_bus_socket");
@@ -1334,7 +1338,7 @@
       break;
 
     case G_BUS_TYPE_SESSION:
-      ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
+      ret = !is_setuid ? g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS")) : NULL;
       if (ret == NULL)
         {
           ret = get_session_address_platform_specific (&local_error);
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index 6b91854..c10e1fc 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -305,6 +305,27 @@
   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 const gchar * const *
 get_lowercase_current_desktops (void)
 {
@@ -320,12 +341,22 @@
       if (envvar)
         {
           gint i, j;
+          gsize tmp_len;
 
           tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0);
+          tmp_len = g_strv_length (tmp);
 
           for (i = 0; tmp[i]; i++)
-            for (j = 0; tmp[i][j]; j++)
-              tmp[i][j] = g_ascii_tolower (tmp[i][j]);
+            {
+              /* If the desktop is invalid, drop it and shift the following
+               * ones (and trailing %NULL) up. */
+              if (!validate_xdg_desktop (tmp[i]))
+                memmove (tmp + i, tmp + i + 1, tmp_len - i);
+
+              /* 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);
@@ -344,6 +375,7 @@
   if (g_once_init_enter (&result))
     {
       gchar **tmp;
+      gsize tmp_len, i;
 
       if (!value)
         value = g_getenv ("XDG_CURRENT_DESKTOP");
@@ -352,6 +384,15 @@
         value = "";
 
       tmp = g_strsplit (value, ":", 0);
+      tmp_len = g_strv_length (tmp);
+
+      for (i = 0; tmp[i]; i++)
+        {
+          /* If the desktop is invalid, drop it and shift the following
+           * ones (and trailing %NULL) up. */
+          if (!validate_xdg_desktop (tmp[i]))
+            memmove (tmp + i, tmp + i + 1, tmp_len - i);
+        }
 
       g_once_init_leave (&result, tmp);
     }
diff --git a/gio/giomodule.c b/gio/giomodule.c
index f20b648..4c4c75b 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"
@@ -812,6 +813,9 @@
       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)
     {
@@ -961,6 +965,9 @@
       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)
     {
@@ -1159,8 +1166,16 @@
 get_gio_module_dir (void)
 {
   gchar *module_dir;
+  gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
 
-  module_dir = g_strdup (g_getenv ("GIO_MODULE_DIR"));
+  /* 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
@@ -1192,13 +1207,14 @@
 
   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 d9ce6f0..53933f9 100644
--- a/gio/gresource.c
+++ b/gio/gresource.c
@@ -32,6 +32,8 @@
 #include <gio/gzlibdecompressor.h>
 #include <gio/gconverterinputstream.h>
 
+#include "glib-private.h"
+
 struct _GResource
 {
   int ref_count;
@@ -163,7 +165,7 @@
  * 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
  *
@@ -334,10 +336,13 @@
 
   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 cbc3fad..26b9a65 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 @@
    */
   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 @@
 
       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++);