summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <smcv@collabora.com>2018-01-15 19:44:45 +0000
committerSimon McVittie <smcv@collabora.com>2018-03-02 14:51:09 +0000
commitb6648ca6550c0d407957e9a859b0cd3f9480827c (patch)
tree3de2a24d6bd79c0796dc163a8c8428874cdfb0e1
parent5a07eda31e6c2fd3d4965ed7ff46991ac86c615b (diff)
sysdeps: Get complete group vector from Linux SO_PEERGROUPS if possible
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>
-rw-r--r--dbus/dbus-auth.c5
-rw-r--r--dbus/dbus-credentials.c109
-rw-r--r--dbus/dbus-credentials.h9
-rw-r--r--dbus/dbus-sysdeps-unix.c139
4 files changed, 262 insertions, 0 deletions
diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c
index 3182026ca..0b886739a 100644
--- a/dbus/dbus-auth.c
+++ b/dbus/dbus-auth.c
@@ -1166,6 +1166,11 @@ handle_server_data_external_mech (DBusAuth *auth,
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))
return FALSE;
diff --git a/dbus/dbus-credentials.c b/dbus/dbus-credentials.c
index 5fa754c2c..63c4b90fd 100644
--- a/dbus/dbus-credentials.c
+++ b/dbus/dbus-credentials.c
@@ -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;
@@ -432,6 +503,9 @@ _dbus_credentials_add_credentials (DBusCredentials *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) &&
_dbus_credentials_add_credential (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))
diff --git a/dbus/dbus-credentials.h b/dbus/dbus-credentials.h
index 6bf6c2b12..3285b50f0 100644
--- a/dbus/dbus-credentials.h
+++ b/dbus/dbus-credentials.h
@@ -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);
diff --git a/dbus/dbus-sysdeps-unix.c b/dbus/dbus-sysdeps-unix.c
index 9f859845d..f1764416e 100644
--- a/dbus/dbus-sysdeps-unix.c
+++ b/dbus/dbus-sysdeps-unix.c
@@ -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;
}