From b142b0553075dd235518c441704268d85eef724e Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 15 Jan 2018 19:44:45 +0000 Subject: [PATCH 02/11] sysdeps: Get complete group vector from Linux SO_PEERGROUPS if possible Signed-off-by: Simon McVittie --- Use size_t for number of group IDs. Use NULL rather than 0 when checking whether malloc failed. Early-return if the kernel claims we have some ridiculous number of group IDs, to guarantee we don't overflow. --- dbus/dbus-auth.c | 5 ++ dbus/dbus-credentials.c | 109 +++++++++++++++++++++++++++++++++++++ dbus/dbus-credentials.h | 9 +++ dbus/dbus-sysdeps-unix.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 262 insertions(+) diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c index 3182026c..0b886739 100644 --- a/dbus/dbus-auth.c +++ b/dbus/dbus-auth.c @@ -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)) diff --git a/dbus/dbus-credentials.c b/dbus/dbus-credentials.c index 5fa754c2..63c4b90f 100644 --- a/dbus/dbus-credentials.c +++ b/dbus/dbus-credentials.c @@ -21,6 +21,7 @@ * */ #include +#include #include #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)) diff --git a/dbus/dbus-credentials.h b/dbus/dbus-credentials.h index 6bf6c2b1..3285b50f 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 9f859845..f1764416 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; } -- 2.16.1