]> git.ipfire.org Git - thirdparty/dbus.git/commitdiff
sysdeps: Get complete group vector from Linux SO_PEERGROUPS if possible
authorSimon McVittie <smcv@collabora.com>
Mon, 15 Jan 2018 19:44:45 +0000 (19:44 +0000)
committerSimon McVittie <smcv@collabora.com>
Fri, 2 Mar 2018 14:51:09 +0000 (14:51 +0000)
Signed-off-by: Simon McVittie <smcv@collabora.com>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=103737
Reviewed-by: Philip Withnall <withnall@endlessm.com>
dbus/dbus-auth.c
dbus/dbus-credentials.c
dbus/dbus-credentials.h
dbus/dbus-sysdeps-unix.c

index 3182026ca6f13e6d96ae7b8d51680ec36a277b44..0b886739a7a85adb0a512826b3acbbdc3498d90e 100644 (file)
@@ -1165,6 +1165,11 @@ handle_server_data_external_mech (DBusAuth         *auth,
                                              auth->credentials))
         return FALSE;
 
+      if (!_dbus_credentials_add_credential (auth->authorized_identity,
+                                             DBUS_CREDENTIAL_UNIX_GROUP_IDS,
+                                             auth->credentials))
+        return FALSE;
+
       if (!_dbus_credentials_add_credential (auth->authorized_identity,
                                              DBUS_CREDENTIAL_LINUX_SECURITY_LABEL,
                                              auth->credentials))
index 5fa754c2cbc37f19d7726c3bc86a304ed24a83a9..63c4b90fd48247ce0d2a4823bafb07356890b03f 100644 (file)
@@ -21,6 +21,7 @@
  *
  */
 #include <config.h>
+#include <stdlib.h>
 #include <string.h>
 #include "dbus-credentials.h"
 #include "dbus-internals.h"
