*
*/
#include <config.h>
+#include <stdlib.h>
#include <string.h>
#include "dbus-credentials.h"
#include "dbus-internals.h"
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;
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;
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);
}
+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.
*
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:
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)) &&
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;
_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) &&
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)
{
{
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);
}
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))
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
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,
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);
}
}
+/* 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,
struct iovec iov;
char buf;
dbus_uid_t uid_read;
+ dbus_gid_t primary_gid_read;
dbus_pid_t pid_read;
int bytes_read;
_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);
{
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
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;
}