mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 01:58:54 +01:00 
			
		
		
		
	gmenumodel: disallow exporting large menus on the bus
This solves problems with validating untrusted inputs from D-Bus, where invalid numbers of added and removed menu entries, and positions, could be specified. Original patch from https://bugzilla.gnome.org/show_bug.cgi?id=728733#c7, tweaked by Philip Withnall to add a few code comments and make `G_MENU_EXPORTER_MAX_SECTION_SIZE` public so callers can check their inputs against it if they want. Also tweaked to use `g_warning()` instead of the nonexistent `g_dbus_warning()`. Fixes: #861
This commit is contained in:
		
				
					committed by
					
						
						Philip Withnall
					
				
			
			
				
	
			
			
			
						parent
						
							a0dbaeed2f
						
					
				
				
					commit
					89a7bbcf6e
				
			@@ -4328,6 +4328,7 @@ g_power_profile_level_get_type
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<SECTION>
 | 
					<SECTION>
 | 
				
			||||||
<FILE>gmenuexporter</FILE>
 | 
					<FILE>gmenuexporter</FILE>
 | 
				
			||||||
 | 
					G_MENU_EXPORTER_MAX_SECTION_SIZE
 | 
				
			||||||
g_dbus_connection_export_menu_model
 | 
					g_dbus_connection_export_menu_model
 | 
				
			||||||
g_dbus_connection_unexport_menu_model
 | 
					g_dbus_connection_unexport_menu_model
 | 
				
			||||||
</SECTION>
 | 
					</SECTION>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "gdbusmenumodel.h"
 | 
					#include "gdbusmenumodel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "gmenuexporter.h"
 | 
				
			||||||
#include "gmenumodel.h"
 | 
					#include "gmenumodel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Prelude {{{1 */
 | 
					/* Prelude {{{1 */
 | 
				
			||||||
@@ -580,6 +581,8 @@ g_dbus_menu_group_deactivate (GDBusMenuGroup *group)
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* @menu_id, @position, @removed and @added are all untrusted since they can
 | 
				
			||||||
 | 
					 * come from an external process. */
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
g_dbus_menu_group_changed (GDBusMenuGroup *group,
 | 
					g_dbus_menu_group_changed (GDBusMenuGroup *group,
 | 
				
			||||||
                           guint           menu_id,
 | 
					                           guint           menu_id,
 | 
				
			||||||
@@ -593,6 +596,20 @@ g_dbus_menu_group_changed (GDBusMenuGroup *group,
 | 
				
			|||||||
  GSequence *items;
 | 
					  GSequence *items;
 | 
				
			||||||
  GVariant *item;
 | 
					  GVariant *item;
 | 
				
			||||||
  gint n_added;
 | 
					  gint n_added;
 | 
				
			||||||
 | 
					  gint n_items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Caller has to check this. */
 | 
				
			||||||
 | 
					  g_assert (g_variant_is_of_type (added, G_VARIANT_TYPE ("aa{sv}")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  n_added = g_variant_n_children (added);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (position < 0 || position >= G_MENU_EXPORTER_MAX_SECTION_SIZE ||
 | 
				
			||||||
 | 
					      removed < 0 || removed >= G_MENU_EXPORTER_MAX_SECTION_SIZE ||
 | 
				
			||||||
 | 
					      n_added >= G_MENU_EXPORTER_MAX_SECTION_SIZE)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      g_warning ("invalid arguments");
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /* We could have signals coming to us when we're not active (due to
 | 
					  /* We could have signals coming to us when we're not active (due to
 | 
				
			||||||
   * some other process having subscribed to this group) or when we're
 | 
					   * some other process having subscribed to this group) or when we're
 | 
				
			||||||
@@ -611,9 +628,17 @@ g_dbus_menu_group_changed (GDBusMenuGroup *group,
 | 
				
			|||||||
      g_hash_table_insert (group->menus, GINT_TO_POINTER (menu_id), items);
 | 
					      g_hash_table_insert (group->menus, GINT_TO_POINTER (menu_id), items);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  point = g_sequence_get_iter_at_pos (items, position + removed);
 | 
					  /* Don’t need to worry about overflow due to the low value of
 | 
				
			||||||
 | 
					   * %G_MENU_EXPORTER_MAX_SECTION_SIZE. */
 | 
				
			||||||
 | 
					  n_items = g_sequence_get_length (items);
 | 
				
			||||||
 | 
					  if (position + removed > n_items ||
 | 
				
			||||||
 | 
					      n_items - removed + n_added > G_MENU_EXPORTER_MAX_SECTION_SIZE)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      g_warning ("invalid arguments");
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  g_return_if_fail (point != NULL);
 | 
					  point = g_sequence_get_iter_at_pos (items, position + removed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (removed)
 | 
					  if (removed)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -623,7 +648,7 @@ g_dbus_menu_group_changed (GDBusMenuGroup *group,
 | 
				
			|||||||
      g_sequence_remove_range (start, point);
 | 
					      g_sequence_remove_range (start, point);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  n_added = g_variant_iter_init (&iter, added);
 | 
					  g_variant_iter_init (&iter, added);
 | 
				
			||||||
  while (g_variant_iter_loop (&iter, "@a{sv}", &item))
 | 
					  while (g_variant_iter_loop (&iter, "@a{sv}", &item))
 | 
				
			||||||
    g_sequence_insert_before (point, g_dbus_menu_group_create_item (item));
 | 
					    g_sequence_insert_before (point, g_dbus_menu_group_create_item (item));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -252,10 +252,17 @@ g_menu_exporter_menu_items_changed (GMenuModel *model,
 | 
				
			|||||||
  GMenuExporterMenu *menu = user_data;
 | 
					  GMenuExporterMenu *menu = user_data;
 | 
				
			||||||
  GSequenceIter *point;
 | 
					  GSequenceIter *point;
 | 
				
			||||||
  gint i;
 | 
					  gint i;
 | 
				
			||||||
 | 
					  gint n_items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  g_assert (menu->model == model);
 | 
					  g_assert (menu->model == model);
 | 
				
			||||||
  g_assert (menu->item_links != NULL);
 | 
					  g_assert (menu->item_links != NULL);
 | 
				
			||||||
  g_assert (position + removed <= g_sequence_get_length (menu->item_links));
 | 
					
 | 
				
			||||||
 | 
					  n_items = g_sequence_get_length (menu->item_links);
 | 
				
			||||||
 | 
					  g_assert (position >= 0 && position < G_MENU_EXPORTER_MAX_SECTION_SIZE);
 | 
				
			||||||
 | 
					  g_assert (removed >= 0 && removed < G_MENU_EXPORTER_MAX_SECTION_SIZE);
 | 
				
			||||||
 | 
					  g_assert (added < G_MENU_EXPORTER_MAX_SECTION_SIZE);
 | 
				
			||||||
 | 
					  g_assert (position + removed <= n_items);
 | 
				
			||||||
 | 
					  g_assert (n_items - removed + added < G_MENU_EXPORTER_MAX_SECTION_SIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
 | 
					  point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
 | 
				
			||||||
  g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
 | 
					  g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
 | 
				
			||||||
@@ -770,6 +777,10 @@ g_menu_exporter_method_call (GDBusConnection       *connection,
 | 
				
			|||||||
 * constraint is violated, the export will fail and 0 will be
 | 
					 * constraint is violated, the export will fail and 0 will be
 | 
				
			||||||
 * returned (with @error set accordingly).
 | 
					 * returned (with @error set accordingly).
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 | 
					 * Exporting menus with sections containing more than
 | 
				
			||||||
 | 
					 * %G_MENU_EXPORTER_MAX_SECTION_SIZE items is not supported and results in
 | 
				
			||||||
 | 
					 * undefined behavior.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 * You can unexport the menu model using
 | 
					 * You can unexport the menu model using
 | 
				
			||||||
 * g_dbus_connection_unexport_menu_model() with the return value of
 | 
					 * g_dbus_connection_unexport_menu_model() with the return value of
 | 
				
			||||||
 * this function.
 | 
					 * this function.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,19 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
G_BEGIN_DECLS
 | 
					G_BEGIN_DECLS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * G_MENU_EXPORTER_MAX_SECTION_SIZE:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The maximum number of entries in a menu section supported by
 | 
				
			||||||
 | 
					 * g_dbus_connection_export_menu_model().
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The exact value of the limit may change in future GLib versions.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Since: 2.76
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define G_MENU_EXPORTER_MAX_SECTION_SIZE 1000 \
 | 
				
			||||||
 | 
					  GIO_AVAILABLE_MACRO_IN_2_76
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GIO_AVAILABLE_IN_2_32
 | 
					GIO_AVAILABLE_IN_2_32
 | 
				
			||||||
guint                   g_dbus_connection_export_menu_model             (GDBusConnection  *connection,
 | 
					guint                   g_dbus_connection_export_menu_model             (GDBusConnection  *connection,
 | 
				
			||||||
                                                                         const gchar      *object_path,
 | 
					                                                                         const gchar      *object_path,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1500,6 +1500,149 @@ test_menuitem (void)
 | 
				
			|||||||
  g_object_unref (submenu);
 | 
					  g_object_unref (submenu);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static GDBusInterfaceInfo *
 | 
				
			||||||
 | 
					org_gtk_Menus_get_interface (void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  static GDBusInterfaceInfo *interface_info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (interface_info == NULL)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      GError *error = NULL;
 | 
				
			||||||
 | 
					      GDBusNodeInfo *info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      info = g_dbus_node_info_new_for_xml ("<node>"
 | 
				
			||||||
 | 
					                                           "  <interface name='org.gtk.Menus'>"
 | 
				
			||||||
 | 
					                                           "    <method name='Start'>"
 | 
				
			||||||
 | 
					                                           "      <arg type='au' name='groups' direction='in'/>"
 | 
				
			||||||
 | 
					                                           "      <arg type='a(uuaa{sv})' name='content' direction='out'/>"
 | 
				
			||||||
 | 
					                                           "    </method>"
 | 
				
			||||||
 | 
					                                           "    <method name='End'>"
 | 
				
			||||||
 | 
					                                           "      <arg type='au' name='groups' direction='in'/>"
 | 
				
			||||||
 | 
					                                           "    </method>"
 | 
				
			||||||
 | 
					                                           "    <signal name='Changed'>"
 | 
				
			||||||
 | 
					                                           "      arg type='a(uuuuaa{sv})' name='changes'/>"
 | 
				
			||||||
 | 
					                                           "    </signal>"
 | 
				
			||||||
 | 
					                                           "  </interface>"
 | 
				
			||||||
 | 
					                                           "</node>", &error);
 | 
				
			||||||
 | 
					      if (info == NULL)
 | 
				
			||||||
 | 
					        g_error ("%s\n", error->message);
 | 
				
			||||||
 | 
					      interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
 | 
				
			||||||
 | 
					      g_assert (interface_info != NULL);
 | 
				
			||||||
 | 
					      g_dbus_interface_info_ref (interface_info);
 | 
				
			||||||
 | 
					      g_dbus_node_info_unref (info);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return interface_info;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					g_menu_exporter_method_call (GDBusConnection       *connection,
 | 
				
			||||||
 | 
					                             const gchar           *sender,
 | 
				
			||||||
 | 
					                             const gchar           *object_path,
 | 
				
			||||||
 | 
					                             const gchar           *interface_name,
 | 
				
			||||||
 | 
					                             const gchar           *method_name,
 | 
				
			||||||
 | 
					                             GVariant              *parameters,
 | 
				
			||||||
 | 
					                             GDBusMethodInvocation *invocation,
 | 
				
			||||||
 | 
					                             gpointer               user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  const struct {
 | 
				
			||||||
 | 
					    guint position;
 | 
				
			||||||
 | 
					    guint removed;
 | 
				
			||||||
 | 
					  } data[] = {
 | 
				
			||||||
 | 
					      { -2, 4 },
 | 
				
			||||||
 | 
					      { 0, 3 },
 | 
				
			||||||
 | 
					      { 4, 1 }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  gsize i;
 | 
				
			||||||
 | 
					  GError *error = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  g_dbus_method_invocation_return_value (invocation, g_variant_new_parsed ("@(a(uuaa{sv})) ([(0, 0, [{ 'label': <'test'> }])],)"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* invalid signatures */
 | 
				
			||||||
 | 
					  g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
 | 
				
			||||||
 | 
					                                 g_variant_new_parsed ("([(1, 2, 3)],)"), &error);
 | 
				
			||||||
 | 
					  g_assert_no_error (error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* add an item at an invalid position */
 | 
				
			||||||
 | 
					  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
 | 
				
			||||||
 | 
					  g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
 | 
				
			||||||
 | 
					                                 g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [{ 'label': <'test'> }])],)", 0, 0, 2, 0),
 | 
				
			||||||
 | 
					                                 &error);
 | 
				
			||||||
 | 
					  g_assert_no_error (error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (i = 0; i < G_N_ELEMENTS (data); i++)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      GVariant *params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
 | 
				
			||||||
 | 
					      params = g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [])],)", 0, 0, data[i].position, data[i].removed);
 | 
				
			||||||
 | 
					      g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed", params, &error);
 | 
				
			||||||
 | 
					      g_assert_no_error (error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					menu_changed (GMenuModel *menu,
 | 
				
			||||||
 | 
					             gint        position,
 | 
				
			||||||
 | 
					              gint        removed,
 | 
				
			||||||
 | 
					              gint        added,
 | 
				
			||||||
 | 
					              gpointer    user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  unsigned int *counter = user_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  *counter += 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					test_input_validation (void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  const GDBusInterfaceVTable vtable = {
 | 
				
			||||||
 | 
					    g_menu_exporter_method_call, NULL, NULL, { NULL, }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  GError *error = NULL;
 | 
				
			||||||
 | 
					  GDBusConnection *bus;
 | 
				
			||||||
 | 
					  GDBusMenuModel *proxy;
 | 
				
			||||||
 | 
					  guint id;
 | 
				
			||||||
 | 
					  const gchar *bus_name;
 | 
				
			||||||
 | 
					  GMainLoop *loop;
 | 
				
			||||||
 | 
					  unsigned int n_signal_emissions = 0;
 | 
				
			||||||
 | 
					  gulong signal_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/861");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
 | 
				
			||||||
 | 
					  g_assert_no_error (error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  id = g_dbus_connection_register_object (bus, "/", org_gtk_Menus_get_interface (),
 | 
				
			||||||
 | 
					                                          &vtable, NULL, NULL, &error);
 | 
				
			||||||
 | 
					  g_assert_no_error (error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bus_name = g_dbus_connection_get_unique_name (bus);
 | 
				
			||||||
 | 
					  proxy = g_dbus_menu_model_get (bus, bus_name, "/");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  signal_id = g_signal_connect (proxy, "items-changed", G_CALLBACK (menu_changed), &n_signal_emissions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* get over laziness */
 | 
				
			||||||
 | 
					  g_menu_model_get_n_items (G_MENU_MODEL (proxy));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loop = g_main_loop_new (NULL, FALSE);
 | 
				
			||||||
 | 
					  g_timeout_add (100, stop_loop, loop);
 | 
				
			||||||
 | 
					  g_main_loop_run (loop);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* "items-changed" should only be emitted for the initial contents of
 | 
				
			||||||
 | 
					   * the menu. Subsequent calls are all invalid.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  g_assert_cmpuint (n_signal_emissions, ==, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  g_test_assert_expected_messages ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  g_main_loop_unref (loop);
 | 
				
			||||||
 | 
					  g_dbus_connection_unregister_object (bus, id);
 | 
				
			||||||
 | 
					  g_signal_handler_disconnect (proxy, signal_id);
 | 
				
			||||||
 | 
					  g_object_unref (proxy);
 | 
				
			||||||
 | 
					  g_object_unref (bus);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Epilogue {{{1 */
 | 
					/* Epilogue {{{1 */
 | 
				
			||||||
int
 | 
					int
 | 
				
			||||||
main (int argc, char **argv)
 | 
					main (int argc, char **argv)
 | 
				
			||||||
@@ -1523,6 +1666,7 @@ main (int argc, char **argv)
 | 
				
			|||||||
  g_test_add_func ("/gmenu/mutable", test_mutable);
 | 
					  g_test_add_func ("/gmenu/mutable", test_mutable);
 | 
				
			||||||
  g_test_add_func ("/gmenu/convenience", test_convenience);
 | 
					  g_test_add_func ("/gmenu/convenience", test_convenience);
 | 
				
			||||||
  g_test_add_func ("/gmenu/menuitem", test_menuitem);
 | 
					  g_test_add_func ("/gmenu/menuitem", test_menuitem);
 | 
				
			||||||
 | 
					  g_test_add_func ("/gmenu/input-validation", test_input_validation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ret = g_test_run ();
 | 
					  ret = g_test_run ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user