@@ -48,6 +49,8 @@
 struct DBusCredentials {
   int refcount;
   dbus_uid_t unix_uid;
+  dbus_gid_t *unix_gids;
+  size_t n_unix_gids;
   dbus_pid_t pid;
   char *windows_sid;
   char *linux_security_label;
@@ -78,6 +81,8 @@ _dbus_credentials_new (void)
   
   creds->refcount = 1;
   creds->unix_uid = DBUS_UID_UNSET;
+  creds->unix_gids = NULL;
+  creds->n_unix_gids = 0;
   creds->pid = DBUS_PID_UNSET;
   creds->windows_sid = NULL;
   creds->linux_security_label = NULL;
@@ -134,6 +139,7 @@ _dbus_credentials_unref (DBusCredentials    *credentials)
   credentials->refcount -= 1;
   if (credentials->refcount == 0)
     {
+      dbus_free (credentials->unix_gids);
       dbus_free (credentials->windows_sid);
       dbus_free (credentials->linux_security_label);
       dbus_free (credentials->adt_audit_data);
@@ -172,6 +178,63 @@ _dbus_credentials_add_unix_uid(DBusCredentials    *credentials,
 
 }
 
+static int
+cmp_gidp (const void *a_, const void *b_)
+{
+  const dbus_gid_t *a = a_;
+  const dbus_gid_t *b = b_;
+
+  if (*a < *b)
+    return -1;
+
+  if (*a > *b)
+    return 1;
+
+  return 0;
+}
+
+/**
+ * Add UNIX group IDs to the credentials, replacing any group IDs that
+ * might already have been present.
+ *
+ * @param credentials the object
+ * @param gids the group IDs, which will be freed by the DBusCredentials object
+ * @param n_gids the number of group IDs
+ */
+void
+_dbus_credentials_take_unix_gids (DBusCredentials *credentials,
+                                  dbus_gid_t      *gids,
+                                  size_t           n_gids)
+{
+  /* So we can compare arrays via a simple memcmp */
+  qsort (gids, n_gids, sizeof (dbus_gid_t), cmp_gidp);
+
+  dbus_free (credentials->unix_gids);
+  credentials->unix_gids = gids;
+  credentials->n_unix_gids = n_gids;
+}
+
+/**
+ * Get the Unix group IDs.
+ *
+ * @param credentials the object
+ * @param gids the group IDs, which will be freed by the DBusCredentials object
+ * @param n_gids the number of group IDs
+ */
+dbus_bool_t
+_dbus_credentials_get_unix_gids (DBusCredentials   *credentials,
+                                 const dbus_gid_t **gids,
+                                 size_t            *n_gids)
+{
+  if (gids != NULL)
+    *gids = credentials->unix_gids;
+
+  if (n_gids != NULL)
+    *n_gids = credentials->n_unix_gids;
+
+  return (credentials->unix_gids != NULL);
+}
+
 /**
  * Add a Windows user SID to the credentials.
  *
@@ -261,6 +324,8 @@ _dbus_credentials_include (DBusCredentials    *credentials,
       return credentials->pid != DBUS_PID_UNSET;
     case DBUS_CREDENTIAL_UNIX_USER_ID:
       return credentials->unix_uid != DBUS_UID_UNSET;
+    case DBUS_CREDENTIAL_UNIX_GROUP_IDS:
+      return credentials->unix_gids != NULL;
     case DBUS_CREDENTIAL_WINDOWS_SID:
       return credentials->windows_sid != NULL;
     case DBUS_CREDENTIAL_LINUX_SECURITY_LABEL:
@@ -368,6 +433,10 @@ _dbus_credentials_are_superset (DBusCredentials    *credentials,
      possible_subset->pid == credentials->pid) &&
     (possible_subset->unix_uid == DBUS_UID_UNSET ||
      possible_subset->unix_uid == credentials->unix_uid) &&
+    (possible_subset->unix_gids == NULL ||
+     (possible_subset->n_unix_gids == credentials->n_unix_gids &&
+      memcmp (possible_subset->unix_gids, credentials->unix_gids,
+              sizeof (dbus_gid_t) * credentials->n_unix_gids) == 0)) &&
     (possible_subset->windows_sid == NULL ||
      (credentials->windows_sid && strcmp (possible_subset->windows_sid,
                                           credentials->windows_sid) == 0)) &&
@@ -393,6 +462,8 @@ _dbus_credentials_are_empty (DBusCredentials    *credentials)
   return
     credentials->pid == DBUS_PID_UNSET &&
     credentials->unix_uid == DBUS_UID_UNSET &&
+    credentials->unix_gids == NULL &&
+    credentials->n_unix_gids == 0 &&
     credentials->windows_sid == NULL &&
     credentials->linux_security_label == NULL &&
     credentials->adt_audit_data == NULL;
@@ -431,6 +502,9 @@ _dbus_credentials_add_credentials (DBusCredentials    *credentials,
     _dbus_credentials_add_credential (credentials,
                                       DBUS_CREDENTIAL_UNIX_USER_ID,
                                       other_credentials) &&
+    _dbus_credentials_add_credential (credentials,
+                                      DBUS_CREDENTIAL_UNIX_GROUP_IDS,
+                                      other_credentials) &&
     _dbus_credentials_add_credential (credentials,
                                       DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID,
                                       other_credentials) &&
@@ -471,6 +545,22 @@ _dbus_credentials_add_credential (DBusCredentials    *credentials,
       if (!_dbus_credentials_add_unix_uid (credentials, other_credentials->unix_uid))
         return FALSE;
     }
+  else if (which == DBUS_CREDENTIAL_UNIX_GROUP_IDS &&
+           other_credentials->unix_gids != NULL)
+    {
+      dbus_gid_t *gids;
+
+      gids = dbus_new (dbus_gid_t, other_credentials->n_unix_gids);
+
+      if (gids == NULL)
+        return FALSE;
+
+      memcpy (gids, other_credentials->unix_gids,
+              sizeof (dbus_gid_t) * other_credentials->n_unix_gids);
+
+      _dbus_credentials_take_unix_gids (credentials, gids,
+                                        other_credentials->n_unix_gids);
+    }
   else if (which == DBUS_CREDENTIAL_WINDOWS_SID &&
            other_credentials->windows_sid != NULL)
     {
@@ -504,6 +594,9 @@ _dbus_credentials_clear (DBusCredentials    *credentials)
 {
   credentials->pid = DBUS_PID_UNSET;
   credentials->unix_uid = DBUS_UID_UNSET;
+  dbus_free (credentials->unix_gids);
+  credentials->unix_gids = NULL;
+  credentials->n_unix_gids = 0;
   dbus_free (credentials->windows_sid);
   credentials->windows_sid = NULL;
   dbus_free (credentials->linux_security_label);
@@ -590,6 +683,22 @@ _dbus_credentials_to_string_append (DBusCredentials    *credentials,
     }
   else
     join = FALSE;
+
+  if (credentials->unix_gids != NULL)
+    {
+      size_t i;
+
+      for (i = 0; i < credentials->n_unix_gids; i++)
+        {
+          if (!_dbus_string_append_printf (string, "%sgid=" DBUS_GID_FORMAT,
+                                           join ? " " : "",
+                                           credentials->unix_gids[i]))
+            goto oom;
+
+          join = TRUE;
+        }
+    }
+
   if (credentials->windows_sid != NULL)
     {
       if (!_dbus_string_append_printf (string, "%ssid=%s", join ? " " : "", credentials->windows_sid))
index 6bf6c2b120bf4b1548ea2b30dc78bbc5b826bde2..3285b50f0e98dce3ae07fc6b5400e5fafaf81784 100644 (file)
@@ -33,6 +33,7 @@ DBUS_BEGIN_DECLS
 typedef enum {
   DBUS_CREDENTIAL_UNIX_PROCESS_ID,
   DBUS_CREDENTIAL_UNIX_USER_ID,
+  DBUS_CREDENTIAL_UNIX_GROUP_IDS,
   DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID,
   DBUS_CREDENTIAL_LINUX_SECURITY_LABEL,
   DBUS_CREDENTIAL_WINDOWS_SID
@@ -53,6 +54,10 @@ DBUS_PRIVATE_EXPORT
 dbus_bool_t      _dbus_credentials_add_unix_uid             (DBusCredentials    *credentials,
                                                              dbus_uid_t          uid);
 DBUS_PRIVATE_EXPORT
+void             _dbus_credentials_take_unix_gids           (DBusCredentials    *credentials,
+                                                             dbus_gid_t         *gids,
+                                                             size_t              n_gids);
+DBUS_PRIVATE_EXPORT
 dbus_bool_t      _dbus_credentials_add_windows_sid          (DBusCredentials    *credentials,
                                                              const char         *windows_sid);
 dbus_bool_t      _dbus_credentials_add_linux_security_label (DBusCredentials    *credentials,
@@ -68,6 +73,10 @@ dbus_pid_t       _dbus_credentials_get_pid                  (DBusCredentials
 DBUS_PRIVATE_EXPORT
 dbus_uid_t       _dbus_credentials_get_unix_uid             (DBusCredentials    *credentials);
 DBUS_PRIVATE_EXPORT
+dbus_bool_t      _dbus_credentials_get_unix_gids            (DBusCredentials    *credentials,
+                                                             const dbus_gid_t  **gids,
+                                                             size_t             *n_gids);
+DBUS_PRIVATE_EXPORT
 const char*      _dbus_credentials_get_windows_sid          (DBusCredentials    *credentials);
 const char *     _dbus_credentials_get_linux_security_label (DBusCredentials    *credentials);
 void *           _dbus_credentials_get_adt_audit_data       (DBusCredentials    *credentials);
index 9f859845d41cb7aee6cf5c7ded9bd1bda014dbc7..f1764416e3a8e63a3a1373ff57fadb2d5e05bf41 100644 (file)
@@ -1748,6 +1748,129 @@ write_credentials_byte (int             server_fd,
     }
 }
 
+/* return FALSE on OOM, TRUE otherwise, even if no groups were found */
+static dbus_bool_t
+add_groups_to_credentials (int              client_fd,
+                           DBusCredentials *credentials,
+                           dbus_gid_t       primary)
+{
+#if defined(__linux__) && defined(SO_PEERGROUPS)
+  _DBUS_STATIC_ASSERT (sizeof (gid_t) <= sizeof (dbus_gid_t));
+  gid_t *buf = NULL;
+  socklen_t len = 1024;
+  dbus_bool_t oom = FALSE;
+  /* libdbus has a different representation of group IDs just to annoy you */
+  dbus_gid_t *converted_gids = NULL;
+  dbus_bool_t need_primary = TRUE;
+  size_t n_gids;
+  size_t i;
+
+  n_gids = ((size_t) len) / sizeof (gid_t);
+  buf = dbus_new (gid_t, n_gids);
+
+  if (buf == NULL)
+    return FALSE;
+
+  while (getsockopt (client_fd, SOL_SOCKET, SO_PEERGROUPS, buf, &len) < 0)
+    {
+      int e = errno;
+      gid_t *replacement;
+
+      _dbus_verbose ("getsockopt failed with %s, len now %lu\n",
+                     _dbus_strerror (e), (unsigned long) len);
+
+      if (e != ERANGE || (size_t) len <= n_gids * sizeof (gid_t))
+        {
+          _dbus_verbose ("Failed to getsockopt(SO_PEERGROUPS): %s\n",
+                         _dbus_strerror (e));
+          goto out;
+        }
+
+      /* If not enough space, len is updated to be enough.
+       * Try again with a large enough buffer. */
+      n_gids = ((size_t) len) / sizeof (gid_t);
+      replacement = dbus_realloc (buf, len);
+
+      if (replacement == NULL)
+        {
+          oom = TRUE;
+          goto out;
+        }
+
+      buf = replacement;
+      _dbus_verbose ("will try again with %lu\n", (unsigned long) len);
+    }
+
+  if (len <= 0)
+    {
+      _dbus_verbose ("getsockopt(SO_PEERGROUPS) yielded <= 0 bytes: %ld\n",
+                     (long) len);
+      goto out;
+    }
+
+  if (len > n_gids * sizeof (gid_t))
+    {
+      _dbus_verbose ("%lu > %zu", (unsigned long) len, n_gids * sizeof (gid_t));
+      _dbus_assert_not_reached ("getsockopt(SO_PEERGROUPS) overflowed");
+    }
+
+  if (len % sizeof (gid_t) != 0)
+    {
+      _dbus_verbose ("getsockopt(SO_PEERGROUPS) did not return an "
+                     "integer multiple of sizeof(gid_t): %lu should be "
+                     "divisible by %zu",
+                     (unsigned long) len, sizeof (gid_t));
+      goto out;
+    }
+
+  /* Allocate an extra space for the primary group ID */
+  n_gids = ((size_t) len) / sizeof (gid_t);
+
+  /* If n_gids is less than this, then (n_gids + 1) certainly doesn't
+   * overflow, and neither does multiplying that by sizeof(dbus_gid_t).
+   * This is using _DBUS_INT32_MAX as a conservative lower bound for
+   * the maximum size_t. */
+  if (n_gids >= (_DBUS_INT32_MAX / sizeof (dbus_gid_t)) - 1)
+    {
+      _dbus_verbose ("getsockopt(SO_PEERGROUPS) returned a huge number "
+                     "of groups (%lu bytes), ignoring",
+                     (unsigned long) len);
+      goto out;
+    }
+
+  converted_gids = dbus_new (dbus_gid_t, n_gids + 1);
+
+  if (converted_gids == NULL)
+    {
+      oom = TRUE;
+      goto out;
+    }
+
+  for (i = 0; i < n_gids; i++)
+    {
+      converted_gids[i] = (dbus_gid_t) buf[i];
+
+      if (converted_gids[i] == primary)
+        need_primary = FALSE;
+    }
+
+  if (need_primary && primary != DBUS_GID_UNSET)
+    {
+      converted_gids[n_gids] = primary;
+      n_gids++;
+    }
+
+  _dbus_credentials_take_unix_gids (credentials, converted_gids, n_gids);
+
+out:
+  dbus_free (buf);
+  return !oom;
+#else
+  /* no error */
+  return TRUE;
+#endif
+}
+
 /* return FALSE on OOM, TRUE otherwise, even if no credentials were found */
 static dbus_bool_t
 add_linux_security_label_to_credentials (int              client_fd,
@@ -1896,6 +2019,7 @@ _dbus_read_credentials_socket  (DBusSocket       client_fd,
   struct iovec iov;
   char buf;
   dbus_uid_t uid_read;
+  dbus_gid_t primary_gid_read;
   dbus_pid_t pid_read;
   int bytes_read;
 
@@ -1915,6 +2039,7 @@ _dbus_read_credentials_socket  (DBusSocket       client_fd,
   _DBUS_STATIC_ASSERT (sizeof (gid_t) <= sizeof (dbus_gid_t));
 
   uid_read = DBUS_UID_UNSET;
+  primary_gid_read = DBUS_GID_UNSET;
   pid_read = DBUS_PID_UNSET;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
@@ -2001,6 +2126,12 @@ _dbus_read_credentials_socket  (DBusSocket       client_fd,
       {
         pid_read = cr.pid;
         uid_read = cr.uid;
+#ifdef __linux__
+        /* Do other platforms have cr.gid? (Not that it really matters,
+         * because the gid is useless to us unless we know the complete
+         * group vector, which we only know on Linux.) */
+        primary_gid_read = cr.gid;
+#endif
       }
 #elif defined(HAVE_UNPCBID) && defined(LOCAL_PEEREID)
     /* Another variant of the above - used on NetBSD
@@ -2181,6 +2312,14 @@ _dbus_read_credentials_socket  (DBusSocket       client_fd,
       return FALSE;
     }
 
+  /* We don't put any groups in the credentials unless we can put them
+   * all there. */
+  if (!add_groups_to_credentials (client_fd.fd, credentials, primary_gid_read))
+    {
+      _DBUS_SET_OOM (error);
+      return FALSE;
+    }
+
   return TRUE;
 }