]> git.ipfire.org Git - thirdparty/dbus.git/commitdiff
userdb: Reference-count DBusUserInfo, DBusGroupInfo
authorSimon McVittie <smcv@collabora.com>
Tue, 30 Jun 2020 18:29:06 +0000 (19:29 +0100)
committerSimon McVittie <smcv@collabora.com>
Wed, 1 Jul 2020 12:21:23 +0000 (13:21 +0100)
Previously, the hash table indexed by uid (or gid) took ownership of the
single reference to the heap-allocated struct, and the hash table
indexed by username (or group name) had a borrowed pointer to the same
struct that exists in the other hash table.

However, this can break down if you have two or more distinct usernames
that share a numeric identifier. This is generally a bad idea, because
the user-space model in such situations does not match the kernel-space
reality, and in particular there is no effective kernel-level security
boundary between such users, but it is sometimes done anyway.

In this case, when the second username is looked up in the userdb, it
overwrites (replaces) the entry in the hash table that is indexed by
uid, freeing the DBusUserInfo. This results in both the key and the
value in the hash table that is indexed by username becoming dangling
pointers (use-after-free), leading to undefined behaviour, which is
certainly not what we want to see when doing access control.

An equivalent situation can occur with groups, in the rare case where
a numeric group ID has two names (although I have not heard of this
being done in practice).

Solve this by reference-counting the data structure. There are up to
three references in practice: one held temporarily while the lookup
function is populating and storing it, one held by the hash table that
is indexed by uid, and one held by the hash table that is indexed by
name.

Closes: dbus#305
Signed-off-by: Simon McVittie <smcv@collabora.com>
dbus/dbus-sysdeps-unix.h
dbus/dbus-userdb-util.c
dbus/dbus-userdb.c
dbus/dbus-userdb.h

index 3bbf34d1a525bdb7071a0fba973b06f143782c79..e86de6d94eb02e398663a9da3994dec54a4d5710 100644 (file)
@@ -105,6 +105,7 @@ typedef struct DBusGroupInfo DBusGroupInfo;
  */
 struct DBusUserInfo
 {
+  size_t      refcount;       /**< Reference count */
   dbus_uid_t  uid;            /**< UID */
   dbus_gid_t  primary_gid;    /**< GID */
   dbus_gid_t *group_ids;      /**< Groups IDs, *including* above primary group */
@@ -118,6 +119,7 @@ struct DBusUserInfo
  */
 struct DBusGroupInfo
 {
+  size_t      refcount;       /**< Reference count */
   dbus_gid_t  gid;            /**< GID */
   char       *groupname;      /**< Group name */
 };
index 18621a17c11ba505500d2530ddd3b717bb180158..1ca21eb717138f89ba987fdf1fc7a12b2ab3dc9e 100644 (file)
  * @{
  */
 
+static DBusGroupInfo *
+_dbus_group_info_ref (DBusGroupInfo *info)
+{
+  _dbus_assert (info->refcount > 0);
+  _dbus_assert (info->refcount < SIZE_MAX);
+  info->refcount++;
+  return info;
+}
+
 /**
  * Checks to see if the UID sent in is the console user
  *
@@ -296,13 +305,14 @@ _dbus_user_database_lookup_group (DBusUserDatabase *db,
           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
           return NULL;
         }
+      info->refcount = 1;
 
       if (gid != DBUS_GID_UNSET)
         {
           if (!_dbus_group_info_fill_gid (info, gid, error))
             {
               _DBUS_ASSERT_ERROR_IS_SET (error);
-              _dbus_group_info_free_allocated (info);
+              _dbus_group_info_unref (info);
               return NULL;
             }
         }
@@ -311,7 +321,7 @@ _dbus_user_database_lookup_group (DBusUserDatabase *db,
           if (!_dbus_group_info_fill (info, groupname, error))
             {
               _DBUS_ASSERT_ERROR_IS_SET (error);
-              _dbus_group_info_free_allocated (info);
+              _dbus_group_info_unref (info);
               return NULL;
             }
         }
@@ -320,23 +330,35 @@ _dbus_user_database_lookup_group (DBusUserDatabase *db,
       gid = DBUS_GID_UNSET;
       groupname = NULL;
 
-      if (!_dbus_hash_table_insert_uintptr (db->groups, info->gid, info))
+      if (_dbus_hash_table_insert_uintptr (db->groups, info->gid, info))
+        {
+          _dbus_group_info_ref (info);
+        }
+      else
         {
           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
-          _dbus_group_info_free_allocated (info);
+          _dbus_group_info_unref (info);
           return NULL;
         }
 
 
-      if (!_dbus_hash_table_insert_string (db->groups_by_name,
-                                           info->groupname,
-                                           info))
+      if (_dbus_hash_table_insert_string (db->groups_by_name,
+                                          info->groupname,
+                                          info))
+        {
+          _dbus_group_info_ref (info);
+        }
+      else
         {
           _dbus_hash_table_remove_uintptr (db->groups, info->gid);
+          _dbus_group_info_unref (info);
           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
           return NULL;
         }
-      
+
+      /* Release the original reference */
+      _dbus_group_info_unref (info);
+
       /* Return a borrowed reference to the DBusGroupInfo owned by the
        * two hash tables */
       return info;
index 78169e5ba541f5968a8eb3bc662399d322d2ac98..ddf41a3c75f5b27239d409b45ccb8261e8a5136f 100644 (file)
  * @{
  */
 
+static DBusUserInfo *
+_dbus_user_info_ref (DBusUserInfo *info)
+{
+  _dbus_assert (info->refcount > 0);
+  _dbus_assert (info->refcount < SIZE_MAX);
+  info->refcount++;
+  return info;
+}
+
 /**
- * Frees the given #DBusUserInfo's members with _dbus_user_info_free()
+ * Decrements the reference count. If it reaches 0,
+ * frees the given #DBusUserInfo's members with _dbus_user_info_free()
  * and also calls dbus_free() on the block itself
  *
  * @param info the info
  */
 void
-_dbus_user_info_free_allocated (DBusUserInfo *info)
+_dbus_user_info_unref (DBusUserInfo *info)
 {
   if (info == NULL) /* hash table will pass NULL */
     return;
 
+  _dbus_assert (info->refcount > 0);
+  _dbus_assert (info->refcount < SIZE_MAX);
+
+  if (--info->refcount > 0)
+    return;
+
   _dbus_user_info_free (info);
   dbus_free (info);
 }
 
 /**
- * Frees the given #DBusGroupInfo's members with _dbus_group_info_free()
+ * Decrements the reference count. If it reaches 0,
+ * frees the given #DBusGroupInfo's members with _dbus_group_info_free()
  * and also calls dbus_free() on the block itself
  *
  * @param info the info
  */
 void
-_dbus_group_info_free_allocated (DBusGroupInfo *info)
+_dbus_group_info_unref (DBusGroupInfo *info)
 {
   if (info == NULL) /* hash table will pass NULL */
     return;
 
+  _dbus_assert (info->refcount > 0);
+  _dbus_assert (info->refcount < SIZE_MAX);
+
+  if (--info->refcount > 0)
+    return;
+
   _dbus_group_info_free (info);
   dbus_free (info);
 }
@@ -177,13 +200,14 @@ _dbus_user_database_lookup (DBusUserDatabase *db,
           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
           return NULL;
         }
+      info->refcount = 1;
 
       if (uid != DBUS_UID_UNSET)
         {
           if (!_dbus_user_info_fill_uid (info, uid, error))
             {
               _DBUS_ASSERT_ERROR_IS_SET (error);
-              _dbus_user_info_free_allocated (info);
+              _dbus_user_info_unref (info);
               return NULL;
             }
         }
@@ -192,7 +216,7 @@ _dbus_user_database_lookup (DBusUserDatabase *db,
           if (!_dbus_user_info_fill (info, username, error))
             {
               _DBUS_ASSERT_ERROR_IS_SET (error);
-              _dbus_user_info_free_allocated (info);
+              _dbus_user_info_unref (info);
               return NULL;
             }
         }
@@ -202,22 +226,33 @@ _dbus_user_database_lookup (DBusUserDatabase *db,
       username = NULL;
 
       /* insert into hash */
-      if (!_dbus_hash_table_insert_uintptr (db->users, info->uid, info))
+      if (_dbus_hash_table_insert_uintptr (db->users, info->uid, info))
+        {
+          _dbus_user_info_ref (info);
+        }
+      else
         {
           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
-          _dbus_user_info_free_allocated (info);
+          _dbus_user_info_unref (info);
           return NULL;
         }
 
-      if (!_dbus_hash_table_insert_string (db->users_by_name,
-                                           info->username,
-                                           info))
+      if (_dbus_hash_table_insert_string (db->users_by_name,
+                                          info->username,
+                                          info))
+        {
+          _dbus_user_info_ref (info);
+        }
+      else
         {
           _dbus_hash_table_remove_uintptr (db->users, info->uid);
           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          _dbus_user_info_unref (info);
           return NULL;
         }
-      
+
+      _dbus_user_info_unref (info);
+
       /* Return a borrowed pointer to the DBusUserInfo owned by the
        * hash tables */
       return info;
@@ -569,24 +604,24 @@ _dbus_user_database_new (void)
   db->refcount = 1;
 
   db->users = _dbus_hash_table_new (DBUS_HASH_UINTPTR,
-                                    NULL, (DBusFreeFunction) _dbus_user_info_free_allocated);
+                                    NULL, (DBusFreeFunction) _dbus_user_info_unref);
   
   if (db->users == NULL)
     goto failed;
 
   db->groups = _dbus_hash_table_new (DBUS_HASH_UINTPTR,
-                                     NULL, (DBusFreeFunction) _dbus_group_info_free_allocated);
+                                     NULL, (DBusFreeFunction) _dbus_group_info_unref);
   
   if (db->groups == NULL)
     goto failed;
 
   db->users_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
-                                            NULL, NULL);
+                                            NULL, (DBusFreeFunction) _dbus_user_info_unref);
   if (db->users_by_name == NULL)
     goto failed;
   
   db->groups_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
-                                             NULL, NULL);
+                                             NULL, (DBusFreeFunction) _dbus_group_info_unref);
   if (db->groups_by_name == NULL)
     goto failed;
   
index 479b2311cb84d1c4fcd028aa6097b123a417e317..fcb515c83ab4bf2d56ca1d310cc440bd812571e1 100644 (file)
@@ -76,10 +76,10 @@ const DBusGroupInfo* _dbus_user_database_lookup_group (DBusUserDatabase *db,
                                                        dbus_gid_t        gid,
                                                        const DBusString *groupname,
                                                        DBusError        *error);
+
+void           _dbus_user_info_unref            (DBusUserInfo     *info);
 DBUS_PRIVATE_EXPORT
-void           _dbus_user_info_free_allocated   (DBusUserInfo     *info);
-DBUS_PRIVATE_EXPORT
-void           _dbus_group_info_free_allocated  (DBusGroupInfo    *info);
+void           _dbus_group_info_unref           (DBusGroupInfo    *info);
 #endif /* DBUS_USERDB_INCLUDES_PRIVATE */
 
 DBUS_PRIVATE_EXPORT