Commit b6648ca6 authored by Simon McVittie's avatar Simon McVittie

sysdeps: Get complete group vector from Linux SO_PEERGROUPS if possible

Signed-off-by: Simon McVittie's avatarSimon McVittie <smcv@collabora.com>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=103737Reviewed-by: Philip Withnall's avatarPhilip Withnall <withnall@endlessm.com>
parent 5a07eda3
......@@ -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))
......
......@@ -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))
......
......@@ -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);
......
......@@ -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;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment