Commit message: None
CVE: CVE-2013-0292
CWE: CWE-20
Vulnerability description: The dbus_g_proxy_manager_filter function in dbus-gproxy in Dbus-glib before 0.100.1 does not properly verify the sender of NameOwnerChanged signals, which allows local users to gain privileges via a spoofed signal.

Project: dbus

dbus_g_proxy_manager_get (DBusConnection *connection)
{
  DBusGProxyManager *manager;

  dbus_connection_allocate_data_slot (&g_proxy_manager_slot);
  if (g_proxy_manager_slot < 0)
    g_error ("out of memory");
  
  g_static_mutex_lock (&connection_g_proxy_lock);
  
  manager = dbus_connection_get_data (connection, g_proxy_manager_slot);
  if (manager != NULL)
    {
      dbus_connection_free_data_slot (&g_proxy_manager_slot);
      dbus_g_proxy_manager_ref (manager);
      g_static_mutex_unlock (&connection_g_proxy_lock);
      return manager;
    }
  
  manager = g_new0 (DBusGProxyManager, 1);

  manager->refcount = 1;
  manager->connection = connection;

  g_static_mutex_init (&manager->lock);

  /* Proxy managers keep the connection alive, which means that
   * DBusGProxy indirectly does. To free a connection you have to free
   * all the proxies referring to it.
   */
  dbus_connection_ref (manager->connection);

  dbus_connection_set_data (connection, g_proxy_manager_slot,
                            manager, NULL);

  dbus_connection_add_filter (connection, dbus_g_proxy_manager_filter,
                              manager, NULL);
  
  g_static_mutex_unlock (&connection_g_proxy_lock);
  
  return manager;
}

Project: dbus

dbus_g_proxy_manager_list_all (DBusGProxyManager *manager)
{
  GSList *ret;

  ret = NULL;

  if (manager->proxy_lists)
    {
      g_hash_table_foreach (manager->proxy_lists,
                            list_proxies_foreach,
                            &ret);
    }

  return ret;
}

Project: dbus

dbus_g_proxy_manager_monitor_name_owner (DBusGProxyManager  *manager,
					 const char         *owner,
					 const char         *name)
{
  GSList *names;
  GSList *link;
  DBusGProxyNameOwnerInfo *nameinfo;

  names = g_hash_table_lookup (manager->owner_names, owner);
  link = g_slist_find_custom (names, name, find_name_in_info);
  
  if (!link)
    {
      nameinfo = g_new0 (DBusGProxyNameOwnerInfo, 1);
      nameinfo->name = g_strdup (name);
      nameinfo->refcount = 1;

      insert_nameinfo (manager, owner, nameinfo);
    }
  else
    {
      nameinfo = link->data;
      nameinfo->refcount++;
    }
}

Project: dbus

dbus_g_proxy_manager_register (DBusGProxyManager *manager,
                               DBusGProxy        *proxy)
{
  DBusGProxyList *list;
  DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy);

  LOCK_MANAGER (manager);

  if (manager->proxy_lists == NULL)
    {
      g_assert (manager->owner_names == NULL);
      g_assert (manager->owner_match_rules == NULL);

      list = NULL;
      manager->proxy_lists = g_hash_table_new_full (tristring_hash,
                                                    tristring_equal,
                                                    NULL,
                                                    (GFreeFunc) g_proxy_list_free);
      manager->owner_names = g_hash_table_new_full (g_str_hash,
                                                    g_str_equal,
                                                    g_free,
                                                    NULL);
      manager->owner_match_rules = g_hash_table_new_full (g_str_hash,
                                                          g_str_equal,
                                                          g_free,
                                                          guint_slice_free);
    }
  else
    {
      char *tri;

      tri = tristring_from_proxy (proxy);
      
      list = g_hash_table_lookup (manager->proxy_lists, tri);

      g_free (tri);
    }
      
  if (list == NULL)
    {
      list = g_proxy_list_new (proxy);
      
      g_hash_table_replace (manager->proxy_lists,
                            list->name, list);
    }

  if (list->proxies == NULL && priv->name)
    {
      /* We have to add match rules to the server,
       * but only if the server is a message bus,
       * not if it's a peer.
       */
      char *rule;
      guint *refcount;

      rule = g_proxy_get_signal_match_rule (proxy);
      /* We don't check for errors; it's not like anyone would handle them, and
       * we don't want a round trip here.
       */
      dbus_bus_add_match (manager->connection, rule, NULL);
      g_free (rule);
       
      refcount = g_hash_table_lookup (manager->owner_match_rules, priv->name);

      if (refcount != NULL)
        {
          g_assert (*refcount != 0);
          g_assert (*refcount < G_MAXUINT);
          (*refcount)++;
        }
      else
        {
          char *rule;
          rule = get_owner_match_rule (priv->name);
          dbus_bus_add_match (manager->connection,
                              rule, NULL);
          g_free (rule);

          refcount = g_slice_new (guint);
          *refcount = 1;
          g_hash_table_insert (manager->owner_match_rules,
                               g_strdup (priv->name), refcount);
        }
    }

  g_assert (g_slist_find (list->proxies, proxy) == NULL);
  
  list->proxies = g_slist_prepend (list->proxies, proxy);

  if (!priv->for_owner)
    {
      const char *owner;
      DBusGProxyNameOwnerInfo *info;

      if (!dbus_g_proxy_manager_lookup_name_owner (manager, priv->name, &info, &owner))
	{
	  priv->name_call = manager_begin_bus_call (manager, "GetNameOwner",
						     got_name_owner_cb,
						     proxy, NULL,
						     G_TYPE_STRING,
						     priv->name, 
						     G_TYPE_INVALID);
	  
	  priv->associated = FALSE;
	}
      else
	{
	  info->refcount++;
	  priv->associated = TRUE;
	}
    }
  
  UNLOCK_MANAGER (manager);
}

Project: dbus

dbus_g_proxy_manager_unmonitor_name_owner (DBusGProxyManager  *manager,
					   const char         *name)
{
  DBusGProxyNameOwnerInfo *info;
  const char *owner;
  gboolean ret;

  ret = dbus_g_proxy_manager_lookup_name_owner (manager, name, &info, &owner);
  g_assert (ret);
  g_assert (info != NULL);
  g_assert (owner != NULL);

  info->refcount--;
  if (info->refcount == 0)
    {
      GSList *names;
      GSList *link;

      names = g_hash_table_lookup (manager->owner_names, owner);
      link = g_slist_find_custom (names, name, find_name_in_info);
      names = g_slist_delete_link (names, link);
      if (names != NULL)
	g_hash_table_insert (manager->owner_names, g_strdup (owner), names);
      else
	g_hash_table_remove (manager->owner_names, owner);

      g_free (info->name);
      g_free (info);
    }
}

Project: dbus

dbus_g_proxy_manager_unref (DBusGProxyManager *manager)
{
  g_assert (manager != NULL);
  g_assert (manager->refcount > 0);

  LOCK_MANAGER (manager);
  manager->refcount -= 1;
  if (manager->refcount == 0)
    {
      UNLOCK_MANAGER (manager);

      if (manager->bus_proxy)
	g_object_unref (manager->bus_proxy);

      if (manager->proxy_lists)
        {
          /* can't have any proxies left since they hold
           * a reference to the proxy manager.
           */
          g_assert (g_hash_table_size (manager->proxy_lists) == 0);
          
          g_hash_table_destroy (manager->proxy_lists);
          manager->proxy_lists = NULL;

        }

      if (manager->owner_match_rules)
        {
	  /* Since we destroyed all proxies, none can be tracking
	   * name owners
	   */
          g_assert (g_hash_table_size (manager->owner_match_rules) == 0);
          g_hash_table_destroy (manager->owner_match_rules);
          manager->owner_match_rules = NULL;
        }

      if (manager->owner_names)
	{
	  /* Since we destroyed all proxies, none can be tracking
	   * name owners
	   */
          g_assert (g_hash_table_size (manager->owner_names) == 0);

	  g_hash_table_destroy (manager->owner_names);
	  manager->owner_names = NULL;
	}

      g_assert (manager->unassociated_proxies == NULL);
      
      g_static_mutex_free (&manager->lock);

      g_static_mutex_lock (&connection_g_proxy_lock);

      dbus_connection_remove_filter (manager->connection, dbus_g_proxy_manager_filter,
                                     manager);
      
      dbus_connection_set_data (manager->connection,
                                g_proxy_manager_slot,
                                NULL, NULL);

      g_static_mutex_unlock (&connection_g_proxy_lock);
      
      dbus_connection_unref (manager->connection);
      g_free (manager);

      dbus_connection_free_data_slot (&g_proxy_manager_slot);
    }
  else
    {
      UNLOCK_MANAGER (manager);
    }
}

Project: dbus

g_proxy_get_signal_match_rule (DBusGProxy *proxy)
{
  DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy);
  /* FIXME Escaping is required here */
  
  if (priv->name)
    return g_strdup_printf ("type='signal',sender='%s',path='%s',interface='%s'",
                            priv->name, priv->path, priv->interface);
  else
    return g_strdup_printf ("type='signal',path='%s',interface='%s'",
                            priv->path, priv->interface);
}

Project: dbus

g_proxy_list_free (DBusGProxyList *list)
{
  /* we don't hold a reference to the proxies in the list,
   * as they ref the GProxyManager
   */
  g_slist_free (list->proxies);  

  g_free (list);
}

Project: dbus

g_proxy_list_new (DBusGProxy *first_proxy)
{
  DBusGProxyList *list;
  DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(first_proxy);
  
  list = (void*) tristring_alloc_from_strings (G_STRUCT_OFFSET (DBusGProxyList, name),
                                               priv->name,
                                               priv->path,
                                               priv->interface);
  list->proxies = NULL;

  return list;
}

Project: dbus

get_name_owner (DBusConnection     *connection,
		const char         *name,
		GError            **error)
{
  DBusError derror;
  DBusMessage *request, *reply;
  char *base_name;
  
  dbus_error_init (&derror);

  base_name = NULL;
  reply = NULL;

  request = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
					  DBUS_PATH_DBUS,
					  DBUS_INTERFACE_DBUS,
					  "GetNameOwner");
  if (request == NULL)
    g_error ("Out of memory");
  
  if (!dbus_message_append_args (request, 
				 DBUS_TYPE_STRING, &name, 
				 DBUS_TYPE_INVALID))
    g_error ("Out of memory");

  reply =
    dbus_connection_send_with_reply_and_block (connection,
                                               request,
                                               2000, &derror);
  if (reply == NULL)
    goto error;

  if (dbus_set_error_from_message (&derror, reply))
    goto error;

  if (!dbus_message_get_args (reply, &derror, 
			      DBUS_TYPE_STRING, &base_name, 
			      DBUS_TYPE_INVALID))
    goto error;

  base_name = g_strdup (base_name);
  goto out;

 error:
  g_assert (dbus_error_is_set (&derror));
  dbus_set_g_error (error, &derror);
  dbus_error_free (&derror);

 out:
  if (request)
    dbus_message_unref (request);
  if (reply)
    dbus_message_unref (reply);

  return base_name;
}

Project: dbus

get_owner_match_rule (const gchar *name)
{
      return g_strdup_printf ("type='signal',sender='" DBUS_SERVICE_DBUS
        "',path='" DBUS_PATH_DBUS
        "',interface='" DBUS_INTERFACE_DBUS
        "',member='NameOwnerChanged'"
        ",arg0='%s'", name);
}

Project: dbus

guint_slice_free (gpointer data)
{
  g_slice_free (guint, data);
}

Project: dbus

insert_nameinfo (DBusGProxyManager       *manager,
		 const char              *owner,
		 DBusGProxyNameOwnerInfo *info)
{
  GSList *names;
  gboolean insert;

  names = g_hash_table_lookup (manager->owner_names, owner);

  /* Only need to g_hash_table_insert the first time */
  insert = (names == NULL);

  names = g_slist_append (names, info); 

  if (insert)
    g_hash_table_insert (manager->owner_names, g_strdup (owner), names);
}

Project: dbus

list_proxies_foreach (gpointer key,
                      gpointer value,
                      gpointer user_data)
{
  DBusGProxyList *list;
  GSList **ret;
  GSList *tmp;
  
  list = value;
  ret = user_data;

  tmp = list->proxies;
  while (tmp != NULL)
    {
      DBusGProxy *proxy = DBUS_G_PROXY (tmp->data);

      g_object_ref (proxy);
      *ret = g_slist_prepend (*ret, proxy);
      
      tmp = tmp->next;
    }
}

Project: dbus

oom (void)
{
  g_error ("no memory");
}

Project: dbus

strequal_len (const char *a,
              const char *b,
              size_t     *lenp)
{
  size_t a_len;
  size_t b_len;

  a_len = strlen (a);
  b_len = strlen (b);

  if (a_len != b_len)
    return FALSE;

  if (memcmp (a, b, a_len) != 0)
    return FALSE;
  
  *lenp = a_len;

  return TRUE;
}

Project: dbus

tristring_alloc_from_strings (size_t      padding_before,
                              const char *name,
                              const char *path,
                              const char *interface)
{
  size_t name_len, iface_len, path_len, len;
  char *tri;
  
  if (name)
    name_len = strlen (name);
  else
    name_len = 0;

  path_len = strlen (path);
  
  iface_len = strlen (interface);

  tri = g_malloc (padding_before + name_len + path_len + iface_len + 3);

  len = padding_before;
  
  if (name)
    memcpy (&tri[len], name, name_len);

  len += name_len;
  tri[len] = '\0';
  len += 1;

  g_assert (len == (padding_before + name_len + 1));
  
  memcpy (&tri[len], path, path_len);
  len += path_len;
  tri[len] = '\0';
  len += 1;

  g_assert (len == (padding_before + name_len + path_len + 2));
  
  memcpy (&tri[len], interface, iface_len);
  len += iface_len;
  tri[len] = '\0';
  len += 1;

  g_assert (len == (padding_before + name_len + path_len + iface_len + 3));

  return tri;
}

Project: dbus

tristring_equal (gconstpointer  a,
                 gconstpointer  b)
{
  const char *ap = a;
  const char *bp = b;
  size_t len;

  if (!strequal_len (ap, bp, &len))
    return FALSE;

  ap += len + 1;
  bp += len + 1;

  if (!strequal_len (ap, bp, &len))
    return FALSE;

  ap += len + 1;
  bp += len + 1;

  if (strcmp (ap, bp) != 0)
    return FALSE;
  
  return TRUE;
}

Project: dbus

tristring_from_message (DBusMessage *message)
{
  const char *path;
  const char *interface;

  path = dbus_message_get_path (message);
  interface = dbus_message_get_interface (message);

  g_assert (path);
  g_assert (interface);
  
  return tristring_alloc_from_strings (0,
                                       dbus_message_get_sender (message),
                                       path, interface);
}

Project: dbus

tristring_from_proxy (DBusGProxy *proxy)
{
  DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy);

  return tristring_alloc_from_strings (0,
                                       priv->name,
                                       priv->path,
                                       priv->interface);
}

Project: dbus

tristring_hash (gconstpointer key)
{
  const char *p = key;
  guint h = *p;

  if (h)
    {
      for (p += 1; *p != '\0'; p++)
        h = (h << 5) - h + *p;
    }

  /* skip nul and do the next substring */
  for (p += 1; *p != '\0'; p++)
    h = (h << 5) - h + *p;

  /* skip nul again and another substring */
  for (p += 1; *p != '\0'; p++)
    h = (h << 5) - h + *p;
  
  return h;
}

Project: dbus

unassociate_proxies (gpointer key, gpointer val, gpointer user_data)
{
  DBusGProxyList *list;
  const char *name;
  GSList *tmp;
  DBusGProxyUnassociateData *data;

  list = val;
  data = user_data;
  name = data->name;
  
  for (tmp = list->proxies; tmp; tmp = tmp->next)
    {
      DBusGProxy *proxy = DBUS_G_PROXY (tmp->data);
      DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy);
      DBusGProxyManager *manager;

      manager = priv->manager;

      if (priv->name != NULL && !strcmp (priv->name, name))
	{
	  if (!priv->for_owner)
	    {
	      /* If a service appeared and then vanished very quickly,
	       * it's conceivable we have an inflight request for
	       * GetNameOwner here.  Cancel it.
	       * https://bugs.freedesktop.org/show_bug.cgi?id=18573
	       */
	      if (priv->name_call)
		dbus_g_proxy_cancel_call (manager->bus_proxy, priv->name_call);

	      priv->name_call = NULL;

	      priv->associated = FALSE;
	      manager->unassociated_proxies = g_slist_prepend (manager->unassociated_proxies, proxy);
	    }
	  else
	    {
	      data->destroyed = g_slist_prepend (data->destroyed, proxy);
              /* make contents of list into weak pointers in case the objects
               * unref each other when disposing */
              g_object_add_weak_pointer (G_OBJECT (proxy),
                  &(data->destroyed->data));
	    }
	}
    }
}